【面試題】697- React萬字長文面試題梳理

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

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

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

點這,與大家一起分享本文吧~
