ReactDOM.render(<MyComponent/>, document.getElementById("root"))發(fā)生了什么
react解析MyComponent標(biāo)簽找到了MyComponent組件舟茶,發(fā)現(xiàn)組件是類定義的亿眠,隨后new出該類的實(shí)例辛萍,并通過(guò)該實(shí)例調(diào)用到原型上的render方法
將render方法返回的虛擬dom轉(zhuǎn)為真實(shí)dom更啄,隨后呈現(xiàn)在頁(yè)面中置鼻,呈現(xiàn)后還會(huì)調(diào)用一個(gè)componentDidMount
ref
字符串的形式(不推薦使用初狰,存在性能問(wèn)題)
-
回調(diào)的方式
<input ref={(currentNode) => {this.input = currentNode}}/>籽前, 該回調(diào)一上來(lái)就會(huì)調(diào)用一次
PS: 向上面這種回調(diào)的方式(內(nèi)聯(lián)的方式)在更新的時(shí)候會(huì)調(diào)用兩次蛙婴,注意是更新的時(shí)候,第一次currentNode為null(因?yàn)槊看蝦ender都會(huì)創(chuàng)建一個(gè)新的內(nèi)聯(lián)函數(shù)舔糖,當(dāng)更新的時(shí)候第一次調(diào)用是為了清空就得函數(shù)娱两,所以返回一個(gè)null),第二次才會(huì)把節(jié)點(diǎn)傳入. 如果非得解決這個(gè)問(wèn)題,react也提供了方案金吗,通過(guò)類綁定的方式:<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返回一個(gè)容器,該容器用于存放被ref所標(biāo)識(shí)的節(jié)點(diǎn)摇庙,該容器專人專用只能綁定一個(gè)旱物,因此后放進(jìn)去的會(huì)覆蓋之前的,如果想使用多個(gè)就需要createRef多次
render() {
return (
<input ref={this.myRef}/> {/*這里相當(dāng)于將input存放到了myRef中了卫袒,input節(jié)點(diǎn)可以通過(guò)this.myRef.current訪問(wèn)*/}
)
}
}
react中的事件
-
1 通過(guò)onXXX屬性指定事件處理函數(shù)
react中使用的是自定義事件(合成事件宵呛,而不是使用原生的dom事件)--- 是為了更好的兼容性react中的事件是通過(guò)事件委托的方式處理的(委托給組件最外層元素)--- 事件委托的原理是事件冒泡,使用事件委托高效
2 通過(guò)event.target得到發(fā)生事件的dom元素對(duì)象
收集表單數(shù)據(jù)
-
非受控組件
現(xiàn)用現(xiàn)取夕凝,通過(guò)ref可以完成
-
受控組件
輸入類的元素在隨著輸入將值維護(hù)到狀態(tài)中
高階函數(shù)和柯里化
高階函數(shù):
若函數(shù)A接受的參數(shù)是一個(gè)函數(shù)宝穗,那么A就稱之為高階函數(shù)
若函數(shù)A調(diào)用的返回值依然是一個(gè)函數(shù),那么A就稱之為高階函數(shù)
常見(jiàn)的高階函數(shù):如Promise, setTimeout, 數(shù)組的常用方式
函數(shù)柯里化
部分求值码秉,函數(shù)的調(diào)用仍舊返回函數(shù)逮矛,實(shí)現(xiàn)多次接受參數(shù)最后統(tǒng)一處理的函數(shù)編碼
生命周期
組件從創(chuàng)建到死亡會(huì)經(jīng)歷一些特定的階段
React組件中包含一系列鉤子函數(shù),會(huì)在特定的時(shí)刻調(diào)用
在定義組件時(shí)泡徙,會(huì)在特定的聲明周期回調(diào)函數(shù)中做特定的工作
- 舊版的生命周期鉤子
組件掛載的執(zhí)行順序
constructor ---> componentWillMount ---> render ---> componentDidMount ---> componentWillUnmount
組件更新執(zhí)行順序(可以分為3條線)
父組件渲染子組件橱鹏,當(dāng)傳入的數(shù)據(jù)更新時(shí)的執(zhí)行路線
componentWillReceiveProps ---> shouldComponentUpdate ---> componenWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount
當(dāng)組件setState更新的時(shí)候
setState() ---> shouldComponentUpdate ---> componentWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount
當(dāng)強(qiáng)制更新組件的時(shí)候(不更改狀態(tài)數(shù)據(jù)就想讓組件更新就可以走這條線)
forceUpdate() ---> componentWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount
ps: shouldComponentUpdate是一個(gè)閥門鉤子,如果不寫這個(gè)鉤子默認(rèn)返回true,接下來(lái)的流程正常執(zhí)行堪藐,如果寫了并且返回false則接下來(lái)的流程不會(huì)執(zhí)行莉兰,只能返回true或者false,
componentWillReciveProps 這個(gè)鉤子第一次不調(diào)用,只有接受的props變化了才會(huì)調(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: 一般做初始化操作例如開(kāi)啟定時(shí)器模捂,發(fā)送網(wǎng)絡(luò)請(qǐng)求捶朵,訂閱消息蜘矢。。综看。
componentWillUnmount: 做一些收尾工作如取消定時(shí)器品腹,取消訂閱等
render: 必須用,需要掉1+n次
- 新的聲明周期 (react最新版本)
所有帶will的鉤子在新版版中都加了UNSAFE_前綴红碑, 除了卸載的鉤子舞吭,為什么這么做,因?yàn)閞eact在設(shè)計(jì)異步渲染析珊,這些組件可能會(huì)出現(xiàn)bug,以后可能會(huì)被廢棄
新版本廢棄了componentWillMount, componentWillUpdate, componentWillReciveProps,新增了getDerivedStateFromProps 和getSnapshotBeforeUpdate
需要注意的是 getDerivedStateFromProps不能在實(shí)例上調(diào)用羡鸥,需要聲明成靜態(tài)的需要加static, 而且該方法必須要有返回值,要么你返回狀態(tài)對(duì)象忠寻,要么返回null惧浴,不能返回其他的。需要注意的是只要返回一個(gè)對(duì)象奕剃,那么狀態(tài)的更新就沒(méi)有意義了衷旅,因?yàn)橐坏┓祷貭顟B(tài),此狀態(tài)就不能被改了祭饭,該方法接收一個(gè)參數(shù)props, 他會(huì)接收props屬性并且會(huì)派生出一個(gè)取決于props的狀態(tài)芜茵⌒鹆浚可以接收第二個(gè)參數(shù)state,使用場(chǎng)景罕見(jiàn)倡蝙,基本不用。
getSnapshotBeforeUpdate: 該鉤子處于render和componentDidUpdate之間绞佩,用于在更新之前獲取快照寺鸥,因?yàn)楦潞笾暗臓顟B(tài)就不見(jiàn)了,在更新之前再看一眼之前的狀態(tài)品山。
componentDidUpdate() 該鉤子會(huì)接收三個(gè)參數(shù)胆建,第一個(gè)是更新前的props,第二個(gè)是更新前的state,第三個(gè)是一個(gè)快照值(即getSnapshotBeforeUpdate返回的值肘交。)
掛載時(shí):
constructor ---> getDerivedStateFromProps ---> render (react更新dom和refs) ---> componentDidMount
更新:
(new props | setState() | forceUpdate)時(shí) ---> getDerivedStateFromProps(得到一個(gè)派生的狀態(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有什么作用笆载?
簡(jiǎn)單說(shuō)key是虛擬dom對(duì)象的標(biāo)識(shí),在更新顯示是key起著極其重要的作用涯呻,凉驻,
詳細(xì)說(shuō),當(dāng)狀態(tài)中的數(shù)據(jù)發(fā)生變化的時(shí)候复罐,react會(huì)根據(jù)新的數(shù)據(jù)生成新的虛擬dom,隨后react進(jìn)行新虛擬dom和就虛擬dom的diff對(duì)比涝登,比較規(guī)則如下:
- 就虛擬dom中找到了與新虛擬dom中相同的key:
1) 若虛擬dom中內(nèi)容沒(méi)有變化,直接使用之前的真實(shí)dom
2) 若虛擬dom中內(nèi)容變了效诅,則生成新的真實(shí)dom胀滚,隨后替換頁(yè)面中之前的真實(shí)dom
- 舊虛擬dom中沒(méi)有找到與新虛擬dom相同的key
根據(jù)數(shù)據(jù)創(chuàng)建新的真實(shí)dom趟济,隨后渲染到頁(yè)面。
用index作為key可能會(huì)引發(fā)的問(wèn)題:
- 若對(duì)數(shù)據(jù)進(jìn)行逆序添加咽笼,逆序刪除等破壞順序操作顷编,會(huì)產(chǎn)生沒(méi)有必要的真實(shí)DOM更新 ==》 界面效果沒(méi)問(wèn)題,但是效率低
- 如果頁(yè)面中還包含輸入類的dom:
會(huì)產(chǎn)生錯(cuò)誤dom更新==》 界面有問(wèn)題
注意剑刑,如果不存在對(duì)數(shù)據(jù)逆序添加刪除等破壞順序的操作勾效,僅用于渲染列表,展示列表叛甫,使用index是沒(méi)有問(wèn)題的
<input type="checkbox"/> 這樣的輸入內(nèi)容在react中有一個(gè)defaultChecked屬性层宫,可以代替checked屬性,因?yàn)槭褂胏hecked其监,則必須使用onchange來(lái)修改其值
react組件通訊
父子組件 通過(guò)props可以通信
兄弟組件 可以提取共同狀態(tài)到公共父組件或者使用消息的發(fā)布訂閱萌腿,或者使用redux之類的狀態(tài)管理庫(kù)
消息的發(fā)布訂閱實(shí)現(xiàn)兄弟組件之間的通信常用的三方庫(kù)有pubsubjs
react路由
-
前端路由的工作原理
依托的是瀏覽器歷史記錄,可以借助history這個(gè)庫(kù)來(lái)完成操作
-
react-router-dom
路由組件
會(huì)自動(dòng)給組件傳入路由相關(guān)的props
NavLink可以實(shí)現(xiàn)路由的高亮抖苦,通過(guò)activeClassName指定樣式名
標(biāo)簽體內(nèi)容是一個(gè)特殊的標(biāo)簽屬性
通過(guò)this.props.children可以獲取標(biāo)簽體內(nèi)容
路由組件一般放在pages目錄
一般組件
一般放在components目錄-
Switch組件
該組件會(huì)提高性能毁菱,如果不適用他,那么在匹配到目標(biāo)組件后還會(huì)繼續(xù)往下匹配锌历,使用了他贮庞,當(dāng)匹配到目標(biāo)組件后就不會(huì)網(wǎng)下匹配其他組件了
-
解決多級(jí)路徑頁(yè)面刷新樣式丟失的問(wèn)題
- public/index.html中引入樣式時(shí)不寫./而是寫/
- public/index.html中引入樣式時(shí)不寫./ 而是%PUBLIC_URL%
- 不要使用HistoryRouter而是使用HashRouter
路由的嚴(yán)格模式與模糊匹配
不是非必要情況下不要用嚴(yán)格模式,比如有二級(jí)路由的情況下使用了嚴(yán)格模式就會(huì)出問(wèn)題究西,如/home /home/mian 當(dāng)使用了嚴(yán)格模式窗慎, 那么/home/main永遠(yuǎn)匹配不到-
Redirect的使用
Redirect放在注冊(cè)路由的最后,當(dāng)所有路由沒(méi)有匹配的時(shí)候提供一個(gè)默認(rèn)的路由
-
嵌套路由
- 注冊(cè)子路由的時(shí)候要寫上父路由的path
- 路由的匹配是按照路由的注冊(cè)順序進(jìn)行的
向路由組件傳遞params
路由導(dǎo)航向路由組件傳遞params參數(shù) <Link to={`home/message/detail/${id}`}> // 可以傳遞多個(gè)參數(shù) <Link to={`home/message/detail/${id}/${title}`}> // 相當(dāng)于傳遞了一個(gè)id,一個(gè)title 注冊(cè)的路由 <Route path="/home/message/detail/:id" component={Detail}> // 可以接收多個(gè)參數(shù) <Route path="/home/message/detail/:id/:title" component={Detail}> Detail組件通過(guò)props參數(shù)就能接收到參數(shù)卤材,在接收到的match里面就有傳遞的參數(shù)
-
- 向路由組件傳遞search參數(shù)
```
<Link to={`home/message/detail/?id=${id}&title=${title}`}> // 相當(dāng)于傳遞了一個(gè)id,一個(gè)title
接受的方式(無(wú)需聲明正常注冊(cè)路由即可)
<Route path="/home/message/detail" component={Detail}>
Detail組件通過(guò)this.props.location.search獲取遮斥,獲取到的是一個(gè)字符串,我們需要的是對(duì)象的格式扇丛,因此需要特殊處理术吗,安裝腳手架的時(shí)候其實(shí)下載了一個(gè)叫做querystring的庫(kù),通過(guò)這個(gè)庫(kù)可以幫我們完成
import qs from 'querystring';
// 通過(guò)qs.stringify(obj) 可以將一個(gè)對(duì)象轉(zhuǎn)為key=value&key=value的格式
// 通過(guò)qs.parse(str) 可以將一個(gè)key-value格式的字符串變成對(duì)象
```
-
向路由組件傳遞state參數(shù) (這里的state不是路由狀態(tài)的state)
無(wú)論是params參數(shù)還是search參數(shù)都在地址欄上帆精,還可以通過(guò)對(duì)象的方式去傳遞较屿,此時(shí)的to屬性只能是一個(gè)對(duì)象,不再是字符串了卓练,
需要注意的是這種方式傳遞參數(shù)雖然地址欄上沒(méi)有參數(shù)隘蝎,但是刷新的時(shí)候同樣不會(huì)丟失參數(shù),因?yàn)樗谴嬖趆istory中的昆庇,如果是HashRouter刷新會(huì)丟失
```
<Link to={{pathname:`/home/message/detail`, state: {id: id, title: title}}}>
// 接受state
通過(guò)this.props.location.state可以獲取
```
- push 與 replace
歷史記錄是一個(gè)壓棧模式末贾,默認(rèn)是push,如果要開(kāi)啟replace需要這么寫, replace是替換模式
<Link replace to={{pathname:`/home/message/detail`, state: {id: id, title: title}}}>
- 編程時(shí)路由導(dǎo)航
通過(guò)腳本的方式進(jìn)行跳轉(zhuǎn)。路由組件通過(guò)this.props.history可以拿到history對(duì)象整吆,里面包含操作歷史的方法拱撵,需要注意的是編程時(shí)路由跳轉(zhuǎn)時(shí)注冊(cè)路由需要對(duì)應(yīng)辉川,也就是說(shuō)通過(guò)search跳轉(zhuǎn)的不能以params的方式注冊(cè)路由,其他同理拴测,params和query的參數(shù)在地址欄好理解乓旗,state的不再地址欄,但是同理對(duì)應(yīng)的方式也有第二個(gè)參數(shù)可以接收state, 如this.props.history.push(path, state)
編程時(shí)路由導(dǎo)航的幾個(gè)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
路由組件里面可以獲取到操作歷史的方法集索,但是再非路由組件里面是沒(méi)有這些的屿愚。可以借助withRouter使一般組件也能獲取到history
import {withRouter} from 'react-router-dom'
比如
export default withRouter(Header) ; 通過(guò)withRouter將一般組件Header加工后暴露务荆,此時(shí)使用header組件就會(huì)有history相關(guān)的內(nèi)容
- BrowserRouter與HashRouter的區(qū)別
* 底層原理不一樣妆距,BrowserRouter使用的是H5的history API,不兼容Ie9及以下版本HashRouter使用的是url的hash值
* url表現(xiàn)形式不一樣函匕,HashRouter帶#
* 刷新后對(duì)路由state的影響
BrowserRouter沒(méi)有影響娱据,因?yàn)閟tate保存在history對(duì)象中
HashRouter會(huì)丟失state參數(shù)
* HashRouter可以用于解決一些路徑錯(cuò)誤相關(guān)的問(wèn)題
redux
-
redux是什么
- 1.1 redux是專門做狀態(tài)管理的庫(kù)不是react插件
- 1.2 可以用在react,vue,angular中盅惜,但基本與react搭配使用
- 1.3 專門做集中式狀態(tài)管理中剩,管理應(yīng)用中多個(gè)組件共享的狀態(tài)
-
什么情況下用redux
- 1.1 某個(gè)組件的狀態(tài)需要讓其他組件隨時(shí)拿到(共享)
- 1.2 一個(gè)組件需要改變另一個(gè)組件的狀態(tài)(通信)
-
redux的工作流程
有一個(gè)核心store,里面的值只能通過(guò)reducer更改,當(dāng)用戶在組件中派發(fā)一個(gè)action的時(shí)候抒寂。action不是特殊的東西结啼,他是一個(gè)帶有type和數(shù)據(jù)的對(duì)象比如{type: "INCREAMENT", data: {count: 1}}就是一個(gè)action,
通過(guò)dispatch可以將action交給store,store不干活屈芜,他會(huì)讓reducer去更改狀態(tài)郊愧。reduer將狀態(tài)加工完畢會(huì)返回一個(gè)新的狀態(tài)給store。reducer能加工狀態(tài)沸伏,那個(gè)狀態(tài)哪里來(lái)糕珊?其實(shí)reducer干了兩件事,一是初始化狀態(tài)毅糟,二是加工狀態(tài)。action creator是一個(gè)返回action對(duì)象的東西澜公。
-
redux的三個(gè)核心概念
-
1 action 是一個(gè)動(dòng)作對(duì)象包含兩個(gè)屬性
- type 標(biāo)識(shí)屬性姆另,值為字符串,唯一坟乾,必要屬性
- data 數(shù)據(jù)屬性迹辐,值類型任意,可選屬性
action分同步action(一般對(duì)象) 和異步action(是一個(gè)函數(shù))甚侣,一個(gè)異步的action一定對(duì)應(yīng)一個(gè)同步的action,使用異步action需要一個(gè)中間件明吩,因?yàn)閍ction需要的是一個(gè)對(duì)象,而不是一個(gè)函數(shù)殷费,因此需要安裝redux-thunk,引入后 需要在創(chuàng)建store的時(shí)候作為第二個(gè)參數(shù)傳遞,異步的action是通過(guò)store幫助調(diào)用的
import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' createStroe(reducers, applyMiddleware(thunk)) 這里的reducers是個(gè)多個(gè)reducer,以對(duì)象的方式去組織印荔,因?yàn)閷?duì)象的key-value存值和取值更方便低葫。因此reducers的結(jié)構(gòu)應(yīng)該是這樣,是多個(gè)reducer合并的結(jié)果 首先引入一個(gè)函數(shù)將所有的reducer合并為一個(gè)總的reducer import {combineReducers} from 'redux' const allReducers = combineReducers({ countRecuder, otherReducer, ... });
-
2 reducer 用于初始化狀態(tài)仍律,加工狀態(tài)嘿悬,加工時(shí)根據(jù)舊的state和action,產(chǎn)生新的state的純函數(shù)
該函數(shù)接受兩個(gè)參數(shù)水泉,第一個(gè)是之前的狀態(tài)善涨,第二個(gè)是action對(duì)象,初始化的時(shí)候第一個(gè)參數(shù)是undefined草则,在創(chuàng)建store的時(shí)候會(huì)傳遞reducer進(jìn)去钢拧,內(nèi)部會(huì)調(diào)用reducer進(jìn)行狀態(tài)初始化 reducer必須是純函數(shù),純函數(shù)炕横,一個(gè)函數(shù)只要接受同樣的實(shí)參娶靡,那么一定得到同樣的結(jié)果,不能改寫參數(shù)的數(shù)據(jù) ``` 比如: let arr = [1,2,3]; arr.push(4); 雖然arr的內(nèi)容變了,但是arr的地址引用沒(méi)有變看锉,這樣的變化redux是不認(rèn)的姿锭。這樣寫才可以 [4, ...arr];這樣得到一個(gè)新數(shù)組 像這樣的函數(shù)不是純函數(shù),同樣的輸入不能得到同樣的輸出 function demo(a) { return Math.random() + a } function demo1(a) { // 改寫了參數(shù)的值 a = 9 } 純函數(shù)不會(huì)產(chǎn)生任何副作用伯铣,例如網(wǎng)絡(luò)請(qǐng)求呻此,輸入和輸出設(shè)備,不能調(diào)用Date.now(), Math.random()等不純的方法 ```
-
3 store 將state,action, reducer聯(lián)系到一起的對(duì)象
通過(guò)store.getState() 可以得到狀態(tài)
通過(guò)store.dispatch(action對(duì)象) 可以更改狀態(tài)腔寡,注意不是直接更改焚鲜,其實(shí)每一個(gè)action對(duì)應(yīng)一個(gè)reducer,最終是通過(guò)reducer來(lái)更改,需要注意的是redux只是在維護(hù)狀態(tài)放前,在react中派發(fā)action導(dǎo)致轉(zhuǎn)臺(tái)改變忿磅,是不能引起頁(yè)面的渲染的。因?yàn)閞edux只承諾維護(hù)狀態(tài)凭语,沒(méi)承諾渲染頁(yè)面葱她,如果要渲染需要我們檢測(cè)狀態(tài),手動(dòng)去調(diào)用render. 注意手動(dòng)調(diào)用不是this.render. 可以通過(guò)this.setState({}) 來(lái)重新渲染
store.subscribe() 可以檢測(cè)redux的狀態(tài)似扔,只要redux中的任意狀態(tài)發(fā)生改變吨些,指定的回調(diào)都會(huì)執(zhí)行,因此頁(yè)面一掛載就可以檢測(cè)redux的狀態(tài)了
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}在組件中寫可能寫很多這樣重復(fù)的代碼炒辉,因此可以將檢測(cè)的邏輯放在入口的index.js中豪墅,這樣只要redux的狀態(tài)一發(fā)生變化,真?zhèn)€App就會(huì)重新渲染黔寇,對(duì)應(yīng)的子組件也會(huì)渲染偶器。因?yàn)橛衐iff算法的存在,不會(huì)引起大面積的重繪重排,因此不用擔(dān)心性能問(wèn)題屏轰。
-
react-redux
react-redux需要注意的幾個(gè)點(diǎn):
- 所有的ui組件都應(yīng)該包裹一個(gè)容器組件颊郎,他們是父子關(guān)系
- 容器組件是真正和redux打交道的,里面可以隨意使用redux的api
- ui組件中不能使用任何redux的api
- 容器組件會(huì)傳給ui組件redux中所保存的狀態(tài)亭枷,用于操作狀態(tài)的方法袭艺。
- 容器給ui傳遞狀態(tài),操作狀態(tài)的方法均通過(guò)props傳遞
- redux的狀態(tài)變化并不會(huì)引起ui的更新叨粘,除非手動(dòng)去檢測(cè)狀態(tài)變化猾编。但是用了react-redux就不需要檢測(cè)也能更新ui, 因?yàn)槭褂胏onnect的時(shí)候他已經(jīng)幫我們檢測(cè)了。
容器組件就是一個(gè)橋梁升敲,用于連接ui組件和redux,因此在容器組件中需要引入要包裝的ui組件答倡,要引入store(redux的核心就是store),需要注意的是容器組件不能親自引入store,而是通過(guò)props的方式傳入store,比如有一個(gè)容器組件Count,此時(shí)可以通過(guò)props傳遞
<Count store={store}/>
需要注意的是頁(yè)面中不止這么一個(gè)容器組件驴党,他都需要store,那么react-redux提供了一個(gè)批量提供store的方法
import {Provider} from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
需要引入連接的方式
import {connect} from 'react-redux'
// 得到一個(gè)容器組件
const ContainerComponent = connect()(ui組件);
容器組件和父子組件通信通過(guò)props的方式傳遞狀態(tài)和操作狀態(tài)的方法瘪撇,正常的組件是通過(guò)標(biāo)簽屬性的方式給子組件傳遞參數(shù)的,但是容器組件是由connect方法形成的港庄,并沒(méi)有和ui組件有這明確的父子關(guān)系倔既,因此需要注意的是connect的第一個(gè)()需要兩個(gè)參數(shù)。第一個(gè)參數(shù)是一個(gè)函數(shù)鹏氧,用于生成傳給ui組件的狀態(tài)渤涌,需要返回一個(gè)對(duì)象,既然是操作狀態(tài)把还,那么該函數(shù)一定能接受store中的狀態(tài)实蓬,該函數(shù)接受一個(gè)參數(shù)state,第二個(gè)參數(shù)同樣是一個(gè)函數(shù)吊履,(當(dāng)然也可以是一個(gè)對(duì)象安皱,里面是一系列action,是精簡(jiǎn)版的寫法艇炎,這這種寫法酌伊,react-redux會(huì)自動(dòng)分發(fā)對(duì)應(yīng)的action),接受一個(gè)分發(fā)action的dispatch方法冕臭,返回一個(gè)對(duì)象腺晾,對(duì)象封裝的是操作狀態(tài)的方法。從語(yǔ)義上來(lái)講辜贵,第一個(gè)參數(shù)就是一個(gè)mapStateToProps,第二個(gè)參數(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)的對(duì)象, 這兩個(gè)函數(shù)都返回一個(gè)對(duì)象
mapDispatchToProps 也可以是一個(gè)對(duì)象归形,對(duì)象是一系列action托慨,react-redux會(huì)自動(dòng)分發(fā)這個(gè)action
注意事項(xiàng):當(dāng)一個(gè)ui組件對(duì)應(yīng)一個(gè)容器組件的時(shí)候,如果這樣編碼會(huì)導(dǎo)致組件的數(shù)量成倍增加暇榴,可以同過(guò)一個(gè)文件將其整合厚棵,定義ui組件蕉世,最終以容器組件暴露
redux的開(kāi)發(fā)工具
一個(gè)項(xiàng)目一般都由多個(gè)人寫,可能都會(huì)用到redux存數(shù)據(jù)婆硬,但是彼此之間不知道狀態(tài)是如何存的狠轻,都存了哪些狀態(tài),這時(shí)可以通過(guò)redux的開(kāi)發(fā)工具來(lái)快速追蹤狀態(tài)的變化彬犯。需要引入一個(gè)庫(kù)redux-devtools-extension
在store.js中引入
import {composeWithDevTools} from redux-devtools-extension
export default createStore(allReducers,composeWithDevTools(applyMiddleware(thunk)))
關(guān)于setState
setState更新?tīng)顟B(tài)的兩種寫法
- setState(stateChange, [callbacnk]); // 對(duì)象式的寫法向楼,更改狀態(tài)需要手動(dòng)獲取原來(lái)的state
eg: this.setState({count: this.state.count+1}) - setState(updater, [callback]); // 函數(shù)式的寫法,不需要獲取原來(lái)的state,因?yàn)闀?huì)作為參數(shù)傳入
eg: this.setState((state, props) => { // 不僅能拿到state, 還可以拿到props
return {count: state.count+1}
})
兩種寫法都有個(gè)可選的回調(diào),因?yàn)閞eact是異步更細(xì)的谐区。需要注意的是setState是一個(gè)同步方法湖蜕,由setState引起react更新?tīng)顟B(tài)是一個(gè)異步的操作,這個(gè)回調(diào)在狀態(tài)更新完成并且在render后才調(diào)用的.
lazyLoad
路由組件的懶加載宋列,不適用懶加載昭抒,所有的路由組件都會(huì)一次性被加載,如果有成百上千個(gè)路由組件炼杖,這是非趁鸱担恐怖的。路由組件應(yīng)該在跳那個(gè)路由就加載那個(gè)路由組件坤邪,這就是懶加載熙含,在需要的時(shí)候才加載組件
hooks
-
React.useState(狀態(tài)初始值)
該方法返回一個(gè)數(shù)組,數(shù)組第一位是當(dāng)前狀態(tài)罩扇,第二位是更新?tīng)顟B(tài)的函數(shù)婆芦,因?yàn)閿?shù)組的解構(gòu)賦值是按下標(biāo)來(lái)的,所以如下:
let [count, setCount] = React.useState(0);
需要注意的是每次調(diào)用setCount更新喂饥,函數(shù)組件都會(huì)執(zhí)行消约,setCount的參數(shù)可以是一個(gè)函數(shù),
-
React.useEffect(fn员帮,[arr]) 可以在函數(shù)式組件里模擬生命周期鉤子,fn就相當(dāng)于一個(gè)聲明周期鉤子至于是哪一個(gè)需要看情況
如果不指定第二個(gè)參數(shù)arr,那么所有的狀態(tài)的改變都會(huì)執(zhí)行回調(diào)fn或粮。如果arr是空,表示全都不監(jiān)測(cè)捞高,如果不寫第二個(gè)參數(shù)那么表示全部進(jìn)行監(jiān)測(cè)氯材,如果要監(jiān)聽(tīng)指定的狀態(tài),就需要在數(shù)組中指明硝岗。fn可以返回一個(gè)函數(shù)氢哮。
React.useRef()
和createRef一樣的作用-
React.createContext
創(chuàng)建context容器對(duì)象
const xxxContext = React.createContext();渲染子組件時(shí),外面包裹xxxContext.Provider,通過(guò)value屬性給后代組件傳遞數(shù)據(jù)
<xxxContext.Provider value={數(shù)據(jù)}>
子組件
</xxxContext.Provider>
3)后代組件讀取數(shù)據(jù)
方式一型檀,僅適用于類組件
首先聲明接收 static contextType = xxxContext 然后才能通過(guò)this.context.value拿到方式二冗尤,函數(shù)式組件和類組件都能接收到 <xxxContext.Consumer> { value => ( // value就是context中的value數(shù)據(jù) 要顯示的內(nèi)容 ) } </xxxContext.Consumer>
注意,在應(yīng)用開(kāi)發(fā)中一般不用context,一般都用它來(lái)封裝react插件。
組件優(yōu)化
- Component的兩個(gè)問(wèn)題
1) 只要執(zhí)行setState(), 即使不改變狀態(tài)數(shù)據(jù)裂七,組件也會(huì)重新render() 皆看,也就是setState({}) 也會(huì)render.
2) 只要當(dāng)前組件重新render,就會(huì)自動(dòng)重新render子組件 ,效率低背零。
高效的做法應(yīng)該是
只有當(dāng)前組件的state或者props數(shù)據(jù)發(fā)生改變時(shí)才重新render
之所以有這樣的問(wèn)題是因?yàn)镃omponent中的shouldComponentUpdate()默認(rèn)總是返回true. 通過(guò)重寫這個(gè)鉤子可以解決這個(gè)問(wèn)題
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ù)對(duì)比來(lái)決定返回ture|false ,至于怎么比有人做了腰吟,react中提供了PureComponet這個(gè)組件,他會(huì)重寫 shouldComponentUpdate, 里面的對(duì)比邏輯是寫好了的
reuturn true;
}
- PureComponent
該組件其實(shí)也沒(méi)干啥徙瓶,就是重寫了shouldComponentUpdate, PureComponent也會(huì)有一點(diǎn)小瑕疵毛雇。因?yàn)樗牡讓舆M(jìn)行了淺對(duì)比,因此如果是下面的這種寫法同樣會(huì)有問(wèn)題
const obj = this.state;
obj.xxx = 'XXX'; // 改了某一個(gè)屬性
this.setState(obj); // 因?yàn)閛bj和state指向了同一個(gè)引用倍啥,因此在使用PureComponent比較時(shí)認(rèn)為state是沒(méi)有變化的禾乘,所以不會(huì)render.因此一定注意使用PureComponent,在更新?tīng)顟B(tài)時(shí)不要和原來(lái)的狀態(tài)發(fā)生任何關(guān)系虽缕。使用時(shí)額外注意數(shù)組的那些方法始藕。
renderProps
場(chǎng)景,比如有兩個(gè)組件A,B氮趋,不應(yīng)該關(guān)系不大伍派,但是因?yàn)槟承┻壿嫷脑蚩赡苄枰@么寫,
<A>
<B/>
</A>
對(duì)于這種格式要個(gè)可以通過(guò)this.props.children來(lái)獲取剩胁,但是有個(gè)問(wèn)題如果B組件需要A組件內(nèi)的數(shù)據(jù)是做不到的诉植,因此就有了下面的這種格式
<A render={(parmas) => <B {...params}/>}/> // 就相當(dāng)于傳遞了一個(gè)函數(shù),該函數(shù)返回一個(gè)組件昵观,函數(shù)名不一定叫render.
在A組件中預(yù)留一個(gè)位置this.props.render(可以傳遞A中的狀態(tài)到B)晾腔,類似于vue中插槽,
組件通信的總結(jié)
* 組件間的關(guān)系
1) 父子組件
2) 兄弟組件 (非嵌套組件)
3) 祖孫組件(跨級(jí)組件)
* 幾種通信方式
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,會(huì)快速開(kāi)啟一個(gè)服務(wù)啊犬。
錯(cuò)誤邊界
一個(gè)頁(yè)面有多個(gè)組件構(gòu)成灼擂,但是其中的某個(gè)組件因?yàn)槟承┎豢煽匾蛩貙?dǎo)致出錯(cuò),此時(shí)整個(gè)頁(yè)面都會(huì)出問(wèn)題觉至,邊界錯(cuò)誤就是處理這類問(wèn)題的剔应,就是當(dāng)某個(gè)組件發(fā)生了錯(cuò)誤,不要導(dǎo)致整個(gè)頁(yè)面崩潰语御,而是將錯(cuò)誤控制在最小的范圍了峻贮,出錯(cuò)了給一個(gè)友好的提示。需要注意的是錯(cuò)誤邊界只有在生產(chǎn)環(huán)境有效应闯,就是打包后才生效纤控,開(kāi)發(fā)環(huán)境是不生效的〉锓模總的來(lái)說(shuō)錯(cuò)誤邊界就是用來(lái)捕獲后代組件錯(cuò)誤嚼黔,渲染出備用頁(yè)面细层,只能捕獲后代組件生命周期產(chǎn)生的錯(cuò)誤惜辑,不能捕獲自己組件產(chǎn)生的錯(cuò)誤和其他組件在合成事件唬涧,定時(shí)器中產(chǎn)生的錯(cuò)誤。
當(dāng)Parent的子組件出錯(cuò)時(shí)會(huì)走這個(gè)鉤子,定義在父組件盛撑,并且攜帶錯(cuò)誤信息err
state = {
error: '' // 默認(rèn)空碎节,當(dāng)發(fā)生了錯(cuò)誤就會(huì)有值,可以根據(jù)這個(gè)值給一個(gè)友好的提示
}
static getDerivedFromError(err) {
return {error: err}
}
當(dāng)然還有一個(gè)鉤子componentDidCatch() {
console.log('渲染組件出錯(cuò)');
} 當(dāng)組件發(fā)生錯(cuò)誤的時(shí)候就會(huì)觸發(fā)該鉤子抵卫,這里可以統(tǒng)計(jì)發(fā)生錯(cuò)誤的次數(shù)