初始化React
從 html 開始你的第一個 react 頁面。引入三個文件:react核心文件船逮、react-dom文件和轉(zhuǎn)化jsx的babel顾腊。
然后聲明一個變量,變量的值是一個不帶引號的標(biāo)簽挖胃,最后使用 ReactDOM.render 方法把該變量渲染到頁面上杂靶。
<div id="app"></div>
<script type="text/babel">
const VDOM = <h1>你好,里愛特</h1>
ReactDOM.render(VDOM,document.querySelector('#app'))
</script>
創(chuàng)建虛擬DOM的兩種方式
第一種:JSX語法
現(xiàn)階段需借助babel
<div id="app"></div>
<script type="text/babel">
const VDOM = (
<h1>
<p class="hello-react">你好酱鸭,里愛特</p>
</h1>
)
ReactDOM.render(VDOM,document.querySelector('#app'))
</script>
第二種:借助 React.createElement
此方法僅供了解吗垮,后續(xù)也不會使用
<div id="app"></div>
<script>
const VDOM = React.createElement('h1',{},React.createElement('span',{class:"hello-react"},'你好,里愛特'))
ReactDOM.render(VDOM,document.querySelector('#app'))
</script>
參數(shù)一凛辣,元素類型抱既。參數(shù)二职烧,配置對象扁誓,用于寫元素屬性防泵。參數(shù)三,元素的內(nèi)容
關(guān)于 虛擬DOM
<div id="app"></div>
<script type="text/babel">
const VDOM = <h1>你好蝗敢,里愛特</h1>
console.log(VDOM)
console.dir(document.querySeletor('#app'))
</script>
創(chuàng)建出來后打印臺輸出可以看到 VDOM 本質(zhì)上就是一個 object 類型的變量捷泞,身上帶有自身特有的虛擬DOM屬性。
與真實(shí) DOM 比較可以發(fā)現(xiàn) react 虛擬 DOM 很輕量
JSX 語法規(guī)則
- 虛擬DOM只能有一個根標(biāo)簽寿谴。
- 虛擬DOM中每一個標(biāo)簽都必須閉合锁右,單標(biāo)簽例如 input 也要寫成自閉合形式。
- JSX 標(biāo)簽變量不能帶引號讶泰。
- 虛擬DOM中如果想使用JS表達(dá)式咏瑟,需要包裹在花括號里面。
- 虛擬DOM中書寫樣式的兩個注意點(diǎn)
- 如果虛擬 DOM 中想要帶上 class 痪署,注意要寫成 className码泞,原因是跟ES6的class關(guān)鍵字沖突了。
- 如果虛擬 DOM 中想要寫內(nèi)聯(lián)樣式狼犯,style后面的值不能接字符串余寥,需要接一個 JS 表達(dá)式,表達(dá)式里面是對象形式悯森。
- 虛擬DOM中的標(biāo)簽如果是小寫字母開頭的標(biāo)簽宋舷,JSX 默認(rèn)解析為 html 原生標(biāo)簽。
虛擬DOM中的標(biāo)簽如果是大寫字母開頭的標(biāo)簽瓢姻,JSX 默認(rèn)解析為 react 組件祝蝠。 - 虛擬DOM注釋
{/* */}
<div id="app"></div>
<script type="text/babel">
const myData = '你好,里愛特幻碱!'
const jsxml = (
<div>
<h1 className="red">{myData}</h1>
<input type="text" style={
{color:'blue'}
}/>
<Good>顧得</Good>
</div>
)
ReactDOM.render(jsxml,document.querySelector('#app'))
</script>
使用 react 渲染可迭代數(shù)據(jù)為DOM
使用JS表達(dá)式的形式渲染可迭代數(shù)據(jù)续膳,例如用 map 渲染數(shù)組,map 的回調(diào)函數(shù) return 虛擬DOM收班,且必須攜帶 key 方便react執(zhí)行diff算法坟岔。
const data = ['angular','react','vue']
const VDOM = (
<div>
<h1>使用react渲染可迭代數(shù)據(jù)</h1>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
ReactDOM.render(VDOM,document.querySelector('#app'))
組件
在使用 ReactDOM.render() 方法的時(shí)候,第一個參數(shù)傳入一個標(biāo)簽摔桦,就會尋找與標(biāo)簽同名的 function 或 class社付,進(jìn)而去創(chuàng)建組件實(shí)例。
函數(shù)式組件(簡單組件)
function MyComponent(){
return <h2>我是react函數(shù)式組件邻耕,我的this指向?yàn)閲?yán)格模式下的js的undefined</h2>
}
ReactDOM.render(<MyComponent/>,document.querySelector('#app'))
類式組件(復(fù)雜組件)
類式組件式繼承 React.Component 父類的之類鸥咖,必須帶有 render() 方法,render中的 this 指向 組件實(shí)例對象兄世。
組件實(shí)例對象會在調(diào)用 ReactDOM.render 時(shí)候自動創(chuàng)建出來啼辣。
<div id="app"></div>
<script type="text/babel">
class MyComponent extends React.Component {
render() {
console.log(this);
return (
<h1>我是類式組件創(chuàng)建出來的組件</h1>
)
}
}
ReactDOM.render(<MyComponent />, document.querySelector('#app'))
</script>
組件的三大屬性
state、props御滩、refs
state
往組件身上添加狀態(tài)鸥拧,需要在組件類里的 constructor 里寫上 this.state={}
注意在添加state之前必須調(diào)用 super()
class RC extends React.Component {
render() {
return (<h1>今天天氣很{this.state.hot}</h1>)
}
constructor() {
super()
this.state = {
hot: '熱'
}
}
}
ReactDOM.render(<RC />, document.getElementById('app'))
解決虛擬DOM實(shí)例上的this指向問題
在 constructor 中利用 bind(this) 解決
class RC extends React.Component {
render() {
const { num } = this.state
return (
<h1 onClick={this.countPlus}>天下第{num}武道會</h1>
)
}
constructor() {
super();
this.state = {
num: 1
};
this.countPlus = this.countPlus.bind(this)
}
countPlus() {
this.state.num++
console.log(this.state.num);
}
}
ReactDOM.render(<RC />, document.getElementById('app'))
setState
上面的例子可以看到頁面并沒有更新党远,那是因?yàn)橹苯有薷膕tate的話不是響應(yīng)式的,需要調(diào)用 setState 修改狀態(tài)富弦,才能刷新 render
class RC extends React.Component {
render() {
const { num } = this.state
return (
<h1 onClick={this.countPlus}>天下第{num}武道會</h1>
)
}
constructor() {
super();
this.state = {
num: 1
};
this.countPlus = this.countPlus.bind(this)
}
countPlus() {
let newNum = this.state.num + 1;
this.setState({
num: newNum
})
}
}
ReactDOM.render(<RC />, document.getElementById('app'))
精簡組件寫法
我們可以看到上面的constructor寫法很麻煩沟娱,大多數(shù)時(shí)候并不需要用到類本身,而是用類的實(shí)例對象腕柜。
在class中直接寫賦值語句可以為實(shí)例對象直接增加屬性济似,用于事件觸發(fā)的回調(diào)函數(shù)也可以寫成箭頭函數(shù)賦值給變量的形式(因?yàn)橐尯瘮?shù)里的this指向組件實(shí)例)
class RC extends React.Component {
render() {
const { name, age } = this.state
return (
<div>
<h1>我叫{name},我今年{age}歲</h1>
<button onClick={this.handleClick}>點(diǎn)我增加歲數(shù)</button>
</div>
)
}
state = {
name: 'ming',
age: 18
}
handleClick = () => {
let newAge = this.state.age + 1
this.setState({
age: newAge
})
}
}
ReactDOM.render(<RC />, document.querySelector('#app'))
props
通過渲染組件時(shí)往組件身上寫屬性和屬性值可以往組件內(nèi)部傳遞數(shù)據(jù)盏缤,通過 this.props.xxx 可以讀取傳遞過來的屬性值
<div id="app"></div>
<div id="app2"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
return (
<div>
<ul>
<li>我是:{this.props.name}</li>
<li>年齡:{this.props.age}</li>
<li>性別:{this.props.sex}</li>
</ul>
</div>
)
}
}
ReactDOM.render(<Person name="he" age="18" sex="male" />, document.getElementById('app'))
ReactDOM.render(<Person name="ming" age="20" sex="female" />, document.getElementById('app2'))
props 批量傳遞
可以傳遞一個對象砰蠢,然后用花括號里面展開對象的形式進(jìn)行 props 傳值
class Person extends React.Component {
render() {
return (
<div>
<ul>
<li>我是:{this.props.name}</li>
<li>年齡:{this.props.age}</li>
<li>性別:{this.props.sex}</li>
</ul>
</div>
)
}
}
const qiming = {
name: 'he',
age: 18,
sex: 'male'
}
ReactDOM.render(<Person {...qiming} />, document.getElementById('app'))
在這個例子中,babel+react里僅限組件傳值可以這樣展開對象唉铜,其他情況展開是個空白的東西
props 限制
對props傳遞的值進(jìn)行類型限制或必要性限制娩脾,需要借助另一個 react 庫 prop-type。
通過往組件身上添加 propTypes 屬性進(jìn)行類型限制和必要性限制打毛,添加 defaultProps 進(jìn)行默認(rèn)屬性值設(shè)置柿赊。
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
return (
<div>
我是{name}
</div>
)
}
}
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func.isRequired
}
Person.defaultProps = {
name: 'he'
}
const p1 = {
name: 'qiming',
age: 18,
sex: 'male'
}
const speak = () => {
console.log('haha');
}
ReactDOM.render(<Person {...p1} speak={speak} />, document.querySelector('#app'))
如果要在限制對象內(nèi)使用進(jìn)一步限制,使用 PropTypes.shape 方法
PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
props 限制的簡寫形式
上面的代碼幻枉,可以使用 static 關(guān)鍵字優(yōu)化一下
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
return (
<div>
我是{name}
</div>
)
}
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func.isRequired
}
static defaultProps = {
name: 'he'
}
}
函數(shù)式組件中的 props
函數(shù)式組件中碰声,在形參中的第一個參數(shù),就是props熬甫,當(dāng)然也可以使用 props限制
function RC(props) {
console.log(props);
return <div></div>
}
RC.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func.isRequired
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
props 注意點(diǎn)
- props 是只讀的胰挑,單向數(shù)據(jù)流保持?jǐn)?shù)據(jù)的可追溯性
construtor
官方文檔:通常,在 React 中椿肩,構(gòu)造函數(shù)僅用于以下兩種情況:
- 通過給 this.state 賦值對象來初始化內(nèi)部 state瞻颂。
- 為事件處理函數(shù)綁定實(shí)例。
也就是說郑象,組件中的 construtor完全可以省略贡这,但一旦寫了,然后super里沒有傳遞props厂榛,就會出現(xiàn) constructor 里的 this.props 丟失的情況盖矫,但是可以直接訪問 props
class RC extends React.Component {
constructor(props) {
super()
console.log(this.props); //這里輸出 undefined
console.log(props); //這里輸出props
}
render() {
return <div></div>
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
ref 與 refs
ref 是聲明在DOM標(biāo)簽上的,refs是用于讀取 ref 列表
class RC extends React.Component {
render() {
return (
<div>
<input type="text" ref="input1" />
<button onClick={this.btnClick}>點(diǎn)我看東西</button>
<input onBlur={this.handlerBlur} type="text" ref="input2" />
</div>
)
}
btnClick = () => {
this.refs.input2.value = this.refs.input1.value
}
handlerBlur = () => {
alert(this.refs.input2.value)
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
ref的三種形式
- ref 的值為字符串形式:官方不推薦使用击奶,存在一些性能上效率上的問題辈双。
- ref 的值為一個回調(diào)函數(shù)形式,取的時(shí)候直接從this身上取而不是 refs 了
render() {
return (
<div>
<input onBlur={ handleBlur } type="text" ref={ currentNode => this.input1=currentNode } />
</div>
)
}
handleBlur = ()=>{
console.log(111)
}
像這種直接在內(nèi)聯(lián)里寫回調(diào)函數(shù)的形式柜砾,在更新過程中會被執(zhí)行兩次湃望。通過在類里面直接定義函數(shù)然后在節(jié)點(diǎn)上使用可以解決這種問題。不過大多時(shí)候無關(guān)緊要
render() {
return (
<div>
<input onBlur={ handleBlur } type="text" ref={ this.currentNode } />
</div>
)
}
currentNode = (c) => this.input1=c
- 使用 React.createRef() 來創(chuàng)建 ref 標(biāo)識,讀取的時(shí)候從 this.容器名.current 才能讀取到該容器的 DOM
render() {
return (
<div>
<input onBlur={ handleBlur } type="text" ref={ this.myRef } />
</div>
)
}
myRef = React.createRef()
綁定事件
給虛擬DOM綁定事件证芭,官方推薦用 onXxx 寫法瞳浦,注意 Xxx 開頭字母需要大寫,例如 onclick 要寫成 onClick
class RC extends React.Component {
render() {
return (
<h1 onClick={handleClick}>點(diǎn)我看控制臺</h1>
)
}
}
function handleClick() {
console.log('我點(diǎn)擊了');
}
ReactDOM.render(<RC />, document.getElementById('app'))
使用 onXxx 寫法原因有二:
- React 使用的是自定義事件檩帐,而不是使用的原生 DOM 事件
- React 中的事件是通過事件委托方式處理的 (委托給組件最外層的元素)
事件可以通過 event.target 得到發(fā)生事件的 DOM 元素對象
表單數(shù)據(jù)收集
非受控組件
現(xiàn)用現(xiàn)取
受控組件
監(jiān)聽 onChange 事件然后通過 this.setState 設(shè)置狀態(tài)
class RC extends React.Component {
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
用戶名:<input onChange={this.handlerUsername} type="text" />
密碼:<input onChange={this.handlerPassword} type="password" />
<button>提交</button>
</form>
</div>
)
}
state = {
username: '',
password: ''
}
handlerUsername = (e) => {
this.setState({
username: e.target.value
})
}
handlerPassword = (e) => {
this.setState({
password: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
const { username, password } = this.state
console.log(`用戶名是${username}术幔,密碼是${password}`);
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
少用函數(shù)優(yōu)化以上代碼
可以使用函數(shù)柯里化另萤、內(nèi)聯(lián)函數(shù)湃密、靈活運(yùn)用 event 對象等方法優(yōu)化以上代碼
class RC extends React.Component {
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
用戶名:<input onChange={this.handleChange} type="text" name="username" />
密碼:<input onChange={this.handleChange} type="password" name="password" />
<button>提交</button>
</form>
</div>
)
}
state = {
username: '',
password: ''
}
handleChange = (e) => {
const value = target.type === "checkbox"? target.checked:target.value
this.setState({
[e.target.name]: value
})
}
handleSubmit = (e) => {
e.preventDefault()
const { username, password } = this.state
console.log(`用戶名是${username},密碼是${password}`);
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
雙向綁定
注意雙向綁定除了要綁定 onChange 事件以外還要把 value 值綁定為state值四敞,否則狀態(tài)改變時(shí)輸入框內(nèi)容不會改變
<input name="username" onChange={this.handleInput} value={this.state.username}></input>
當(dāng)輸入組件的 name 和 state 里面的狀態(tài)值名一致時(shí)就能做到上面的優(yōu)化
React 生命周期
生命周期(舊)
shouldComponentUpdate鉤子默認(rèn)返回真泛源,如果返回值為假,后面的生命周期都不會走
forceUpdate 強(qiáng)制觸發(fā)更新
componentWillReceiveProps 第一次接收props不會觸發(fā)忿危,后面才會觸發(fā)
[圖片上傳失敗...(image-69038a-1658627198688)]
生命周期>16.4
[圖片上傳失敗...(image-3124ce-1658627198688)]
可以看到新的生命周期多了 static getDerivedStateFromProps 和 getSnapshotBeforeUpdate达箍。
實(shí)際上工作中常用的就
static getDerivedStateFromProps
直譯 從props衍生的狀態(tài),使用這個鉤子會影響狀態(tài)更新铺厨,可以接到 props 參數(shù)和 state缎玫。返回值就成為組件的狀態(tài)
通常這么用,官方文檔說用于 state的值任何時(shí)候都取決于 props
static getDerivedStateFromProps(props,state){
return props
}
getSnapshotBeforeUpdate
直譯:在更新之前獲取快照解滓,必須返回一個 null 或者快照值
該鉤子會在最近一次渲染輸出(提交到DOM節(jié)點(diǎn))之前調(diào)用赃磨。該鉤子的返回值會傳給 componentDidUpdate 的第三個參數(shù)
getSnapshotBeforeUpdate(){
return 給componentDidUpdate的值
}
componentDidUpdate
該鉤子可以接到兩個參數(shù),一個是更新之前的props洼裤,另一個是更新前的state邻辉,
如果getSnapshotBeforeUpdate return了一個值,就會在第三個參數(shù)中接到
componentDidUpdate(preProps,preState,snapshot){
}
如果要在這個鉤子中使用 setState腮鞍,必須放在一個 if 中
樣式模塊化
在樣式文件中間加上 .module. 例如 index.module.css
在引入的時(shí)候可以起個名字接住這個樣式模塊值骇,使用的時(shí)候用對象點(diǎn)的形式
import hello from './index.module.css'
export default class hello extends Component{
render(){
return <h2 className={hello.title}></h2>
}
}
組件通信
父子:props
子父:父給子傳遞函數(shù),子調(diào)用函數(shù)傳遞參數(shù)
兄弟:第三方庫 pubsub-js
import PubSub from 'pubsub-js'
PubSub.subscribe("消息名",回調(diào)函數(shù)(訂閱名,消息內(nèi)容){})
PubSub.subscribe('userInfo',(_,data)=>{})
PubSub.publish('消息名',消息)
腳手架代理解決跨域
方法一(簡單方式):在 package.json 配置
"proxy":"地址"
方法二:在src目錄下創(chuàng)建 setupProxy.js
const proxy = require('http-proxy-middleware'
module.exports = function (app){
app.use(
//新版是proxy.createProxyMiddleware
proxy('/api1',{
target: 'http://localhost:5000',
changeOrigin: true,
pathRewrite: {'^/api1': ''}
})
)
}
使用 pathRewrite 的原因是防止與項(xiàng)目目錄下的資源同名移国,
react 路由
注意2022年路由用的是6版本拧廊,react16舊一點(diǎn)的用5
npm i react-router-dom@5
路由跳轉(zhuǎn)組件,使用前要從庫中引入凶杖,Link組件包在Router組件里面颅围。用to屬性來指定跳轉(zhuǎn)地址
import {Link,Router} from 'react-router-dom'
<Router>
<Link to="/about"></Link>
</Router>
路由器分為 BrowserRouter 和 HashRouter,直接使用Router會報(bào)錯
import {Link,BrowserRouter} from 'react-router-dom'
<BrowserRouter>
<Link to="/about"></Link>
</BrowserRouter>
使用 Route 注冊路由裹芝,兩個關(guān)鍵屬性 path 指定展示的組件和 component 指定要展示的組件部逮,然后也要用 Router(BrowserRouter 和 HashRouter) 組件包著
import About from './component/About'
import Home from './component/Home'
<BrowserRouter>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</BrowserRouter>
而且注意 Link 和 Route 要包在一個 Router 里
<BrowserRouter>
<Link to="/home"></Link>
<Link to="/about"></Link>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</BrowserRouter>
尚硅谷的教程里把 </BrowserRouter> 扔在了 index.js 里寫在了 App 組件外面
組件分類
組件分為路由組件與一般組件,路由用的組件一般不寫在 component 中嫂易,寫在 pages 或 views 里
路由傳參
路由組件默認(rèn)收到4個props參數(shù):history兄朋、location、match、staticcontext
這些參數(shù)有幾個方法和屬性需要關(guān)注
history:go,goBack,goForward,push,replace
location:pathname,search,state
match:params,path,url
一般組件插槽
<MyComponent>自定義插槽內(nèi)容</MyComponent>
上面的自定義插槽內(nèi)容傳到哪里了呢颅和,在組件內(nèi)部輸出this.props傅事,可以看到掛在了 children 上
console.log(this.props.children)
就會輸出插槽內(nèi)容
NavLink
NavLink 組件會默認(rèn)給活躍的路由組件加上 active 類名,
也可以使用 activeClassName 指定活躍的類名
<NavLInk activeClassName="zidingyi"/>
一個地址匹配多個路由
<Route path="/home" component={Home}>
<Route path="/home" component={About}>
像上面這種情況峡扩,router版本5會都顯示
Switch組件
Switch組件可以防止上面的情況出現(xiàn)蹭越,匹配到了一個路由就不會往下匹配
import {Switch,Route} from 'react-router-dom'
<Switch>
<Route path="/home" component={Home}>
<Route path="/home" component={About}>
</Switch>
多級路由資源丟失問題
低版本路由存在多級路由資源丟失的問題,解決方法有3
- 使用絕對路徑
- 使用 %PUBLIC_URL% 代替絕對路徑
- 使用 HashRouter
但是 HashRouter 會造成 刷新后 state 參數(shù)的丟失教届,后面會講
路由模糊匹配與精準(zhǔn)匹配
Link 組件的 to 路徑會從右往左進(jìn)行模糊匹配响鹃,如果匹配上了會展示模糊匹配的組件,如果從左開始沒命中則不展示案训。
如果要使用精準(zhǔn)匹配在 Route 組件中使用 exact 屬性開啟精準(zhǔn)匹配
<Route exact></Route>
路由重定向 Redirect 兜底組件
<Route path="/home" component={Home}>
<Route path="/about" component={About}>
<Redirect to="/about">
這個組件指如果所有的Route組件都匹配不上則走重定向組件
多級路由/嵌套路由
一級路由Link完成注冊以及Route準(zhǔn)備好展示之后买置,需要在子頁面上繼續(xù)注冊和準(zhǔn)備展示。
路由傳參
路由傳遞 params 參數(shù)
需要在 Link 在 to 用模板字符串傳遞參數(shù)强霎,然后在 Route 的 path 用冒號占位
<Link to={`/home/msg/${x1}`}></Link>
<Route path="home/msg/:id"/>
然后在路由組件身上的 this.props.match 上就會找到 params
傳遞 search 參數(shù)
傳遞 search 參數(shù)則無需在 Route 組件上聲明接受忿项,直接去組件身上的
this.props.location.search
找
<Link to={"home/msg?id=1"></Link>
這種 urlencoded 參數(shù)可以借助庫來拆解為對象或字符串,例如 node 自帶的 qs 庫
傳遞 state 參數(shù)
傳遞 state 參數(shù)城舞,Link 組件里的 to就要寫成對象形式
<Link to={{
pathname:'/home/message',
state:{
id:'1',
name:'ming'
}
}}></LInk>
然后去組件的 this.props.location.state
上取轩触,這種參數(shù)在 BrowserRouter 方式的路由上刷新不會丟失,清空瀏覽器歷史記錄才會丟失
push 與 replace
路由開啟 replace 模式家夺,只需要在 Link 組件中聲明 replace 屬性即可脱柱。
<Link replace></Link>
或
<Link replace={true}></Link>
編程式路由
在 Link 組件里面的元素,身上的 props 都具有路由組件的方法秦踪,調(diào)用這些方法可以實(shí)現(xiàn)編程式路由
this.props.history.push()
和 this.props.history.replace()
傳遞 params 參數(shù)和 search 都是一樣的寫法褐捻,傳遞 state 參數(shù)則是直接在地址后面接一個對象
this.props.history.push('/home/msg',{id,name})
然后 this.props.history.go() goBack() goFoward() 就是前進(jìn)后退了
一般組件使用編程式路由導(dǎo)航 withRouter
使用 withRouter 包住一般組件可以讓一般組件也有 history,location 這些東西
import {withRouter} from 'react-router-dom'
class MyComponent extends Component{}
export default withRouter(MyComponent)
狀態(tài)管理 redux
redux 三大核心概念:action creators椅邓,store柠逞,reducers
action
action就是個對象,包含兩個屬性:
- type:值為字符串景馁,必要屬性
- data:任意類型板壮,可選屬性
{type:'ACT_ADD',data:{name:'ming'}}
store.dispatch() 分發(fā)action
reducer
- 用于初始化狀態(tài)和加工狀態(tài)
- 加工時(shí),根據(jù)舊的 state 和 action合住,產(chǎn)生新的 state 純函數(shù)
- reducer 函數(shù)會接到兩個參數(shù)绰精,分別為之前的狀態(tài)和動作對象
store
組件中讀取 store 的數(shù)據(jù)使用 getState() 方法
創(chuàng)建 redux/store.js
import { createStore } from "redux";
// import { legacy_createStore as creatStore} from "redux";
import countReducer from './count_reducer'
export default createStore(countReducer)
創(chuàng)建 reducer
const initState = 0
export default function countReducer(preState = initState, action) {
console.log(preState);
const { type, data } = action
switch (type) {
case 'increment':
return preState + data
case 'decrement':
return preState - data
default:
return preState
}
}
組件中使用store
import store from '@/redux/store'
//讀取用 getState()
//分發(fā)action用dispatch
注意 redux 只管理狀態(tài)不更新頁面,使用 store.subscribe(()=>{}) 即可透葛,只要redux狀態(tài)變化笨使,就會調(diào)用回調(diào)
componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}
或者直接在 index.js 使用
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
創(chuàng)建專門文件管理 action
redux/xxx_action.js
export const createIncrementAction = (data)=>{
return ({
type:'increment',
data
})
}
創(chuàng)建專門文件管理redux變量常量
redux/constant.js
export INCREMENT = 'increment'
異步 action
import store from "./store"
export const asyncPlus = (data) => {
return () => {
setTimeout(() => {
store.dispatch({ type: 'increment', data })
}, 500)
}
}
借助 redux-thunk 中間件實(shí)現(xiàn)
在store.js里
import {createStore,applyMiddleware} from 'redux'
import thunk 'redux-thunk'
import countReducer from './count_reducer'
export default createStore(countReducer,applyMiddleware(thunk))
當(dāng)然異步 action 不是必須的,完全可以自己等待異步任務(wù)的結(jié)果再去派發(fā)同步 action
react-redux
npm i react-redux
react-redux 多了一層 UI組件和容器組件的概念僚害,UI組件不能直接與 redux 進(jìn)行交互硫椰,只能與容器組件通過 props 進(jìn)行交互,而容器組件才能進(jìn)行 store.getState() 等操作
創(chuàng)建容器組件
//引入 UI 組件
import CountUI from '@/compoents/Count'
//引入 store
import store from '@/redux/store'
//引入connect用于鏈接UI組件與redux
import {connect} from 'react-redux'
//a函數(shù)作為返回的對象中key就作為傳遞給UI組件props的key,value就作為傳遞給UI組件props的value——狀態(tài)靶草,會接到一個形參是redux傳過來的state
function a(state){
return {count:state}
}
//b函數(shù)作為返回的對象中key就作為傳遞給UI組件props的key蹄胰,value就作為傳遞給UI組件props的value——操作狀態(tài)的方法
//b函數(shù)能接到redux傳過來的dispatch。返回的函數(shù)接到形參是UI組件傳過來的數(shù)據(jù)data
function b(dispatch){
return {jia:(number)={
dispatch({type:'increment',data:number})
}}
}
//使用 connect()() 創(chuàng)建并暴露一個 Count 的容器組件奕翔,第一個括號用于傳遞狀態(tài)和操作狀態(tài)的方法裕寨,官方名稱叫mapStateToProps 和 mapDispatchToProps
export default connect(a,b)(CountUI)
此時(shí)注意寫好容器組件后,掛在頁面上的就是容器組件而不是UI組件派继,并且要通過props傳遞store
import Count from './containers/Count'
import store from '@/redux/store'
export default class App extends Component{
render(){
return (
<div>
<Count store={store}/>
</div>
)
}
}
mapDispatchToProps簡寫
原寫法是像上面那樣一個接到dispatch的函數(shù)宾袜,簡寫攜程一個對象,react-redux自動幫你dispatch
export default connect(
state=>({count:state}),
{
jia:createIncrementAction,
jian:createDecrementAction
}
)
Provider組件
我們在使用容器組件的時(shí)候都需要手動傳一個 store 進(jìn)去互艾,如果不傳則會報(bào)錯试和,這時(shí)可以借助 Provider 組件讯泣,同時(shí)注意 react-redux 不需要自己手動更新 render 了
在 index.js 中
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
combineReducers 管理多個模塊狀態(tài)
最終react-redux容器組件
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createIncrementAction } from '@/redux/count_action'
class Count extends Component {
render() {
return (
<div>
<h1>當(dāng)前求和為:{this.props.he}</h1>
<button onClick={this.add}>點(diǎn)我加</button>
</div>
)
}
add = () => {
this.props.jiafa(1)
}
}
export default connect(
state => ({
he: state,
}),
{
jiafa: createIncrementAction
}
)(Count)
setState 拓展
setState 寫法一
this.setState()會接受兩個參數(shù)纫普,第一個參數(shù)是狀態(tài)改變對象,第二個參數(shù)是狀態(tài)更新視圖也女性g之后會調(diào)用的回調(diào)
this.setState({num:1},()=>{
console.log(this.state.num)
})
console.log(this.state.num) //這里是讀不到的
因?yàn)閞eact更新視圖是在異步隊(duì)列進(jìn)行的好渠,如果在 setState 后面直接讀取一些信息會讀不到
setState 寫法二
寫法二的第一個參數(shù)寫成一個函數(shù)昨稼,該函數(shù)會接到兩個形參,state和props拳锚。
this.setState((state,props)=>{
return {num:state.num+1}
},()=>{
})
如果新狀態(tài)依賴于原狀態(tài)假栓,推薦使用寫法二函數(shù)式。
第二個參數(shù)是一個回調(diào)函數(shù)霍掺,該回調(diào)會在 state 更新完后執(zhí)行匾荆。
setState 其他說明
一次函數(shù)中多次調(diào)用 setState,只會出發(fā)一次重新渲染
路由懶加載
從 react 上引入 lazy 使用杆烁,需要搭配 Suspense 指定懶加載替換組件牙丽,替換組件不能懶加載
import React,{Compoent,lazy} from 'react'
Hook/Hooks
useState
react 16.8 之前,函數(shù)式組件沒有 state兔魂,refs 等烤芦,函數(shù)式組件連自己的this都沒有。有了 hooks 后析校,函數(shù)式組件就可以做很多東西了构罗。但同時(shí)注意,函數(shù)式組件的render要放最后智玻,不然會出現(xiàn)奇怪的bug遂唧。
import React from 'react'
export default function Count() {
// const [狀態(tài)名,修改狀態(tài)的方法] = React.useState(初始值)
const [count, setCount] = React.useState(0)
function add() {
// setCount(count + 1)
setCount((value) => {
return value + 1
})
}
return (
<div>index{count}
<button onClick={add}>+</button>
</div>
)
}
修改狀態(tài)的方法可以接受兩種參數(shù),第一種就是直接寫值吊奢,第二種寫成回調(diào)函數(shù)形式盖彭,形參可以接收到原來的值
useEffect
React.useEffect(()=>{},[])
useEffect 可以接收到兩個參數(shù),第一個參數(shù)是回調(diào)函數(shù),該回調(diào)會在第二個數(shù)組里監(jiān)測的數(shù)據(jù)改變后執(zhí)行谬泌,第二個參數(shù)是數(shù)組滔韵,里面的數(shù)據(jù)會影響第一個參數(shù)的回調(diào)函數(shù)執(zhí)行。
如果不寫第二個參數(shù)掌实,就相當(dāng)于監(jiān)視所有數(shù)據(jù)的變化陪蜻。
使用 effect hook 模擬生命周期
當(dāng)?shù)诙€參數(shù)的數(shù)組里不寫東西,就相當(dāng)于寫了一個 componentDidMount 生命周期贱鼻。
當(dāng)?shù)谝粋€參數(shù)返回一個函數(shù)宴卖,這個返回的函數(shù)就相當(dāng)于一個 componentWill
useRef
類似 createRef
import React from 'react'
export default function Count() {
// const [狀態(tài)名,修改狀態(tài)的方法] = React.useState(初始值)
const myRef = React.useRef()
const logVal = () => {
console.log(myRef.current.value);
}
return (
<div>
<input type="text" ref={myRef} />
<button onClick={logVal}></button>
</div>
)
}
Fragment 與 空標(biāo)簽
組件必須有一個根標(biāo)簽,此時(shí)可以借助 Fragment 組件或根標(biāo)簽來解決
但是注意 Fragment 除了key以外不能寫其他屬性邻悬,空標(biāo)簽則不能加其他屬性
context
創(chuàng)建 Context 容器對象:const XxxContext = React.createContext()
渲染子組件時(shí)症昏,外面包裹 xxxContext.Provider,通過 value 屬性給后代組件傳遞數(shù)據(jù):
<XxxContext.Provider value={數(shù)據(jù)}>
子組件
</XxxContext.Provider>
后代組件讀取數(shù)據(jù):
//第一種方式:類似逐漸
static contextTyoe = XxxContext
this.context
//第二種方式父丰,函數(shù)組件與類組件都可以
<xxxContext.Consumer>
{
value=>(
要顯示內(nèi)容de
)
}
<xxxContext.Consumer>
通常開發(fā)不會使用context肝谭,只會用來開發(fā)插件。
注意是 React.createContext() 調(diào)用后取到的組件蛾扇,不然會被報(bào)錯卡住
import React, { useState } from 'react'
const { Provider, Consumer } = React.createContext()
export default function Hello(props) {
const [count] = useState(2)
return (
<Provider value={count}>
<Acompo />
</Provider>
)
}
const Acompo = (props) => {
return (
<Bcompo />
)
}
const Bcompo = (props) => {
return (
<Consumer>
{data => (
<>{data}</>
)}
</Consumer>
)
}
PureComponent
Component的兩個問題
- 只要執(zhí)行 setState() 即使不改變狀態(tài)數(shù)據(jù)攘烛,組件也會重新 render()
- 只要當(dāng)前組件重新 render() ,就會自動重新 render 子組件(效率低)
想要效率高镀首,那就是只有當(dāng)組件的 state 或 props 時(shí)才重新 render
原因
組件中的 shouldComponentUpdate() 總是返回true坟漱,該鉤子可以接收到兩個形參,nextProps 和 nextState
shouldComponentUpdate(nextProps,nextState){
}
解決方法之一就是重寫這個鉤子更哄,比較新舊 state 或 props 值芋齿,如果變化了才返回 true,沒有變化則 false
最佳解決方法 PureComponent
PureComponent 原理也是重寫了shouldComponentUpdate成翩,不過注意的是只是對 state 和 props 進(jìn)行淺比較觅捆。
import {PureComponent} from 'react'
export default class MyComponent extends PureComponent{}
renderProps 插槽
如果一個非自閉合組件在標(biāo)簽體內(nèi)寫一般內(nèi)容,可以在 this.children 上讀取到捕传,但如果放進(jìn)去的是 DOM惠拭,同時(shí)還要不知道放什么子組件但要給子組件傳參時(shí),那么需要借助 render才行
import React, { Component } from 'react'
export default class index extends Component {
render() {
return (
<div>
<A render={(參數(shù)) => {
return <B propname={參數(shù)}></B>
}} />
</div>
)
}
}
class A extends Component {
render() {
return (
<>
我是A啊 <br />
{this.props.render(參數(shù))}
<br /> 我是A最后
</>
)
}
}
class B extends Component {
render() {
return (
<>
我是b啊
</>
)
}
}
render props 是一種模式庸论,具體怎么用看個人喜歡职辅,也有直接用 props.children 的。本質(zhì)還是通過函數(shù)實(shí)現(xiàn)組件通信聂示,插槽通信域携。此時(shí)最好對 children 進(jìn)行一些校驗(yàn)
復(fù)用組件.propTypes = {
children: PropTypes.func.isRequired
}
error boundary 錯誤邊界
錯誤邊界只能用于生產(chǎn)環(huán)境中,可以限制錯誤擴(kuò)散鱼喉,不過getDerivedStateFromError不能捕獲自己的錯誤秀鞭,只能捕獲子組件并且只能捕獲到生命周期里的錯誤
import React, { Component } from 'react'
export default class Parent extends Component {
static getDerivedStateFromError(error) {
console.log(error);
return { hasError: error }
}
state = {
hasError: ''
}
render() {
return (
<div>Parent</div>
{this.state.hasError?<h2>當(dāng)前網(wǎng)絡(luò)不穩(wěn)定趋观,請稍后再試</h2>:<Child/>}
)
}
}
配合 componentDIdCatch(){} 鉤子統(tǒng)計(jì)錯誤
高階組件
高階組件就是一個function,參數(shù)是一個沒有自己狀態(tài)與方法的jsx組件只使用 props 的 jsx 組件锋边。
//需要配合高階組件使用的低階組件
let lowerMouse = (props) => {
return (
<img src={img} style={{
width: 10,
position: 'absolute',
top: props.y,
left: props.x
}}></img>
)
}
let LowerMouse = withMouse(lowerMouse)
高階組件一般以 withXXX 命名皱坛,我們來看一下他怎么寫的:
接受一個組件的函數(shù),在 render 里使用豆巨,并且在使用時(shí)候傳遞函數(shù)里的組件的state作為 props剩辟。
這個函數(shù)最終把函數(shù)內(nèi)的組件返回出去,那么接收一個組件作為參數(shù)后就可以為低級組件包裝了往扔。
export default function withMouse(WrappedComponent) {
class index extends Component {
state = {
x: undefined,
y: undefined
}
handleXY = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount() {
window.addEventListener('mousemove', this.handleXY)
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleXY)
}
render() {
return (
<WrappedComponent {...this.state} {...this.props}></WrappedComponent >
)
}
}
//優(yōu)化 設(shè)置displayName
index.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
return index
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
優(yōu)化
- 設(shè)置displayName優(yōu)化開發(fā)者工具
- 添加 props
組件性能優(yōu)化
組件性能優(yōu)化
減輕state:只存儲跟組件渲染相關(guān)的數(shù)據(jù)贩猎,不做渲染的數(shù)據(jù)放在this上即可,例如定時(shí)器的id
React18
ReactDOM.createRoot
將原生DOM作為參數(shù)傳入該方法萍膛,調(diào)用后返回值就可以掛載虛擬DOM吭服,參數(shù)內(nèi)的所有內(nèi)容都會被清空
const div = React.createRoot(document.getElementById('root'))
root.render(<App/>)
react-dom/client
18之后引入 ReactDOM 不是從 react-dom 上引入而是
import ReactDOM from 'react-dom/client'
useState()
需要一個值作為參數(shù)作為該state的初始值,該函數(shù)會返回一個數(shù)組蝗罗,
數(shù)組第一個元素是初始值艇棕,直接修改不會出發(fā)組件的重新渲染。
數(shù)組的第二個元素是一個函數(shù)绿饵,通常命名為setXXX欠肾,用來修改state瓶颠,調(diào)用其修改state后會觸發(fā)組件更新拟赊。
該函數(shù)的參數(shù)就會作為新的值賦值給該state
import {useState} from 'react'
let result = useState(1)
let count = result[0]
let setCount = result[1]
setCount()
當(dāng) setState 調(diào)用的時(shí)候使用舊的state值一定要注意,很可能會出現(xiàn)計(jì)算錯誤的情況粹淋,因?yàn)楦?state 是異步進(jìn)行的吸祟。這種時(shí)候可以使用回調(diào)函數(shù)來進(jìn)行setState,回調(diào)函數(shù)會接到原來的數(shù)值作為參數(shù)桃移,返回值將作為新值
setCount((preValue)=>{
return preValue+1
})
useRef()
該鉤子只能在函數(shù)組件中直接使用屋匕,不要在嵌套的函數(shù)中使用
const myRef = useRef()
return(
<input ref={myRef}/>
)
注意讀出組件是從 myRef.current 上讀不是直接讀 myRef。
其實(shí)可以直接寫一個普通對象代替 useRef()const myRef = {current:undefined}
不過普通對象會在組件每次重新渲染時(shí)創(chuàng)建一個新對象借杰,但 useRef 不會