ReactDOM.render(<MyComponent/>, document.getElementById("root"))發(fā)生了什么
react解析MyComponent標(biāo)簽找到了MyComponent組件,發(fā)現(xiàn)組件是類定義的,隨后new出該類的實(shí)例,并通過該實(shí)例調(diào)用到原型上的render方法
將render方法返回的虛擬dom轉(zhuǎn)為真實(shí)dom,隨后呈現(xiàn)在頁面中,呈現(xiàn)后還會調(diào)用一個componentDidMount
ref
字符串的形式(不推薦使用,存在性能問題)
-
回調(diào)的方式
<input ref={(currentNode) => {this.input = currentNode}}/>, 該回調(diào)一上來就會調(diào)用一次
PS: 向上面這種回調(diào)的方式(內(nèi)聯(lián)的方式)在更新的時候會調(diào)用兩次,注意是更新的時候,第一次currentNode為null(因?yàn)槊看蝦ender都會創(chuàng)建一個新的內(nèi)聯(lián)函數(shù),當(dāng)更新的時候第一次調(diào)用是為了清空就得函數(shù),所以返回一個null),第二次才會把節(jié)點(diǎn)傳入. 如果非得解決這個問題,react也提供了方案,通過類綁定的方式:<input ref={this.saveInput}/>,
在class里面定義saveInput
saveInput = (c) => { // c就是傳入的node節(jié)點(diǎn)
this.input1 = c;
}
createRef
class Demo extends React.Compoent{
myRef = React.createRef(); // createRef返回一個容器,該容器用于存放被ref所標(biāo)識的節(jié)點(diǎn),該容器專人專用只能綁定一個,因此后放進(jìn)去的會覆蓋之前的,如果想使用多個就需要createRef多次
render() {
return (
<input ref={this.myRef}/> {/*這里相當(dāng)于將input存放到了myRef中了,input節(jié)點(diǎn)可以通過this.myRef.current訪問*/}
)
}
}
react中的事件
-
1 通過onXXX屬性指定事件處理函數(shù)
react中使用的是自定義事件(合成事件,而不是使用原生的dom事件)--- 是為了更好的兼容性react中的事件是通過事件委托的方式處理的(委托給組件最外層元素)--- 事件委托的原理是事件冒泡,使用事件委托高效 2 通過event.target得到發(fā)生事件的dom元素對象
收集表單數(shù)據(jù)
-
非受控組件
現(xiàn)用現(xiàn)取,通過ref可以完成
-
受控組件
輸入類的元素在隨著輸入將值維護(hù)到狀態(tài)中
高階函數(shù)和柯里化
高階函數(shù):
若函數(shù)A接受的參數(shù)是一個函數(shù),那么A就稱之為高階函數(shù)
若函數(shù)A調(diào)用的返回值依然是一個函數(shù),那么A就稱之為高階函數(shù)
常見的高階函數(shù):如Promise, setTimeout, 數(shù)組的常用方式
函數(shù)柯里化
部分求值,函數(shù)的調(diào)用仍舊返回函數(shù),實(shí)現(xiàn)多次接受參數(shù)最后統(tǒng)一處理的函數(shù)編碼
生命周期
組件從創(chuàng)建到死亡會經(jīng)歷一些特定的階段
React組件中包含一系列鉤子函數(shù),會在特定的時刻調(diào)用
在定義組件時,會在特定的聲明周期回調(diào)函數(shù)中做特定的工作
- 舊版的生命周期鉤子
組件掛載的執(zhí)行順序
constructor ---> componentWillMount ---> render ---> componentDidMount ---> componentWillUnmount
組件更新執(zhí)行順序(可以分為3條線)
父組件渲染子組件,當(dāng)傳入的數(shù)據(jù)更新時的執(zhí)行路線
componentWillReceiveProps ---> shouldComponentUpdate ---> componenWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount
當(dāng)組件setState更新的時候
setState() ---> shouldComponentUpdate ---> componentWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount
當(dāng)強(qiáng)制更新組件的時候(不更改狀態(tài)數(shù)據(jù)就想讓組件更新就可以走這條線)
forceUpdate() ---> componentWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount
ps: shouldComponentUpdate是一個閥門鉤子,如果不寫這個鉤子默認(rèn)返回true,接下來的流程正常執(zhí)行,如果寫了并且返回false則接下來的流程不會執(zhí)行,只能返回true或者false,
componentWillReciveProps 這個鉤子第一次不調(diào)用,只有接受的props變化了才會調(diào)用,也就是父組件重新渲染導(dǎo)致子組件渲染才執(zhí)行該鉤子,接受新的props。
總結(jié):
初始化階段
constructor ---> componentWillMount ---> render() ---> componentDidMount
更新階段: 由組件內(nèi)部this.setState() 或者父組件更新觸發(fā)
shouldComponentUpdate ---> componentWillUpdate ---> render ---> componentDidUpdate
卸載階段:由reactDOM.unmountComponentAtNode(node)觸發(fā)
componentWillUnmount()
常用的鉤子:
componentDidMount: 一般做初始化操作例如開啟定時器,發(fā)送網(wǎng)絡(luò)請求,訂閱消息。。。
componentWillUnmount: 做一些收尾工作如取消定時器,取消訂閱等
render: 必須用,需要掉1+n次
- 新的聲明周期 (react最新版本)
所有帶will的鉤子在新版版中都加了UNSAFE_前綴, 除了卸載的鉤子,為什么這么做,因?yàn)閞eact在設(shè)計異步渲染,這些組件可能會出現(xiàn)bug,以后可能會被廢棄
新版本廢棄了componentWillMount, componentWillUpdate, componentWillReciveProps,新增了getDerivedStateFromProps 和getSnapshotBeforeUpdate
需要注意的是 getDerivedStateFromProps不能在實(shí)例上調(diào)用,需要聲明成靜態(tài)的需要加static, 而且該方法必須要有返回值,要么你返回狀態(tài)對象,要么返回null,不能返回其他的。需要注意的是只要返回一個對象,那么狀態(tài)的更新就沒有意義了,因?yàn)橐坏┓祷貭顟B(tài),此狀態(tài)就不能被改了,該方法接收一個參數(shù)props, 他會接收props屬性并且會派生出一個取決于props的狀態(tài)??梢越邮盏诙€參數(shù)state,使用場景罕見,基本不用。
getSnapshotBeforeUpdate: 該鉤子處于render和componentDidUpdate之間,用于在更新之前獲取快照,因?yàn)楦潞笾暗臓顟B(tài)就不見了,在更新之前再看一眼之前的狀態(tài)。
componentDidUpdate() 該鉤子會接收三個參數(shù),第一個是更新前的props,第二個是更新前的state,第三個是一個快照值(即getSnapshotBeforeUpdate返回的值。)
掛載時:
constructor ---> getDerivedStateFromProps ---> render (react更新dom和refs) ---> componentDidMount
更新:
(new props | setState() | forceUpdate)時 ---> getDerivedStateFromProps(得到一個派生的狀態(tài)) ---> shouldComponentUpdate
dom的diff算法
class Person extends React.Component{
state = {
persons: [
{id: 2, name: '小張', age: 23},
{id: 3, name: '小李', age: 19}
]
}
render() {
return (
<ul>
{
this.state.persons.map((person,index) => {
return <li key={index}>{person.name}</li>
})
}
</ul>
)
}
}
虛擬dom中的key有什么作用?
簡單說key是虛擬dom對象的標(biāo)識,在更新顯示是key起著極其重要的作用,,
詳細(xì)說,當(dāng)狀態(tài)中的數(shù)據(jù)發(fā)生變化的時候,react會根據(jù)新的數(shù)據(jù)生成新的虛擬dom,隨后react進(jìn)行新虛擬dom和就虛擬dom的diff對比,比較規(guī)則如下:
- 就虛擬dom中找到了與新虛擬dom中相同的key:
1) 若虛擬dom中內(nèi)容沒有變化,直接使用之前的真實(shí)dom
2) 若虛擬dom中內(nèi)容變了,則生成新的真實(shí)dom,隨后替換頁面中之前的真實(shí)dom
- 舊虛擬dom中沒有找到與新虛擬dom相同的key
根據(jù)數(shù)據(jù)創(chuàng)建新的真實(shí)dom,隨后渲染到頁面。
用index作為key可能會引發(fā)的問題:
- 若對數(shù)據(jù)進(jìn)行逆序添加,逆序刪除等破壞順序操作,會產(chǎn)生沒有必要的真實(shí)DOM更新 ==》 界面效果沒問題,但是效率低
- 如果頁面中還包含輸入類的dom:
會產(chǎn)生錯誤dom更新==》 界面有問題
注意,如果不存在對數(shù)據(jù)逆序添加刪除等破壞順序的操作,僅用于渲染列表,展示列表,使用index是沒有問題的
<input type="checkbox"/> 這樣的輸入內(nèi)容在react中有一個defaultChecked屬性,可以代替checked屬性,因?yàn)槭褂胏hecked,則必須使用onchange來修改其值
react組件通訊
父子組件 通過props可以通信
兄弟組件 可以提取共同狀態(tài)到公共父組件或者使用消息的發(fā)布訂閱,或者使用redux之類的狀態(tài)管理庫
消息的發(fā)布訂閱實(shí)現(xiàn)兄弟組件之間的通信常用的三方庫有pubsubjs
react路由
-
前端路由的工作原理
依托的是瀏覽器歷史記錄,可以借助history這個庫來完成操作
-
react-router-dom
路由組件
會自動給組件傳入路由相關(guān)的props
NavLink可以實(shí)現(xiàn)路由的高亮,通過activeClassName指定樣式名
標(biāo)簽體內(nèi)容是一個特殊的標(biāo)簽屬性
通過this.props.children可以獲取標(biāo)簽體內(nèi)容
路由組件一般放在pages目錄
一般組件
一般放在components目錄-
Switch組件
該組件會提高性能,如果不適用他,那么在匹配到目標(biāo)組件后還會繼續(xù)往下匹配,使用了他,當(dāng)匹配到目標(biāo)組件后就不會網(wǎng)下匹配其他組件了
-
解決多級路徑頁面刷新樣式丟失的問題
- public/index.html中引入樣式時不寫./而是寫/
- public/index.html中引入樣式時不寫./ 而是%PUBLIC_URL%
- 不要使用HistoryRouter而是使用HashRouter
路由的嚴(yán)格模式與模糊匹配
不是非必要情況下不要用嚴(yán)格模式,比如有二級路由的情況下使用了嚴(yán)格模式就會出問題,如/home /home/mian 當(dāng)使用了嚴(yán)格模式, 那么/home/main永遠(yuǎn)匹配不到-
Redirect的使用
Redirect放在注冊路由的最后,當(dāng)所有路由沒有匹配的時候提供一個默認(rèn)的路由
-
嵌套路由
- 注冊子路由的時候要寫上父路由的path
- 路由的匹配是按照路由的注冊順序進(jìn)行的
向路由組件傳遞params
路由導(dǎo)航向路由組件傳遞params參數(shù) <Link to={`home/message/detail/${id}`}> // 可以傳遞多個參數(shù) <Link to={`home/message/detail/${id}/${title}`}> // 相當(dāng)于傳遞了一個id,一個title 注冊的路由 <Route path="/home/message/detail/:id" component={Detail}> // 可以接收多個參數(shù) <Route path="/home/message/detail/:id/:title" component={Detail}> Detail組件通過props參數(shù)就能接收到參數(shù),在接收到的match里面就有傳遞的參數(shù) -
- 向路由組件傳遞search參數(shù)
```
<Link to={`home/message/detail/?id=${id}&title=${title}`}> // 相當(dāng)于傳遞了一個id,一個title
接受的方式(無需聲明正常注冊路由即可)
<Route path="/home/message/detail" component={Detail}>
Detail組件通過this.props.location.search獲取,獲取到的是一個字符串,我們需要的是對象的格式,因此需要特殊處理,安裝腳手架的時候其實(shí)下載了一個叫做querystring的庫,通過這個庫可以幫我們完成
import qs from 'querystring';
// 通過qs.stringify(obj) 可以將一個對象轉(zhuǎn)為key=value&key=value的格式
// 通過qs.parse(str) 可以將一個key-value格式的字符串變成對象
```
-
向路由組件傳遞state參數(shù) (這里的state不是路由狀態(tài)的state)
無論是params參數(shù)還是search參數(shù)都在地址欄上,還可以通過對象的方式去傳遞,此時的to屬性只能是一個對象,不再是字符串了,
需要注意的是這種方式傳遞參數(shù)雖然地址欄上沒有參數(shù),但是刷新的時候同樣不會丟失參數(shù),因?yàn)樗谴嬖趆istory中的,如果是HashRouter刷新會丟失
```
<Link to={{pathname:`/home/message/detail`, state: {id: id, title: title}}}>
// 接受state
通過this.props.location.state可以獲取
```
- push 與 replace
歷史記錄是一個壓棧模式,默認(rèn)是push,如果要開啟replace需要這么寫, replace是替換模式
<Link replace to={{pathname:`/home/message/detail`, state: {id: id, title: title}}}>
- 編程時路由導(dǎo)航
通過腳本的方式進(jìn)行跳轉(zhuǎn)。路由組件通過this.props.history可以拿到history對象,里面包含操作歷史的方法,需要注意的是編程時路由跳轉(zhuǎn)時注冊路由需要對應(yīng),也就是說通過search跳轉(zhuǎn)的不能以params的方式注冊路由,其他同理,params和query的參數(shù)在地址欄好理解,state的不再地址欄,但是同理對應(yīng)的方式也有第二個參數(shù)可以接收state, 如this.props.history.push(path, state)
編程時路由導(dǎo)航的幾個api:
* this.props.history.push
* this.props.history.replace
* this.props.history.goBack
* this.props.history.goForward
* this.props.history.goBack
* this.props.history.go
- withRouter
路由組件里面可以獲取到操作歷史的方法,但是再非路由組件里面是沒有這些的??梢越柚鷚ithRouter使一般組件也能獲取到history
import {withRouter} from 'react-router-dom'
比如
export default withRouter(Header) ; 通過withRouter將一般組件Header加工后暴露,此時使用header組件就會有history相關(guān)的內(nèi)容
- BrowserRouter與HashRouter的區(qū)別
* 底層原理不一樣,BrowserRouter使用的是H5的history API,不兼容Ie9及以下版本HashRouter使用的是url的hash值
* url表現(xiàn)形式不一樣,HashRouter帶#
* 刷新后對路由state的影響
BrowserRouter沒有影響,因?yàn)閟tate保存在history對象中
HashRouter會丟失state參數(shù)
* HashRouter可以用于解決一些路徑錯誤相關(guān)的問題
redux
-
redux是什么
- 1.1 redux是專門做狀態(tài)管理的庫不是react插件
- 1.2 可以用在react,vue,angular中,但基本與react搭配使用
- 1.3 專門做集中式狀態(tài)管理,管理應(yīng)用中多個組件共享的狀態(tài)
-
什么情況下用redux
- 1.1 某個組件的狀態(tài)需要讓其他組件隨時拿到(共享)
- 1.2 一個組件需要改變另一個組件的狀態(tài)(通信)
-
redux的工作流程
有一個核心store,里面的值只能通過reducer更改,當(dāng)用戶在組件中派發(fā)一個action的時候。action不是特殊的東西,他是一個帶有type和數(shù)據(jù)的對象比如{type: "INCREAMENT", data: {count: 1}}就是一個action,
通過dispatch可以將action交給store,store不干活,他會讓reducer去更改狀態(tài)。reduer將狀態(tài)加工完畢會返回一個新的狀態(tài)給store。reducer能加工狀態(tài),那個狀態(tài)哪里來?其實(shí)reducer干了兩件事,一是初始化狀態(tài),二是加工狀態(tài)。action creator是一個返回action對象的東西。
-
redux的三個核心概念
-
1 action 是一個動作對象包含兩個屬性
- type 標(biāo)識屬性,值為字符串,唯一,必要屬性
- data 數(shù)據(jù)屬性,值類型任意,可選屬性
action分同步action(一般對象) 和異步action(是一個函數(shù)),一個異步的action一定對應(yīng)一個同步的action,使用異步action需要一個中間件,因?yàn)閍ction需要的是一個對象,而不是一個函數(shù),因此需要安裝redux-thunk,引入后 需要在創(chuàng)建store的時候作為第二個參數(shù)傳遞,異步的action是通過store幫助調(diào)用的
import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' createStroe(reducers, applyMiddleware(thunk)) 這里的reducers是個多個reducer,以對象的方式去組織,因?yàn)閷ο蟮膋ey-value存值和取值更方便。因此reducers的結(jié)構(gòu)應(yīng)該是這樣,是多個reducer合并的結(jié)果 首先引入一個函數(shù)將所有的reducer合并為一個總的reducer import {combineReducers} from 'redux' const allReducers = combineReducers({ countRecuder, otherReducer, ... }); -
2 reducer 用于初始化狀態(tài),加工狀態(tài),加工時根據(jù)舊的state和action,產(chǎn)生新的state的純函數(shù)
該函數(shù)接受兩個參數(shù),第一個是之前的狀態(tài),第二個是action對象,初始化的時候第一個參數(shù)是undefined,在創(chuàng)建store的時候會傳遞reducer進(jìn)去,內(nèi)部會調(diào)用reducer進(jìn)行狀態(tài)初始化 reducer必須是純函數(shù),純函數(shù),一個函數(shù)只要接受同樣的實(shí)參,那么一定得到同樣的結(jié)果,不能改寫參數(shù)的數(shù)據(jù) ``` 比如: let arr = [1,2,3]; arr.push(4); 雖然arr的內(nèi)容變了,但是arr的地址引用沒有變,這樣的變化redux是不認(rèn)的。這樣寫才可以 [4, ...arr];這樣得到一個新數(shù)組 像這樣的函數(shù)不是純函數(shù),同樣的輸入不能得到同樣的輸出 function demo(a) { return Math.random() + a } function demo1(a) { // 改寫了參數(shù)的值 a = 9 } 純函數(shù)不會產(chǎn)生任何副作用,例如網(wǎng)絡(luò)請求,輸入和輸出設(shè)備,不能調(diào)用Date.now(), Math.random()等不純的方法 ``` -
3 store 將state,action, reducer聯(lián)系到一起的對象
通過store.getState() 可以得到狀態(tài)
通過store.dispatch(action對象) 可以更改狀態(tài),注意不是直接更改,其實(shí)每一個action對應(yīng)一個reducer,最終是通過reducer來更改,需要注意的是redux只是在維護(hù)狀態(tài),在react中派發(fā)action導(dǎo)致轉(zhuǎn)臺改變,是不能引起頁面的渲染的。因?yàn)閞edux只承諾維護(hù)狀態(tài),沒承諾渲染頁面,如果要渲染需要我們檢測狀態(tài),手動去調(diào)用render. 注意手動調(diào)用不是this.render. 可以通過this.setState({}) 來重新渲染
store.subscribe() 可以檢測redux的狀態(tài),只要redux中的任意狀態(tài)發(fā)生改變,指定的回調(diào)都會執(zhí)行,因此頁面一掛載就可以檢測redux的狀態(tài)了
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}在組件中寫可能寫很多這樣重復(fù)的代碼,因此可以將檢測的邏輯放在入口的index.js中,這樣只要redux的狀態(tài)一發(fā)生變化,真?zhèn)€App就會重新渲染,對應(yīng)的子組件也會渲染。因?yàn)橛衐iff算法的存在,不會引起大面積的重繪重排,因此不用擔(dān)心性能問題。
-
react-redux
react-redux需要注意的幾個點(diǎn):
- 所有的ui組件都應(yīng)該包裹一個容器組件,他們是父子關(guān)系
- 容器組件是真正和redux打交道的,里面可以隨意使用redux的api
- ui組件中不能使用任何redux的api
- 容器組件會傳給ui組件redux中所保存的狀態(tài),用于操作狀態(tài)的方法。
- 容器給ui傳遞狀態(tài),操作狀態(tài)的方法均通過props傳遞
- redux的狀態(tài)變化并不會引起ui的更新,除非手動去檢測狀態(tài)變化。但是用了react-redux就不需要檢測也能更新ui, 因?yàn)槭褂胏onnect的時候他已經(jīng)幫我們檢測了。
容器組件就是一個橋梁,用于連接ui組件和redux,因此在容器組件中需要引入要包裝的ui組件,要引入store(redux的核心就是store),需要注意的是容器組件不能親自引入store,而是通過props的方式傳入store,比如有一個容器組件Count,此時可以通過props傳遞
<Count store={store}/>
需要注意的是頁面中不止這么一個容器組件,他都需要store,那么react-redux提供了一個批量提供store的方法
import {Provider} from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
需要引入連接的方式
import {connect} from 'react-redux'
// 得到一個容器組件
const ContainerComponent = connect()(ui組件);
容器組件和父子組件通信通過props的方式傳遞狀態(tài)和操作狀態(tài)的方法,正常的組件是通過標(biāo)簽屬性的方式給子組件傳遞參數(shù)的,但是容器組件是由connect方法形成的,并沒有和ui組件有這明確的父子關(guān)系,因此需要注意的是connect的第一個()需要兩個參數(shù)。第一個參數(shù)是一個函數(shù),用于生成傳給ui組件的狀態(tài),需要返回一個對象,既然是操作狀態(tài),那么該函數(shù)一定能接受store中的狀態(tài),該函數(shù)接受一個參數(shù)state,第二個參數(shù)同樣是一個函數(shù),(當(dāng)然也可以是一個對象,里面是一系列action,是精簡版的寫法,這這種寫法,react-redux會自動分發(fā)對應(yīng)的action),接受一個分發(fā)action的dispatch方法,返回一個對象,對象封裝的是操作狀態(tài)的方法。從語義上來講,第一個參數(shù)就是一個mapStateToProps,第二個參數(shù)就是mapDispatchToProps
示例代碼
funciton mapStateToProps(state) {
return {
count: state
}
}
function mapDispatchToProps(dispatch) {
return {
increament: (number) => dispatch(createInreamentAction(number))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ui組件)
mapStateToProps映射狀態(tài) mapDispatchToProps映射操作狀態(tài)的對象, 這兩個函數(shù)都返回一個對象
mapDispatchToProps 也可以是一個對象,對象是一系列action,react-redux會自動分發(fā)這個action
注意事項(xiàng):當(dāng)一個ui組件對應(yīng)一個容器組件的時候,如果這樣編碼會導(dǎo)致組件的數(shù)量成倍增加,可以同過一個文件將其整合,定義ui組件,最終以容器組件暴露
redux的開發(fā)工具
一個項(xiàng)目一般都由多個人寫,可能都會用到redux存數(shù)據(jù),但是彼此之間不知道狀態(tài)是如何存的,都存了哪些狀態(tài),這時可以通過redux的開發(fā)工具來快速追蹤狀態(tài)的變化。需要引入一個庫redux-devtools-extension
在store.js中引入
import {composeWithDevTools} from redux-devtools-extension
export default createStore(allReducers,composeWithDevTools(applyMiddleware(thunk)))
關(guān)于setState
setState更新狀態(tài)的兩種寫法
- setState(stateChange, [callbacnk]); // 對象式的寫法,更改狀態(tài)需要手動獲取原來的state
eg: this.setState({count: this.state.count+1}) - setState(updater, [callback]); // 函數(shù)式的寫法,不需要獲取原來的state,因?yàn)闀鳛閰?shù)傳入
eg: this.setState((state, props) => { // 不僅能拿到state, 還可以拿到props
return {count: state.count+1}
})
兩種寫法都有個可選的回調(diào),因?yàn)閞eact是異步更細(xì)的。需要注意的是setState是一個同步方法,由setState引起react更新狀態(tài)是一個異步的操作,這個回調(diào)在狀態(tài)更新完成并且在render后才調(diào)用的.
lazyLoad
路由組件的懶加載,不適用懶加載,所有的路由組件都會一次性被加載,如果有成百上千個路由組件,這是非??植赖摹B酚山M件應(yīng)該在跳那個路由就加載那個路由組件,這就是懶加載,在需要的時候才加載組件
hooks
-
React.useState(狀態(tài)初始值)
該方法返回一個數(shù)組,數(shù)組第一位是當(dāng)前狀態(tài),第二位是更新狀態(tài)的函數(shù),因?yàn)閿?shù)組的解構(gòu)賦值是按下標(biāo)來的,所以如下:
let [count, setCount] = React.useState(0);
需要注意的是每次調(diào)用setCount更新,函數(shù)組件都會執(zhí)行,setCount的參數(shù)可以是一個函數(shù),
-
React.useEffect(fn,[arr]) 可以在函數(shù)式組件里模擬生命周期鉤子,fn就相當(dāng)于一個聲明周期鉤子至于是哪一個需要看情況
如果不指定第二個參數(shù)arr,那么所有的狀態(tài)的改變都會執(zhí)行回調(diào)fn。如果arr是空,表示全都不監(jiān)測,如果不寫第二個參數(shù)那么表示全部進(jìn)行監(jiān)測,如果要監(jiān)聽指定的狀態(tài),就需要在數(shù)組中指明。fn可以返回一個函數(shù)。
React.useRef()
和createRef一樣的作用-
React.createContext
創(chuàng)建context容器對象
const xxxContext = React.createContext();渲染子組件時,外面包裹xxxContext.Provider,通過value屬性給后代組件傳遞數(shù)據(jù)
<xxxContext.Provider value={數(shù)據(jù)}>
子組件
</xxxContext.Provider>
3)后代組件讀取數(shù)據(jù)
方式一,僅適用于類組件
首先聲明接收 static contextType = xxxContext 然后才能通過this.context.value拿到方式二,函數(shù)式組件和類組件都能接收到 <xxxContext.Consumer> { value => ( // value就是context中的value數(shù)據(jù) 要顯示的內(nèi)容 ) } </xxxContext.Consumer>注意,在應(yīng)用開發(fā)中一般不用context,一般都用它來封裝react插件。
組件優(yōu)化
- Component的兩個問題
1) 只要執(zhí)行setState(), 即使不改變狀態(tài)數(shù)據(jù),組件也會重新render() ,也就是setState({}) 也會render.
2) 只要當(dāng)前組件重新render,就會自動重新render子組件 ,效率低。
高效的做法應(yīng)該是
只有當(dāng)前組件的state或者props數(shù)據(jù)發(fā)生改變時才重新render
之所以有這樣的問題是因?yàn)镃omponent中的shouldComponentUpdate()默認(rèn)總是返回true. 通過重寫這個鉤子可以解決這個問題
shouldComponentUpdate(nextProps, nextState){
console.log(this.props, this.state) // 當(dāng)前的props和state
console.log(nextProps, nextState); // 目標(biāo)props和state
// 根據(jù)當(dāng)前數(shù)據(jù)和目標(biāo)數(shù)據(jù)對比來決定返回ture|false ,至于怎么比有人做了,react中提供了PureComponet這個組件,他會重寫 shouldComponentUpdate, 里面的對比邏輯是寫好了的
reuturn true;
}
- PureComponent
該組件其實(shí)也沒干啥,就是重寫了shouldComponentUpdate, PureComponent也會有一點(diǎn)小瑕疵。因?yàn)樗牡讓舆M(jìn)行了淺對比,因此如果是下面的這種寫法同樣會有問題
const obj = this.state;
obj.xxx = 'XXX'; // 改了某一個屬性
this.setState(obj); // 因?yàn)閛bj和state指向了同一個引用,因此在使用PureComponent比較時認(rèn)為state是沒有變化的,所以不會render.因此一定注意使用PureComponent,在更新狀態(tài)時不要和原來的狀態(tài)發(fā)生任何關(guān)系。使用時額外注意數(shù)組的那些方法。
renderProps
場景,比如有兩個組件A,B,不應(yīng)該關(guān)系不大,但是因?yàn)槟承┻壿嫷脑蚩赡苄枰@么寫,
<A>
<B/>
</A>
對于這種格式要個可以通過this.props.children來獲取,但是有個問題如果B組件需要A組件內(nèi)的數(shù)據(jù)是做不到的,因此就有了下面的這種格式
<A render={(parmas) => <B {...params}/>}/> // 就相當(dāng)于傳遞了一個函數(shù),該函數(shù)返回一個組件,函數(shù)名不一定叫render.
在A組件中預(yù)留一個位置this.props.render(可以傳遞A中的狀態(tài)到B),類似于vue中插槽,
組件通信的總結(jié)
* 組件間的關(guān)系
1) 父子組件
2) 兄弟組件 (非嵌套組件)
3) 祖孫組件(跨級組件)
* 幾種通信方式
1) props
1.1) children props
1.2) render props
2) 消息的發(fā)布訂閱
pubsub ,event等
3) 集中式管理如redux, dva等
4) context:
生產(chǎn)者和消費(fèi)者模式
快速預(yù)覽打包后的程序
全局安裝serve,會快速開啟一個服務(wù)。
錯誤邊界
一個頁面有多個組件構(gòu)成,但是其中的某個組件因?yàn)槟承┎豢煽匾蛩貙?dǎo)致出錯,此時整個頁面都會出問題,邊界錯誤就是處理這類問題的,就是當(dāng)某個組件發(fā)生了錯誤,不要導(dǎo)致整個頁面崩潰,而是將錯誤控制在最小的范圍了,出錯了給一個友好的提示。需要注意的是錯誤邊界只有在生產(chǎn)環(huán)境有效,就是打包后才生效,開發(fā)環(huán)境是不生效的??偟膩碚f錯誤邊界就是用來捕獲后代組件錯誤,渲染出備用頁面,只能捕獲后代組件生命周期產(chǎn)生的錯誤,不能捕獲自己組件產(chǎn)生的錯誤和其他組件在合成事件,定時器中產(chǎn)生的錯誤。
當(dāng)Parent的子組件出錯時會走這個鉤子,定義在父組件,并且攜帶錯誤信息err
state = {
error: '' // 默認(rèn)空,當(dāng)發(fā)生了錯誤就會有值,可以根據(jù)這個值給一個友好的提示
}
static getDerivedFromError(err) {
return {error: err}
}
當(dāng)然還有一個鉤子componentDidCatch() {
console.log('渲染組件出錯');
} 當(dāng)組件發(fā)生錯誤的時候就會觸發(fā)該鉤子,這里可以統(tǒng)計發(fā)生錯誤的次數(shù)