【面試題】697- React萬(wàn)字長(zhǎng)文面試題梳理

原文地址:www.imooc.com/article/309371
1、React 中 keys的作用是什么
Keys是 React 用于追蹤哪些列表中元素被修改、被添加或者被移除的輔助標(biāo)識(shí)
在開(kāi)發(fā)過(guò)程中我們需要保證某個(gè)元素的 key 在其同級(jí)元素中具有唯一性。在 React Diff 算法中React 會(huì)借助元素的 Key 值來(lái)判斷該元素是新近創(chuàng)建的還是被移動(dòng)而來(lái)的元素從而減少不必要的元素重渲染。此外React 還需要借助 Key 值來(lái)判斷元素與本地狀態(tài)的關(guān)聯(lián)關(guān)系因此我們絕不可忽視轉(zhuǎn)換函數(shù)中 Key 的重要性
2、傳入 setState 函數(shù)的第二個(gè)參數(shù)的作用是什么
該函數(shù)會(huì)在 setState 函數(shù)調(diào)用完成并且組件開(kāi)始重渲染的時(shí)候被調(diào)用我們可以用該函數(shù)來(lái)監(jiān)聽(tīng)渲染是否完成
this.setState(
??{?username:?'tylermcginnis33'?},
??()?=>?console.log('setState?has?finished?and?the?component?has?re-rendered.')
)
this.setState((prevState,?props)?=>?{
??return?{
????streak:?prevState.streak?+?props.count
??}
})
3、React 中 refs 的作用是什么
Refs 是 React 提供給我們的安全訪問(wèn) DOM元素或者某個(gè)組件實(shí)例的句柄可以為元素添加ref屬性然后在回調(diào)函數(shù)中接受該元素在 DOM 樹(shù)中的句柄該值會(huì)作為回調(diào)函數(shù)的第一個(gè)參數(shù)返回
4、在生命周期中的哪一步你應(yīng)該發(fā)起 AJAX 請(qǐng)求
我們應(yīng)當(dāng)將AJAX 請(qǐng)求放到 componentDidMount 函數(shù)中執(zhí)行主要原因有下
React 下一代調(diào)和算法 Fiber 會(huì)通過(guò)開(kāi)始或停止渲染的方式優(yōu)化應(yīng)用性能其會(huì)影響到 componentWillMount 的觸發(fā)次數(shù)。對(duì)于 componentWillMount 這個(gè)生命周期函數(shù)的調(diào)用次數(shù)會(huì)變得不確定React 可能會(huì)多次頻繁調(diào)用 componentWillMount。如果我們將 AJAX 請(qǐng)求放到 componentWillMount 函數(shù)中那么顯而易見(jiàn)其會(huì)被觸發(fā)多次自然也就不是好的選擇。如果我們將AJAX 請(qǐng)求放置在生命周期的其他函數(shù)中我們并不能保證請(qǐng)求僅在組件掛載完畢后才會(huì)要求響應(yīng)。如果我們的數(shù)據(jù)請(qǐng)求在組件掛載之前就完成并且調(diào)用了setState函數(shù)將數(shù)據(jù)添加到組件狀態(tài)中對(duì)于未掛載的組件則會(huì)報(bào)錯(cuò)。而在 componentDidMount 函數(shù)中進(jìn)行 AJAX 請(qǐng)求則能有效避免這個(gè)問(wèn)題
5、shouldComponentUpdate 的作用
shouldComponentUpdate 允許我們手動(dòng)地判斷是否要進(jìn)行組件更新根據(jù)組件的應(yīng)用場(chǎng)景設(shè)置函數(shù)的合理返回值能夠幫我們避免不必要的更新
6、如何告訴 React 它應(yīng)該編譯生產(chǎn)環(huán)境版
通常情況下我們會(huì)使用 Webpack 的 DefinePlugin 方法來(lái)將 NODE_ENV 變量值設(shè)置為 production。編譯版本中 React會(huì)忽略 propType 驗(yàn)證以及其他的告警信息同時(shí)還會(huì)降低代碼庫(kù)的大小React 使用了 Uglify 插件來(lái)移除生產(chǎn)環(huán)境下不必要的注釋等信息
7、概述下 React 中的事件處理邏輯
為了解決跨瀏覽器兼容性問(wèn)題React 會(huì)將瀏覽器原生事件Browser Native Event封裝為合成事件SyntheticEvent傳入設(shè)置的事件處理器中。這里的合成事件提供了與原生事件相同的接口不過(guò)它們屏蔽了底層瀏覽器的細(xì)節(jié)差異保證了行為的一致性。另外有意思的是React 并沒(méi)有直接將事件附著到子元素上而是以單一事件監(jiān)聽(tīng)器的方式將所有的事件發(fā)送到頂層進(jìn)行處理。這樣 React 在更新 DOM 的時(shí)候就不需要考慮如何去處理附著在 DOM 上的事件監(jiān)聽(tīng)器最終達(dá)到優(yōu)化性能的目的
8、createElement 與 cloneElement 的區(qū)別是什么
createElement 函數(shù)是 JSX 編譯之后使用的創(chuàng)建 React Element的函數(shù)而 cloneElement 則是用于復(fù)制某個(gè)元素并傳入新的 Props
9、redux中間件
中間件提供第三方插件的模式自定義攔截 action -> reducer 的過(guò)程。變?yōu)?action -> middlewares -> reducer。這種機(jī)制可以讓我們改變數(shù)據(jù)流實(shí)現(xiàn)如異步action action 過(guò)濾日志輸出異常報(bào)告等功能
redux-logger提供日志輸出redux-thunk處理異步操作redux-promise處理異步操作actionCreator的返回值是promise
10、redux有什么缺點(diǎn)
一個(gè)組件所需要的數(shù)據(jù)必須由父組件傳過(guò)來(lái)而不能像flux中直接從store取。當(dāng)一個(gè)組件相關(guān)數(shù)據(jù)更新時(shí)即使父組件不需要用到這個(gè)組件父組件還是會(huì)重新render可能會(huì)有效率影響或者需要寫復(fù)雜的shouldComponentUpdate進(jìn)行判斷。
11、react組件的劃分業(yè)務(wù)組件技術(shù)組件
根據(jù)組件的職責(zé)通常把組件分為UI組件和容器組件。UI 組件負(fù)責(zé) UI 的呈現(xiàn)容器組件負(fù)責(zé)管理數(shù)據(jù)和邏輯。兩者通過(guò)React-Redux 提供connect方法聯(lián)系起來(lái)
12、react舊版生命周期函數(shù)
初始化階段
getDefaultProps:獲取實(shí)例的默認(rèn)屬性getInitialState:獲取每個(gè)實(shí)例的初始化狀態(tài)componentWillMount組件即將被裝載、渲染到頁(yè)面上render:組件在這里生成虛擬的DOM節(jié)點(diǎn)componentDidMount:組件真正在被裝載之后
運(yùn)行中狀態(tài)
componentWillReceiveProps:組件將要接收到屬性的時(shí)候調(diào)用shouldComponentUpdate:組件接受到新屬性或者新?tīng)顟B(tài)的時(shí)候可以返回false接收數(shù)據(jù)后不更新阻止render調(diào)用后面的函數(shù)不會(huì)被繼續(xù)執(zhí)行了componentWillUpdate:組件即將更新不能修改屬性和狀態(tài)render:組件重新描繪componentDidUpdate:組件已經(jīng)更新
銷毀階段
componentWillUnmount:組件即將銷毀新版生命周期
在新版本中React 官方對(duì)生命周期有了新的 變動(dòng)建議:
使用getDerivedStateFromProps替換componentWillMount 使用getSnapshotBeforeUpdate替換componentWillUpdate 避免使用componentWillReceiveProps
其實(shí)該變動(dòng)的原因正是由于上述提到的 Fiber。首先從上面我們知道 React 可以分成 reconciliation 與 commit兩個(gè)階段對(duì)應(yīng)的生命周期如下:
reconciliation
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
commit
componentDidMoun
componentDidUpdate
componentWillUnmount
在 Fiber 中reconciliation 階段進(jìn)行了任務(wù)分割涉及到 暫停 和 重啟因此可能會(huì)導(dǎo)致 reconciliation 中的生命周期函數(shù)在一次更新渲染循環(huán)中被 多次調(diào)用 的情況產(chǎn)生一些意外錯(cuò)誤
新版的建議生命周期如下:
class?Component?extends?React.Component?{
??//?替換?`componentWillReceiveProps`?
??//?初始化和?update?時(shí)被調(diào)用
??//?靜態(tài)函數(shù)無(wú)法使用?this
??static?getDerivedStateFromProps(nextProps,?prevState)?{}
??
??//?判斷是否需要更新組件
??//?可以用于組件性能優(yōu)化
??shouldComponentUpdate(nextProps,?nextState)?{}
??
??//?組件被掛載后觸發(fā)
??componentDidMount()?{}
??
??//?替換?componentWillUpdate
??//?可以在更新之前獲取最新?dom?數(shù)據(jù)
??getSnapshotBeforeUpdate()?{}
??
??//?組件更新后調(diào)用
??componentDidUpdate()?{}
??
??//?組件即將銷毀
??componentWillUnmount()?{}
??
??//?組件已銷毀
??componentDidUnMount()?{}
}
使用建議:
在 constructor初始化state在 componentDidMount中進(jìn)行事件監(jiān)聽(tīng)并在componentWillUnmount中解綁事件在 componentDidMount中進(jìn)行數(shù)據(jù)的請(qǐng)求而不是在componentWillMount需要根據(jù) props更新state時(shí)使用getDerivedStateFromProps(nextProps, prevState)舊 props需要自己存儲(chǔ)以便比較
public?static?getDerivedStateFromProps(nextProps,?prevState)?{
?//?當(dāng)新?props?中的?data?發(fā)生變化時(shí)同步更新到?state?上
?if?(nextProps.data?!==?prevState.data)?{
??return?{
???data:?nextProps.data
??}
?}?else?{
??return?null1
?}
}
可以在componentDidUpdate監(jiān)聽(tīng) props 或者 state 的變化例如:
componentDidUpdate(prevProps)?{
?//?當(dāng)?id?發(fā)生變化時(shí)重新獲取數(shù)據(jù)
?if?(this.props.id?!==?prevProps.id)?{
??this.fetchData(this.props.id);
?}
}
在componentDidUpdate使用setState`時(shí)必須加條件否則將進(jìn)入死循環(huán)
shouldComponentUpdate: 默認(rèn)每次調(diào)用setState一定會(huì)最終走到 diff 階段但可以通過(guò)shouldComponentUpdate的生命鉤子返回false來(lái)直接阻止后面的邏輯執(zhí)行通常是用于做條件渲染優(yōu)化渲染的性能。
13、react性能優(yōu)化是哪個(gè)周期函數(shù)
shouldComponentUpdate 這個(gè)方法用來(lái)判斷是否需要調(diào)用render方法重新描繪dom。因?yàn)閐om的描繪非常消耗性能如果我們能在
shouldComponentUpdate方法中能夠?qū)懗龈鼉?yōu)化的dom diff算法可以極大的提高性能
14、為什么虛擬dom會(huì)提高性能
虛擬dom相當(dāng)于在js和真實(shí)dom中間加了一個(gè)緩存利用dom diff算法避免了沒(méi)有必要的dom操作從而提高性能
具體實(shí)現(xiàn)步驟如下
用 JavaScript對(duì)象結(jié)構(gòu)表示 DOM 樹(shù)的結(jié)構(gòu)然后用這個(gè)樹(shù)構(gòu)建一個(gè)真正的DOM樹(shù)插到文檔當(dāng)中當(dāng)狀態(tài)變更的時(shí)候重新構(gòu)造一棵新的對(duì)象樹(shù)。然后用新的樹(shù)和舊的樹(shù)進(jìn)行比較記錄兩棵樹(shù)差異 把2所記錄的差異應(yīng)用到步驟1所構(gòu)建的真正的 DOM樹(shù)上視圖就更新
15、diff算法?
把樹(shù)形結(jié)構(gòu)按照層級(jí)分解只比較同級(jí)元素。 給列表結(jié)構(gòu)的每個(gè)單元添加唯一的key屬性方便比較。 React只會(huì)匹配相同class的component這里面的class指的是組件的名字合并操作調(diào)用 component的setState方法的時(shí)候, React 將其標(biāo)記為 -dirty.到每一個(gè)事件循環(huán)結(jié)束,React檢查所有標(biāo)記dirty的component重新繪制.選擇性子樹(shù)渲染。開(kāi)發(fā)人員可以重寫 shouldComponentUpdate提高diff的性能
16、react性能優(yōu)化方案
重寫 shouldComponentUpdate來(lái)避免不必要的dom操作使用 production版本的react.js使用 key來(lái)幫助React識(shí)別列表中所有子組件的最小變化
17、簡(jiǎn)述flux 思想
Flux 的最大特點(diǎn)就是數(shù)據(jù)的"單向流動(dòng)"。
用戶訪問(wèn)
ViewView發(fā)出用戶的
ActionDispatcher收到Action要求Store進(jìn)行相應(yīng)的更新Store更新后發(fā)出一個(gè)"change"事件View收到"change"事件后更新頁(yè)面
18、說(shuō)說(shuō)你用react有什么坑點(diǎn)
JSX做表達(dá)式判斷時(shí)候需要強(qiáng)轉(zhuǎn)為boolean類型
如果不使用 !!b 進(jìn)行強(qiáng)轉(zhuǎn)數(shù)據(jù)類型會(huì)在頁(yè)面里面輸出 0。
render()?{
??const?b?=?0;
??return?<div>
????{
??????!!b?&&?<div>這是一段文本div>
????}
??div>
}
盡量不要在
componentWillReviceProps里使用setState如果一定要使用那么需要判斷結(jié)束條件不然會(huì)出現(xiàn)無(wú)限重渲染導(dǎo)致頁(yè)面崩潰給組件添加ref時(shí)候盡量不要使用匿名函數(shù)因?yàn)楫?dāng)組件更新的時(shí)候匿名函數(shù)會(huì)被當(dāng)做新的
prop處理讓ref屬性接受到新函數(shù)的時(shí)候react內(nèi)部會(huì)先清空ref也就是會(huì)以null為回調(diào)參數(shù)先執(zhí)行一次ref這個(gè)props然后在以該組件的實(shí)例執(zhí)行一次ref所以用匿名函數(shù)做ref的時(shí)候有的時(shí)候去ref賦值后的屬性會(huì)取到null遍歷子節(jié)點(diǎn)的時(shí)候不要用 index 作為組件的 key 進(jìn)行傳入
19、我現(xiàn)在有一個(gè)button要用react在上面綁定點(diǎn)擊事件要怎么做
class?Demo?{
??render()?{
????return?<button?onClick={(e)?=>?{
??????alert('我點(diǎn)擊了按鈕')
????}}>
??????按鈕
????button>
??}
}
你覺(jué)得你這樣設(shè)置點(diǎn)擊事件會(huì)有什么問(wèn)題嗎
由于onClick使用的是匿名函數(shù)所有每次重渲染的時(shí)候會(huì)把該onClick當(dāng)做一個(gè)新的prop來(lái)處理會(huì)將內(nèi)部緩存的onClick事件進(jìn)行重新賦值所以相對(duì)直接使用函數(shù)來(lái)說(shuō)可能有一點(diǎn)的性能下降
修改
class?Demo?{
??onClick?=?(e)?=>?{
????alert('我點(diǎn)擊了按鈕')
??}
??render()?{
????return?<button?onClick={this.onClick}>
??????按鈕
????button>
??}
20、react 的虛擬dom是怎么實(shí)現(xiàn)的
首先說(shuō)說(shuō)為什么要使用Virturl DOM因?yàn)椴僮髡鎸?shí)DOM的耗費(fèi)的性能代價(jià)太高所以react內(nèi)部使用js實(shí)現(xiàn)了一套dom結(jié)構(gòu)在每次操作在和真實(shí)dom之前使用實(shí)現(xiàn)好的diff算法對(duì)虛擬dom進(jìn)行比較遞歸找出有變化的dom節(jié)點(diǎn)然后對(duì)其進(jìn)行更新操作。為了實(shí)現(xiàn)虛擬DOM我們需要把每一種節(jié)點(diǎn)類型抽象成對(duì)象每一種節(jié)點(diǎn)類型有自己的屬性也就是prop每次進(jìn)行diff的時(shí)候react會(huì)先比較該節(jié)點(diǎn)類型假如節(jié)點(diǎn)類型不一樣那么react會(huì)直接刪除該節(jié)點(diǎn)然后直接創(chuàng)建新的節(jié)點(diǎn)插入到其中假如節(jié)點(diǎn)類型一樣那么會(huì)比較prop是否有更新假如有prop不一樣那么react會(huì)判定該節(jié)點(diǎn)有更新那么重渲染該節(jié)點(diǎn)然后在對(duì)其子節(jié)點(diǎn)進(jìn)行比較一層一層往下直到?jīng)]有子節(jié)點(diǎn)
21、react 的渲染過(guò)程中兄弟節(jié)點(diǎn)之間是怎么處理的也就是key值不一樣的時(shí)候
通常我們輸出節(jié)點(diǎn)的時(shí)候都是map一個(gè)數(shù)組然后返回一個(gè)ReactNode為了方便react內(nèi)部進(jìn)行優(yōu)化我們必須給每一個(gè)reactNode添加key這個(gè)key prop在設(shè)計(jì)值處不是給開(kāi)發(fā)者用的而是給react用的大概的作用就是給每一個(gè)reactNode添加一個(gè)身份標(biāo)識(shí)方便react進(jìn)行識(shí)別在重渲染過(guò)程中如果key一樣若組件屬性有所變化則react只更新組件對(duì)應(yīng)的屬性沒(méi)有變化則不更新如果key不一樣則react先銷毀該組件然后重新創(chuàng)建該組件
23、react-router里的標(biāo)簽和標(biāo)簽有什么區(qū)別
24、connect原理
首先connect之所以會(huì)成功是因?yàn)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Provider組件在原應(yīng)用組件上包裹一層使原來(lái)整個(gè)應(yīng)用成為Provider的子組件接收Redux的store作為props通過(guò)context對(duì)象傳遞給子孫組件上的connectconnect做了些什么。它真正連接 Redux和 React它包在我們的容器組件的外一層它接收上面 Provider 提供的 store 里面的state 和 dispatch傳給一個(gè)構(gòu)造函數(shù)返回一個(gè)對(duì)象以屬性形式傳給我們的容器組件
connect是一個(gè)高階函數(shù)首先傳入mapStateToProps、mapDispatchToProps然后返回一個(gè)生產(chǎn)Component的函數(shù)(wrapWithConnect)然后再將真正的Component作為參數(shù)傳入wrapWithConnect這樣就生產(chǎn)出一個(gè)經(jīng)過(guò)包裹的Connect組件該組件具有如下特點(diǎn)
通過(guò)props.store獲取祖先Component的store props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState作為props傳給真正的Component componentDidMount時(shí)添加事件this.store.subscribe(this.handleChange)實(shí)現(xiàn)頁(yè)面交互shouldComponentUpdate時(shí)判斷是否有避免進(jìn)行渲染提升頁(yè)面性能并得到nextState componentWillUnmount時(shí)移除注冊(cè)的事件this.handleChange
由于connect的源碼過(guò)長(zhǎng)我們只看主要邏輯
export?default?function?connect(mapStateToProps,?mapDispatchToProps,?mergeProps,?options?=?{})?{
??return?function?wrapWithConnect(WrappedComponent)?{
????class?Connect?extends?Component?{
??????constructor(props,?context)?{
????????//?從祖先Component處獲得store
????????this.store?=?props.store?||?context.store
????????this.stateProps?=?computeStateProps(this.store,?props)
????????this.dispatchProps?=?computeDispatchProps(this.store,?props)
????????this.state?=?{?storeState:?null?}
????????//?對(duì)stateProps、dispatchProps、parentProps進(jìn)行合并
????????this.updateState()
??????}
??????shouldComponentUpdate(nextProps,?nextState)?{
????????//?進(jìn)行判斷當(dāng)數(shù)據(jù)發(fā)生改變時(shí)Component重新渲染
????????if?(propsChanged?
????????||?mapStateProducedChange?
????????||?dispatchPropsChanged)?{
??????????this.updateState(nextProps)
????????????return?true
??????????}
????????}
????????componentDidMount()?{
??????????//?改變Component的state
??????????this.store.subscribe(()?=?{
????????????this.setState({
??????????????storeState:?this.store.getState()
????????????})
??????????})
????????}
????????render()?{
??????????//?生成包裹組件Connect
??????????return?(
????????????<WrappedComponent?{...this.nextState}?/>
??????????)
????????}
??????}
??????Connect.contextTypes?=?{
????????store:?storeShape
??????}
??????return?Connect;
????}
??}
25、Redux實(shí)現(xiàn)原理解析
為什么要用redux
在React中數(shù)據(jù)在組件中是單向流動(dòng)的數(shù)據(jù)從一個(gè)方向父組件流向子組件通過(guò)props,所以兩個(gè)非父子組件之間通信就相對(duì)麻煩redux的出現(xiàn)就是為了解決state里面的數(shù)據(jù)問(wèn)題
Redux設(shè)計(jì)理念
Redux是將整個(gè)應(yīng)用狀態(tài)存儲(chǔ)到一個(gè)地方上稱為store,里面保存著一個(gè)狀態(tài)樹(shù)store tree,組件可以派發(fā)(dispatch)行為(action)給store,而不是直接通知其他組件組件內(nèi)部通過(guò)訂閱store中的狀態(tài)state來(lái)刷新自己的視圖

image
Redux三大原則
1.唯一數(shù)據(jù)源
整個(gè)應(yīng)用的state都被存儲(chǔ)到一個(gè)狀態(tài)樹(shù)里面并且這個(gè)狀態(tài)樹(shù)只存在于唯一的store中
2.保持只讀狀態(tài)
state是只讀的唯一改變state的方法就是觸發(fā)actionaction是一個(gè)用于描述以發(fā)生時(shí)間的普通對(duì)象
3.數(shù)據(jù)改變只能通過(guò)純函數(shù)來(lái)執(zhí)行
使用純函數(shù)來(lái)執(zhí)行修改為了描述action如何改變state的你需要編寫reducers
Redux源碼
let?createStore?=?(reducer)?=>?{
????let?state;
????//獲取狀態(tài)對(duì)象
????//存放所有的監(jiān)聽(tīng)函數(shù)
????let?listeners?=?[];
????let?getState?=?()?=>?state;
????//提供一個(gè)方法供外部調(diào)用派發(fā)action
????let?dispath?=?(action)?=>?{
????????//調(diào)用管理員reducer得到新的state
????????state?=?reducer(state,?action);
????????//執(zhí)行所有的監(jiān)聽(tīng)函數(shù)
????????listeners.forEach((l)?=>?l())
????}
????//訂閱狀態(tài)變化事件當(dāng)狀態(tài)改變發(fā)生之后執(zhí)行監(jiān)聽(tīng)函數(shù)
????let?subscribe?=?(listener)?=>?{
????????listeners.push(listener);
????}
????dispath();
????return?{
????????getState,
????????dispath,
????????subscribe
????}
}
let?combineReducers=(renducers)=>{
????//傳入一個(gè)renducers管理組返回的是一個(gè)renducer
????return?function(state={},action={}){
????????let?newState={};
????????for(var?attr?in?renducers){
????????????newState[attr]=renducers[attr](state[attr],action)
????????}
????????return?newState;
????}
}
export?{createStore,combineReducers};
26、pureComponent和FunctionComponent區(qū)別
PureComponent和Component完全相同但是在shouldComponentUpdate實(shí)現(xiàn)中PureComponent使用了props和state的淺比較。主要作用是用來(lái)提高某些特定場(chǎng)景的性能
27 react hooks它帶來(lái)了那些便利
代碼邏輯聚合邏輯復(fù)用 HOC嵌套地獄代替 classReact中通常使用 類定義 或者 函數(shù)定義 創(chuàng)建組件:
在類定義中我們可以使用到許多 React 特性例如 state、 各種組件生命周期鉤子等但是在函數(shù)定義中我們卻無(wú)能為力因此 React 16.8 版本推出了一個(gè)新功能 (React Hooks)通過(guò)它可以更好的在函數(shù)定義組件中使用 React 特性。
好處:
跨組件復(fù)用: 其實(shí) renderprops/HOC也是為了復(fù)用相比于它們Hooks 作為官方的底層API最為輕量而且改造成本小不會(huì)影響原來(lái)的* 組件層次結(jié)構(gòu)和傳說(shuō)中的嵌套地獄類定義更為復(fù)雜 不同的生命周期會(huì)使邏輯變得分散且混亂不易維護(hù)和管理 時(shí)刻需要關(guān)注 this的指向問(wèn)題代碼復(fù)用代價(jià)高高階組件的使用經(jīng)常會(huì)使整個(gè)組件樹(shù)變得臃腫 狀態(tài)與UI隔離: 正是由于 Hooks的特性狀態(tài)邏輯會(huì)變成更小的粒度并且極容易被抽象成一個(gè)自定義Hooks組件中的狀態(tài)和UI變得更為清晰和隔離。
注意:
避免在 循環(huán)/條件判斷/嵌套函數(shù) 中調(diào)用 hooks保證調(diào)用順序的穩(wěn)定只有 函數(shù)定義組件 和 hooks 可以調(diào)用 hooks避免在 類組件 或者 普通函數(shù) 中調(diào)用不能在useEffect中使用useStateReact 會(huì)報(bào)錯(cuò)提示類組件不會(huì)被替換或廢棄不需要強(qiáng)制改造類組件兩種方式能并存重要鉤子
狀態(tài)鉤子 (useState): 用于定義組件的State其到類定義中this.state的功能
//?useState?只接受一個(gè)參數(shù):?初始狀態(tài)
//?返回的是組件名和更改該組件對(duì)應(yīng)的函數(shù)
const?[flag,?setFlag]?=?useState(true);
//?修改狀態(tài)
setFlag(false)
?
//?上面的代碼映射到類定義中:
this.state?=?{
?flag:?true?
}
const?flag?=?this.state.flag
const?setFlag?=?(bool)?=>?{
????this.setState({
????????flag:?bool,
????})
}
生命周期鉤子 (useEffect):
類定義中有許多生命周期函數(shù)而在 React Hooks 中也提供了一個(gè)相應(yīng)的函數(shù) (useEffect)這里可以看做componentDidMount、componentDidUpdate和componentWillUnmount的結(jié)合。
useEffect(callback, [source])接受兩個(gè)參數(shù)callback: 鉤子回調(diào)函數(shù)source: 設(shè)置觸發(fā)條件僅當(dāng)source發(fā)生改變時(shí)才會(huì)觸發(fā)useEffect鉤子在沒(méi)有傳入[source]參數(shù)時(shí)默認(rèn)在每次render時(shí)都會(huì)優(yōu)先調(diào)用上次保存的回調(diào)中返回的函數(shù)后再重新調(diào)用回調(diào)
useEffect(()?=>?{
?//?組件掛載后執(zhí)行事件綁定
?console.log('on')
?addEventListener()
?//?組件?update?時(shí)會(huì)執(zhí)行事件解綁
?return?()?=>?{
??console.log('off')
??removeEventListener()
?}
},?[source]);
//?每次?source?發(fā)生改變時(shí)執(zhí)行結(jié)果(以類定義的生命周期便于大家理解):
//?---?DidMount?---
//?'on'
//?---?DidUpdate?---
//?'off'
//?'on'
//?---?DidUpdate?---
//?'off'
//?'on'
//?---?WillUnmount?---?
//?'off'
通過(guò)第二個(gè)參數(shù)我們便可模擬出幾個(gè)常用的生命周期:
componentDidMount: 傳入[]時(shí)就只會(huì)在初始化時(shí)調(diào)用一次const useMount = (fn) => useEffect(fn, [])componentWillUnmount: 傳入[]回調(diào)中的返回的函數(shù)也只會(huì)被最終執(zhí)行一次const useUnmount = (fn) => useEffect(() => fn, [])mounted: 可以使用 useState 封裝成一個(gè)高度可復(fù)用的mounted狀態(tài)
const?useMounted?=?()?=>?{
????const?[mounted,?setMounted]?=?useState(false);
????useEffect(()?=>?{
????????!mounted?&&?setMounted(true);
????????return?()?=>?setMounted(false);
????},?[]);
????return?mounted;
}
componentDidUpdate:?useEffect每次均會(huì)執(zhí)行其實(shí)就是排除了?DidMount?后即可
const?mounted?=?useMounted()?
useEffect(()?=>?{
????mounted?&&?fn()
})
其它內(nèi)置鉤子:
useContext: 獲取context對(duì)象useReducer: 類似于Redux思想的實(shí)現(xiàn)但其并不足以替代 Redux可以理解成一個(gè)組件內(nèi)部的redux:并不是持久化存儲(chǔ)會(huì)隨著組件被銷毀而銷毀屬于組件內(nèi)部各個(gè)組件是相互隔離的單純用它并無(wú)法共享數(shù)據(jù) 配合 useContext的全局性可以完成一個(gè)輕量級(jí)的Redux(easy-peasy)useCallback: 緩存回調(diào)函數(shù)避免傳入的回調(diào)每次都是新的函數(shù)實(shí)例而導(dǎo)致依賴組件重新渲染具有性能優(yōu)化的效果useMemo: 用于緩存?zhèn)魅氲?props避免依賴的組件每次都重新渲染useRef: 獲取組件的真實(shí)節(jié)點(diǎn)useLayoutEffectDOM更新同步鉤子。用法與useEffect類似只是區(qū)別于執(zhí)行時(shí)間點(diǎn)的不同 useEffect屬于異步執(zhí)行并不會(huì)等待 DOM 真正渲染后執(zhí)行而 useLayoutEffect則會(huì)真正渲染后才觸發(fā)可以獲取更新后的 state自定義鉤子( useXxxxx): 基于Hooks可以引用其它Hooks這個(gè)特性我們可以編寫自定義鉤子如上面的useMounted。又例如我們需要每個(gè)頁(yè)面自定義標(biāo)題:
function?useTitle(title)?{
??useEffect(
????()?=>?{
??????document.title?=?title;
????});
}
//?使用:
function?Home()?{
?const?title?=?'我是首頁(yè)'
?useTitle(title)
?
?return?(
??<div>{title}div>
?)
}
28、React Portal 有哪些使用場(chǎng)景
在以前 react 中所有的組件都會(huì)位于 #app 下而使用 Portals 提供了一種脫離 #app 的組件因此 Portals 適合脫離文檔流(out of flow) 的組件特別是 position: absolute 與 position: fixed的組件。比如模態(tài)框通知警告goTop 等。
以下是官方一個(gè)模態(tài)框的示例可以在以下地址中測(cè)試效果
<html>
??<body>
????<div?id="app">div>
????<div?id="modal">div>
????<div?id="gotop">div>
????<div?id="alert">div>
??body>
html>
const?modalRoot?=?document.getElementById('modal');
class?Modal?extends?React.Component?{
??constructor(props)?{
????super(props);
????this.el?=?document.createElement('div');
??}
??componentDidMount()?{
????modalRoot.appendChild(this.el);
??}
??componentWillUnmount()?{
????modalRoot.removeChild(this.el);
??}
??render()?{
????return?ReactDOM.createPortal(
??????this.props.children,
??????this.el,
????);
??}
}
React Hooks當(dāng)中的useEffect是如何區(qū)分生命周期鉤子的
useEffect可以看成是componentDidMountcomponentDidUpdate和componentWillUnmount三者的結(jié)合。useEffect(callback, [source])接收兩個(gè)參數(shù)調(diào)用方式如下
?useEffect(()?=>?{
???console.log('mounted');
???
???return?()?=>?{
???????console.log('willUnmount');
???}
?},?[source]);
生命周期函數(shù)的調(diào)用主要是通過(guò)第二個(gè)參數(shù)[source]來(lái)進(jìn)行控制有如下幾種情況
[source]參數(shù)不傳時(shí)則每次都會(huì)優(yōu)先調(diào)用上次保存的函數(shù)中返回的那個(gè)函數(shù)然后再調(diào)用外部那個(gè)函數(shù) [source]參數(shù)傳[]時(shí)則外部的函數(shù)只會(huì)在初始化時(shí)調(diào)用一次返回的那個(gè)函數(shù)也只會(huì)最終在組件卸載時(shí)調(diào)用一次 [source]參數(shù)有值時(shí)則只會(huì)監(jiān)聽(tīng)到數(shù)組中的值發(fā)生變化后才優(yōu)先調(diào)用返回的那個(gè)函數(shù)再調(diào)用外部的函數(shù)。
29、react和vue的區(qū)別
相同點(diǎn)
數(shù)據(jù)驅(qū)動(dòng)頁(yè)面提供響應(yīng)式的試圖組件 都有 virtual DOM,組件化的開(kāi)發(fā)通過(guò)props參數(shù)進(jìn)行父子之間組件傳遞數(shù)據(jù)都實(shí)現(xiàn)了webComponents規(guī)范數(shù)據(jù)流動(dòng)單向都支持服務(wù)器的渲染SSR 都有支持 native的方法react有React native vue有wexx
不同點(diǎn)
數(shù)據(jù)綁定 Vue實(shí)現(xiàn)了雙向的數(shù)據(jù)綁定react數(shù)據(jù)流動(dòng)是單向的數(shù)據(jù)渲染大規(guī)模的數(shù)據(jù)渲染 react更快使用場(chǎng)景 React配合Redux架構(gòu)適合大規(guī)模多人協(xié)作復(fù)雜項(xiàng)目Vue適合小快的項(xiàng)目開(kāi)發(fā)風(fēng)格 react推薦做法jsx+inline style把html和css都寫在js了vue是采用webpack+vue-loader單文件組件格式html,js,css同一個(gè)文件
30、什么是高階組件(HOC)
高階組件(Higher Order Componennt)本身其實(shí)不是組件而是一個(gè)函數(shù)這個(gè)函數(shù)接收一個(gè)元組件作為參數(shù)然后返回一個(gè)新的增強(qiáng)組件高階組件的出現(xiàn)本身也是為了邏輯復(fù)用舉個(gè)例子
function?withLoginAuth(WrappedComponent)?{
??return?class?extends?React.Component?{
??????
??????constructor(props)?{
??????????super(props);
??????????this.state?=?{
????????????isLogin:?false
??????????};
??????}
??????
??????async?componentDidMount()?{
??????????const?isLogin?=?await?getLoginStatus();
??????????this.setState({?isLogin?});
??????}
??????
??????render()?{
????????if?(this.state.isLogin)?{
????????????return?<WrappedComponent?{...this.props}?/>;
????????}
????????
????????return?(<div>您還未登錄...div>);
??????}
??}
}
31、React實(shí)現(xiàn)的移動(dòng)應(yīng)用中如果出現(xiàn)卡頓有哪些可以考慮的優(yōu)化方案
增加
shouldComponentUpdate鉤子對(duì)新舊props進(jìn)行比較如果值相同則阻止更新避免不必要的渲染或者使用PureReactComponent替代Component其內(nèi)部已經(jīng)封裝了shouldComponentUpdate的淺比較邏輯對(duì)于列表或其他結(jié)構(gòu)相同的節(jié)點(diǎn)為其中的每一項(xiàng)增加唯一
key屬性以方便React的diff算法中對(duì)該節(jié)點(diǎn)的復(fù)用減少節(jié)點(diǎn)的創(chuàng)建和刪除操作
render函數(shù)中減少類似
onClick={()?=>?{
????doSomething()
}}
的寫法每次調(diào)用render函數(shù)時(shí)均會(huì)創(chuàng)建一個(gè)新的函數(shù)即使內(nèi)容沒(méi)有發(fā)生任何變化也會(huì)導(dǎo)致節(jié)點(diǎn)沒(méi)必要的重渲染建議將函數(shù)保存在組件的成員對(duì)象中這樣只會(huì)創(chuàng)建一次
組件的props如果需要經(jīng)過(guò)一系列運(yùn)算后才能拿到最終結(jié)果則可以考慮使用
reselect庫(kù)對(duì)結(jié)果進(jìn)行緩存如果props值未發(fā)生變化則結(jié)果直接從緩存中拿避免高昂的運(yùn)算代價(jià)webpack-bundle-analyzer分析當(dāng)前頁(yè)面的依賴包是否存在不合理性如果存在找到優(yōu)化點(diǎn)并進(jìn)行優(yōu)化
32、setState
在了解setState之前我們先來(lái)簡(jiǎn)單了解下 React 一個(gè)包裝結(jié)構(gòu): Transaction:
事務(wù) (Transaction)
是 React 中的一個(gè)調(diào)用結(jié)構(gòu)用于包裝一個(gè)方法結(jié)構(gòu)為: initialize - perform(method) - close。通過(guò)事務(wù)可以統(tǒng)一管理一個(gè)方法的開(kāi)始與結(jié)束處于事務(wù)流中表示進(jìn)程正在執(zhí)行一些操作
setState: React 中用于修改狀態(tài)更新視圖。它具有以下特點(diǎn):
異步與同步: setState并不是單純的異步或同步這其實(shí)與調(diào)用時(shí)的環(huán)境相關(guān):
在合成事件 和 生命周期鉤子(除 componentDidUpdate) 中setState是"異步"的
原因: 因?yàn)樵?code>setState的實(shí)現(xiàn)中有一個(gè)判斷: 當(dāng)更新策略正在事務(wù)流的執(zhí)行中時(shí)該組件更新會(huì)被推入dirtyComponents隊(duì)列中等待執(zhí)行否則開(kāi)始執(zhí)行batchedUpdates隊(duì)列更新
在生命周期鉤子調(diào)用中更新策略都處于更新之前組件仍處于事務(wù)流中而componentDidUpdate是在更新之后此時(shí)組件已經(jīng)不在事務(wù)流中了因此則會(huì)同步執(zhí)行
在合成事件中React 是基于 事務(wù)流完成的事件委托機(jī)制 實(shí)現(xiàn)也是處于事務(wù)流中
問(wèn)題: 無(wú)法在setState后馬上從this.state上獲取更新后的值。
解決: 如果需要馬上同步去獲取新值setState其實(shí)是可以傳入第二個(gè)參數(shù)的。setState(updater, callback)在回調(diào)中即可獲取最新值
在 原生事件 和 setTimeout 中setState是同步的可以馬上獲取更新后的值
原因: 原生事件是瀏覽器本身的實(shí)現(xiàn)與事務(wù)流無(wú)關(guān)自然是同步而setTimeout是放置于定時(shí)器線程中延后執(zhí)行此時(shí)事務(wù)流已結(jié)束因此也是同步
批量更新: 在 合成事件 和 生命周期鉤子 中setState更新隊(duì)列時(shí)存儲(chǔ)的是 合并狀態(tài)(Object.assign)。因此前面設(shè)置的 key 值會(huì)被后面所覆蓋最終只會(huì)執(zhí)行一次更新
函數(shù)式: 由于 Fiber 及 合并 的問(wèn)題官方推薦可以傳入 函數(shù) 的形式。setState(fn)在fn中返回新的state對(duì)象即可例如
this.setState((state,?props)?=>?newState)
使用函數(shù)式可以用于避免setState的批量更新的邏輯傳入的函數(shù)將會(huì)被 順序調(diào)用
注意事項(xiàng):
setState合并在 合成事件 和 生命周期鉤子 中多次連續(xù)調(diào)用會(huì)被優(yōu)化為一次當(dāng)組件已被銷毀如果再次調(diào)用 setStateReact會(huì)報(bào)錯(cuò)警告通常有兩種解決辦法將數(shù)據(jù)掛載到外部通過(guò) props 傳入如放到 Redux 或 父級(jí)中 在組件內(nèi)部維護(hù)一個(gè)狀態(tài)量 ( isUnmounted)componentWillUnmount中標(biāo)記為true在setState前進(jìn)行判斷
34、HOC(高階組件)
HOC(Higher Order Componennt) 是在 React 機(jī)制下社區(qū)形成的一種組件模式在很多第三方開(kāi)源庫(kù)中表現(xiàn)強(qiáng)大。
簡(jiǎn)述:
高階組件不是組件是 增強(qiáng)函數(shù)可以輸入一個(gè)元組件返回出一個(gè)新的增強(qiáng)組件 高階組件的主要作用是 代碼復(fù)用操作 狀態(tài)和參數(shù)用法:
屬性代理 (Props Proxy): 返回出一個(gè)組件它基于被包裹組件進(jìn)行 功能增強(qiáng)默認(rèn)參數(shù): 可以為組件包裹一層默認(rèn)參數(shù)
function?proxyHoc(Comp)?{
?return?class?extends?React.Component?{
??render()?{
???const?newProps?=?{
????name:?'tayde',
????age:?1,
???}
???return?<Comp?{...this.props}?{...newProps}?/>
??}
?}
}
提取狀態(tài): 可以通過(guò) props 將被包裹組件中的 state 依賴外層例如用于轉(zhuǎn)換受控組件:
function?withOnChange(Comp)?{
?return?class?extends?React.Component?{
??constructor(props)?{
???super(props)
???this.state?=?{
????name:?'',
???}
??}
??onChangeName?=?()?=>?{
???this.setState({
????name:?'dongdong',
???})
??}
??render()?{
???const?newProps?=?{
????value:?this.state.name,
????onChange:?this.onChangeName,
???}
???return?<Comp?{...this.props}?{...newProps}?/>
??}
?}
}
使用姿勢(shì)如下這樣就能非常快速的將一個(gè) Input 組件轉(zhuǎn)化成受控組件。
const?NameInput?=?props?=>?(<input?name="name"?{...props}?/>)
export?default?withOnChange(NameInput)
包裹組件: 可以為被包裹元素進(jìn)行一層包裝
function?withMask(Comp)?{
??return?class?extends?React.Component?{
???render()?{
???return?(
?????
?????
????????????width:?'100%',
???????height:?'100%',
???????backgroundColor:?'rgba(0,?0,?0,?.6)',
?????}}?
????
???)
???}
??}
}
反向繼承 (Inheritance Inversion): 返回出一個(gè)組件繼承于被包裹組件常用于以下操作
function?IIHoc(Comp)?{
????return?class?extends?Comp?{
????????render()?{
????????????return?super.render();
????????}
????};
}
渲染劫持 (Render Highjacking)
條件渲染: 根據(jù)條件渲染不同的組件
function?withLoading(Comp)?{
??return?class?extends?Comp?{
????render()?{
??????if(this.props.isLoading)?{
??????????return?<Loading?/>
??????}?else?{
??????????return?super.render()
??????}
????}
??};
}
可以直接修改被包裹組件渲染出的 React 元素樹(shù)
操作狀態(tài) (Operate State): 可以直接通過(guò) this.state 獲取到被包裹組件的狀態(tài)并進(jìn)行操作。但這樣的操作容易使 state 變得難以追蹤不易維護(hù)謹(jǐn)慎使用。
應(yīng)用場(chǎng)景:
權(quán)限控制通過(guò)抽象邏輯統(tǒng)一對(duì)頁(yè)面進(jìn)行權(quán)限判斷按不同的條件進(jìn)行頁(yè)面渲染:
function?withAdminAuth(WrappedComponent)?{
??return?class?extends?React.Component?{
???constructor(props){
????super(props)
????this.state?=?{
????????isAdmin:?false,
????}
???}?
???async?componentWillMount()?{
?????const?currentRole?=?await?getCurrentUserRole();
?????this.setState({
?????????isAdmin:?currentRole?===?'Admin',
?????});
???}
???render()?{
?????if?(this.state.isAdmin)?{
???????return?<Comp?{...this.props}?/>;
?????}?else?{
???????return?(<div>您沒(méi)有權(quán)限查看該頁(yè)面請(qǐng)聯(lián)系管理員div>);
?????}
???}
??};
}
性能監(jiān)控包裹組件的生命周期進(jìn)行統(tǒng)一埋點(diǎn):
function?withTiming(Comp)?{
????return?class?extends?Comp?{
????????constructor(props)?{
????????????super(props);
????????????this.start?=?Date.now();
????????????this.end?=?0;
????????}
????????componentDidMount()?{
????????????super.componentDidMount?&&?super.componentDidMount();
????????????this.end?=?Date.now();
????????????console.log(`${WrappedComponent.name}?組件渲染時(shí)間為?${this.end?-?this.start}?ms`);
????????}
????????render()?{
????????????return?super.render();
????????}
????};
}
代碼復(fù)用可以將重復(fù)的邏輯進(jìn)行抽象。
使用注意:
純函數(shù): 增強(qiáng)函數(shù)應(yīng)為純函數(shù)避免侵入修改元組件 避免用法污染: 理想狀態(tài)下應(yīng)透?jìng)髟M件的無(wú)關(guān)參數(shù)與事件盡量保證用法不變 命名空間: 為HOC增加特異性的組件名稱這樣能便于開(kāi)發(fā)調(diào)試和查找問(wèn)題 引用傳遞: 如果需要傳遞元組件的 refs 引用可以使用React.forwardRef 靜態(tài)方法: 元組件上的靜態(tài)方法并無(wú)法被自動(dòng)傳出會(huì)導(dǎo)致業(yè)務(wù)層無(wú)法調(diào)用解決: 函數(shù)導(dǎo)出 靜態(tài)方法賦值 重新渲染: 由于增強(qiáng)函數(shù)每次調(diào)用是返回一個(gè)新組件因此如果在 Render中使用增強(qiáng)函數(shù)就會(huì)導(dǎo)致每次都重新渲染整個(gè)HOC而且之前的狀態(tài)會(huì)丟失
35、React如何進(jìn)行組件/邏輯復(fù)用?
拋開(kāi)已經(jīng)被官方棄用的Mixin,組件抽象的技術(shù)目前有三種比較主流:
高階組件:
屬性代理 反向繼承 渲染屬性 react-hooks
36、你對(duì) Time Slice的理解?
時(shí)間分片
React 在渲染render的時(shí)候不會(huì)阻塞現(xiàn)在的線程 如果你的設(shè)備足夠快你會(huì)感覺(jué)渲染是同步的 如果你設(shè)備非常慢你會(huì)感覺(jué)還算是靈敏的 雖然是異步渲染但是你將會(huì)看到完整的渲染而不是一個(gè)組件一行行的渲染出來(lái) 同樣書(shū)寫組件的方式 也就是說(shuō)這是React背后在做的事情對(duì)于我們開(kāi)發(fā)者來(lái)說(shuō)是透明的具體是什么樣的效果呢
37、setState到底是異步還是同步?
先給出答案: 有時(shí)表現(xiàn)出異步,有時(shí)表現(xiàn)出同步
setState只在合成事件和鉤子函數(shù)中是“異步”的在原生事件和setTimeout 中都是同步的
setState 的“異步”并不是說(shuō)內(nèi)部由異步代碼實(shí)現(xiàn)其實(shí)本身執(zhí)行的過(guò)程和代碼都是同步的只是合成事件和鉤子函數(shù)的調(diào)用順序在更新之前導(dǎo)致在合成事件和鉤子函數(shù)中沒(méi)法立馬拿到更新后的值形成了所謂的“異步”當(dāng)然可以通過(guò)第二個(gè)參數(shù)setState(partialState, callback)中的callback拿到更新后的結(jié)果
setState 的批量更新優(yōu)化也是建立在“異步”合成事件、鉤子函數(shù)之上的在原生事件和setTimeout 中不會(huì)批量更新在“異步”中如果對(duì)同一個(gè)值進(jìn)行多次setState的批量更新策略會(huì)對(duì)其進(jìn)行覆蓋取最后一次的執(zhí)行如果是同時(shí)setState多個(gè)不同的值在更新時(shí)會(huì)對(duì)其進(jìn)行合并批量更新

1. JavaScript 重溫系列(22篇全)
2. ECMAScript 重溫系列(10篇全)
3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全) 4.?正則 / 框架 / 算法等 重溫系列(16篇全) 5.?Webpack4 入門(上)||?Webpack4 入門(下) 6.?MobX 入門(上)?||??MobX 入門(下) 7.?70+篇原創(chuàng)系列匯總 回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看70+篇原創(chuàng)文章

點(diǎn)這,與大家一起分享本文吧~
