合作社網(wǎng)站模板search搜索引擎
一. 發(fā)生更新的時機以及順序##

image.png
- props/state改變
- render函數(shù)重新執(zhí)行
- 產(chǎn)生新的VDOM樹
- 新舊DOM樹進(jìn)行diff
- 計算出差異進(jìn)行更新
- 更新到真實的DOM
二. React更新流程##
React將最好的O(n^3)的tree比較算法優(yōu)化為O(n)。
- 同層節(jié)點之間相互比較,不跨節(jié)點。
- 不同類型的節(jié)點,產(chǎn)生不同的樹結(jié)構(gòu):如果該節(jié)點不同,會將舊tree中該節(jié)點的子樹全部刪掉。直接生成新的子樹,掛載到DOM中。
- 開發(fā)中,可以通過key來指定哪些節(jié)點在不同的渲染下保持穩(wěn)定。
三. 不同情況##
情況一:對比不同類型的元素
當(dāng)節(jié)點為不同的元素,React會拆卸原有的樹,并且建立起新的樹。
- 當(dāng)一個元素改變,會觸發(fā)一個完整的重建流程。
- 當(dāng)卸載一棵樹時,對應(yīng)的DOM節(jié)點也會被銷毀,組件實例將執(zhí)行componentWillUnmount()方法。
- 當(dāng)建立一棵新樹時,對應(yīng)得DOM節(jié)點會被創(chuàng)建以及插入到DOM中,組件實例將執(zhí)行componentWillMount()方法,緊接著componentDidMount()方法。
-
子樹銷毀,元素不會復(fù)用。####
情況二:對比同一類型的元素
- 當(dāng)對比兩個相同類型的React元素時,React會保留DOM節(jié)點,僅比對及更新有改變的屬性, 比如下面例子:

image.png
- React知道只需要修改DOM元素上的className屬性。

image.png
-當(dāng) 更新style屬性時,React僅更新有所改變的屬性,沒有變化的屬性不會變。
- 如果是同類型的組件元素:組件會保持不變,React會更新該組件的props,并且調(diào)用componentWillReceiveProps()和componentWillUpdate()方法;
- 下一步,調(diào)用render()方法,diff算法將在之前的結(jié)果以及新的結(jié)果中進(jìn)行遞歸。
情況三:對子節(jié)點進(jìn)行遞歸

image.png
- 默認(rèn)條件下,當(dāng)遞歸DOM節(jié)點的子元素時,React會同時遍歷兩個子元素的列表;當(dāng)產(chǎn)生差異時,生成一個mutation。
- 如上圖,前兩個比較相同,不會有mutation。
- 最后一個比較,產(chǎn)生一個mutation,將其插入到新的DOM樹中即可。
- 當(dāng)然這是理想情況

image.png
如果我們在中間插入一條數(shù)據(jù):
- React會對每一個子元素產(chǎn)生一個mutation,而不是保持其不變。
- 這種方式會有一定的性能問題。
所以這時需要key來優(yōu)化###
四. key優(yōu)化##
- 在尾部添加數(shù)據(jù)
- 有無key意義并不大。
- 在前面插入數(shù)據(jù)
- 這種情況,在沒有key的情況下,所有l(wèi)i都需要進(jìn)行修改。
- 當(dāng)子元素?fù)碛衚ey時,React使用key來匹配原有樹上的子元素以及最新樹上的子元素:這種情況下:原有的元素只是發(fā)生了位移。
render() {return (<div><h2>電影列表</h2><ul>{this.state.movies.map((item,index) => {return <li key={item}>{item}</li>})}</ul><button onClick={e => this.insertMovie()}>添加電影</button></div>)}
- key的注意事項:
- key應(yīng)該是唯一的。
- key不要使用隨機數(shù)(隨機數(shù)在下一次render時,會重新生成一個數(shù)字)。
- 使用index作為key,對性能是沒有優(yōu)化的,id比較合適。
五. 組件嵌套的render調(diào)用##
import React, { Component } from 'react'// Header
function Header() {console.log("Header被調(diào)用");return <h2>我是Header組件</h2>
}// Banner
class Banner extends Component {render() {console.log('Banner的render函數(shù)被調(diào)用');return <h3>我是bannner組件</h3>}
}function ProductList() {console.log("ProductList被調(diào)用");return (<ul><li>商品列表1</li><li>商品列表2</li><li>商品列表3</li><li>商品列表4</li><li>商品列表5</li></ul>)
}
// Main
class Main extends Component {render() {console.log('Main render函數(shù)被調(diào)用');return (<div><Banner /><ProductList /></div >)}
}
// Footer
function Footer() {console.log("Footer被調(diào)用");return <h2>我是Footer組件</h2>
}export default class App extends Component {constructor(props) {super(props);this.state = {counter: 0,}}render() {console.log('App render函數(shù)被調(diào)用');return (<div><h2>當(dāng)前計數(shù):{this.state.counter}</h2><button onClick={e=>this.increment()}>+1</button><Header /><Main /><Footer /></div>)}increment(){this.setState({counter: this.state.counter + 1})}
}
- 調(diào)用一個無關(guān)的函數(shù),界面改變時,按理來說不應(yīng)該讓別的沒有改變的東西重新render。
- 這個例子中我們在前面插入了一個<h2>和<button>標(biāo)簽,現(xiàn)在點擊按鈕時全局都會重新渲染。
- 現(xiàn)在對其進(jìn)行優(yōu)化
六. 組件嵌套的render調(diào)用的優(yōu)化##
- 調(diào)用完setState后,不想render時阻斷其渲染。
- 使用shouldComponentUpdate() {}這個生命函數(shù),默認(rèn)情況下其返回true,也就是重新渲染;手動設(shè)置為false后,將不會重新渲染,但不影響初始化的渲染。
- 我們的目的是:想要阻斷時阻斷(事件發(fā)生后與界面沒有依賴),不想阻斷時渲染,如下代碼。
shouldComponentUpdate(nextProps, nextState){if(this.state.counter !== nextState.counter){return true;}return false;}
以上為簡單情況,當(dāng)組件變多后,情況將很復(fù)雜,函數(shù)/類組件都需要考慮到###
每個類都設(shè)置該生命周期函數(shù)太麻煩。
我們通過繼承PureComponent而不是Component來進(jìn)行簡化,其會對state和props進(jìn)行比較來決定是否重新render。
shouldComponentUpdate在源碼中進(jìn)行更新時,決定是否需要render。
回溯到源碼ReactFiberClassComponent中時,有如下方法:
function checkShouldComponentUpdate(workInProgress,ctor,oldProps,newProps,oldState,newState,nextContext,
) {const instance = workInProgress.stateNode;
// 判斷有無該生命周期函數(shù)if (typeof instance.shouldComponentUpdate === 'function') {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
// -----------------
// 核心代碼const shouldUpdate = instance.shouldComponentUpdate(newProps,newState,nextContext,);stopPhaseTimer();return shouldUpdate;}
// -----------------if (ctor.prototype && ctor.prototype.isPureReactComponent) {return (!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState));}return true;
}
- 該方法最終返回true/false。
- 這個對應(yīng)React中PureComponent的特點
image.png - 其中isPureReactComponent屬性對應(yīng)上面放出來源代碼中對原型中isPureReactComponent屬性的判斷。
- 如果有isPureReactComponent屬性,則對oldProps,oldState和newProps,newState進(jìn)行一個淺層比較。
- 通過淺層比較來判斷是否發(fā)生了改變。
追溯到shallowEqual方法源碼中
import is from './objectIs';const hasOwnProperty = Object.prototype.hasOwnProperty;/*** Performs equality by iterating through keys on an object and returning false* when any key has values which are not strictly equal between the arguments.* Returns true when the values of all keys are strictly equal.*/
function shallowEqual(objA: mixed, objB: mixed): boolean {if (is(objA, objB)) {return true;}if (typeof objA !== 'object' ||objA === null ||typeof objB !== 'object' ||objB === null) {return false;}const keysA = Object.keys(objA);const keysB = Object.keys(objB);if (keysA.length !== keysB.length) {return false;}// Test for A's keys different from B.for (let i = 0; i < keysA.length; i++) {if (!hasOwnProperty.call(objB, keysA[i]) ||!is(objA[keysA[i]], objB[keysA[i]])) {return false;}}return true;
}export default shallowEqual;
- 先判斷兩個對象,相同則true,返回后取反,表示不需要更新。
- 接著分別判斷兩個對象,不是對象或者為null時返回false,強制刷新
- 然后將兩個對象中的keys取出來,若長度不想等則返回false,若相等,則對其中屬性進(jìn)行比較,不相等則返回false進(jìn)行刷新。
這就回到我們案例中,只有App,Header,Footer的render被調(diào)用。
- PureComponent對props和state進(jìn)行shallowEqual 。
- Main,Banner,ProductList沒有依賴任何props/state,所以沒有重新渲染。
- 開發(fā)中只需要shallowEqual,深層比較非常浪費性能。
- PureComponent可以解決類組件的render調(diào)用,但解決不了函數(shù)式組件
七. memo的使用,優(yōu)化函數(shù)式組件##
memo為高階組件。
const MemoHeader = memo(function Header() {console.log("Header被調(diào)用");return <h2>我是Header組件</h2>
})

image.png
- 我們將原來的函數(shù)組件傳入memo函數(shù)中,生成一個新的組件類型。
- 將Footer也進(jìn)行轉(zhuǎn)換,這樣只有App重新渲染了,
- 但我們沒有更改ProductList,其也沒有重新渲染,原因是在Main中,重新渲染已經(jīng)被阻止了。
- 為了以防萬一,也可以用memo優(yōu)化。
理論上:建議所有類組件都用PureComponent,所有函數(shù)組件都包裹memo
? 著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

喜歡的朋友記得點贊、收藏、關(guān)注哦!!!