React中immutable的UI組件渲染性能詳解
引言
react 一直遵循UI = fn(state) 的原則,有時候我們的state卻和UI不同步 有時候組件本身在業(yè)務(wù)上不需要渲染,卻又會再一次re-render。之前在項目中遇到的一些問題,這里做一個簡單的分析,大家可以一起交流一下
UI組件渲染性能
react每次觸發(fā)頁面的更新可大致分成兩步:
- render(): 主要是計算v-dom的diff
- commit階段 :將得到的diff v-dom一次性更新到真實DOM
一般我們討論的渲染 指的是第一步, 我可以悄悄的告訴你 第二步我們也管不了,什么時候更新真實DOM, React有一套自己的機(jī)制
組件渲染分為首次渲染和重渲染,首次渲染不可避免就不討論 重渲染指當(dāng)組件state或者props發(fā)生變化的時候造成的后續(xù)渲染過程,也是本文的討論重點
其實React 在更新組件這方面 一直都有一個詬病 就是:
父組件重渲染的時候,會遞歸重渲染所有的子組件
const List = () => { const [name, setName] = useState<string>(""); // 用來測試的其它狀態(tài)值 const [count, setCount] = useState<number>(0); const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; setName(val); }; const handleClick = () => { setCount((c) => c + 1); }; return ( <main> <div className="list"> <input value={name} onChange={handleInputChange} /> <button onClick={handleClick}>測試</button> <Child count={count} /> </div> </main> ); }; const Child: React.FC<any> = (props) => { console.log("Child has render"); return <p>count:{props.count}</p>; };
當(dāng) Input name改變的時候 List觸發(fā)rerender Child會發(fā)生rerender 可是Child 依賴的props只有count而已, 如果所有的子組件都被迫渲染,計算在render花費的時間和資源有可能成為性能瓶頸.
方案一:shallow compare
React其實剛出來就提供了優(yōu)化的手段:
- shouldComponentUpdate: 返回false 就直接跳過組件的render過程
- React.PureComponent: 對props進(jìn)行淺比較,如果相等 則跳過render 用于class 組件
- React.memo: 也是進(jìn)行淺比較,適用于functional Component
本文設(shè)計的組件以functioal component為主 因為后面會涉及到hooks的使用,對上述例子修改:
const Child: React.FC<any> = React.memo((props) => { console.log("Child has render"); return <p>count:{props.count}</p>; })
很好 child沒有跟著name重渲染了,如果props是一個對象呢?
const List = () => { const [name, setName] = useState<string>(""); // 用來測試的其它狀態(tài)值 const [count, setCount] = useState<number>(0); console.log(count) const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; setName(val); }; const handleClick = () => { setCount((c) => c + 1); }; const item: IItem = { text: name, id: 1, }; return ( <main> <div className="list"> <input value={name} onChange={handleInputChange} /> <button onClick={handleClick}>測試</button> <Child item={item} /> </div> </main> ); }; const Child: React.FC<{ count?: number; item: IItem }> = React.memo( ({ item }) => { console.log("Child has render"); return <p>text:{item.text}</p>; } );
改變name時候Child會改變 這是預(yù)期內(nèi)的 而當(dāng)改變count時,Child還是會重渲染,這是什么原因呢?因為count改變后 List組件會rerender 從而導(dǎo)致導(dǎo)致 item這個對象又重新生成了 導(dǎo)致child每次接受的是一個新的object對象 由于每個literal object的比較是引用比較 雖然前后屬性相同,但比較得出的結(jié)果為false,造成 Child rerender 。
淺比較一定要相同引用嗎?不一定,一般的面試中淺比較只是對值的比較 但是React.memo中要求引用類型一定要相同 為什么呢?我猜是出于對性能的考慮,不用深比較也是為了節(jié)約性能 通常情況下 我們想要的UI對應(yīng)的是每個葉子節(jié)點的值 ,即只要葉子節(jié)點的值不發(fā)生變化 就不要rerender
方案二:直接對前后的對象進(jìn)行deepCompare
還好React.memo有第二個參數(shù)可以使用
const Child: React.FC<{ item: IItem }> = React.memo( ({ item }) => { console.log("Child has render"); return <p>text:{item.text}</p>; }, (preProps, nextProps) => { return _.isEqual(preProps, nextProps); // lodash的深比較 } );
保證引用相等的情況下,值也相等 useRef
const item: MutableRefObject<IItem> = React.useRef({ text: name, id: 1, }); <Child item={item.current} />
好家伙,name無論怎么變化 Child 始終不會更新,useRef保證了返回的值是一個MutableObject 不可變的,意思就是引用完全相同 不管值變化 就不會保持更新.導(dǎo)致了UI不一致,那么我們怎么保證 name 不變的時候 item 和上次相等,name 改變的時候才和上次不等。useMemo
const item: IItem = React.useMemo( () => ({ text: name, id: 1, }), [name] // name變化觸發(fā)item不等 name不變item和上次相同 );
總結(jié):
- 父組件重渲染的時候,會遞歸重渲染所有的子組件
- 對primitive 值的數(shù)據(jù) React比較值的相等來判斷是否重渲染組件 對Object數(shù)據(jù) React比較引用 如果引用相同 不會重渲染,如果引用不同 會認(rèn)為是不同對象 造成重渲染
- useRef返回一個MutableRefObject數(shù)據(jù) 永遠(yuǎn)返回的是同一個引用 直到生命周期結(jié)束,官網(wǎng)的注解
useRef
returns a mutable ref object whose .current
property is initialized to the passed argument
(initialValue
). The returned object will persist for the full lifetime of the component.
useMemo 返回一個計算的值 當(dāng)dep改變時 返回的值才改變(引用的改變)
以上就是React中immutable的UI組件渲染性能詳解的詳細(xì)內(nèi)容,更多關(guān)于React immutable UI組件渲染的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于React實現(xiàn)表單數(shù)據(jù)的添加和刪除詳解
這篇文章主要給大家介紹了基于React實現(xiàn)表單數(shù)據(jù)的添加和刪除的方法,文中給出了詳細(xì)的示例供大家參考,相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-03-03react中useState使用:如何實現(xiàn)在當(dāng)前表格直接更改數(shù)據(jù)
這篇文章主要介紹了react中useState的使用:如何實現(xiàn)在當(dāng)前表格直接更改數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08react-native使用leanclound消息推送的方法
這篇文章主要介紹了react-native使用leanclound消息推送的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08react實現(xiàn)pure render時bind(this)隱患需注意!
這篇文章主要為大家詳細(xì)介紹了值得你在react實現(xiàn)pure render的時候,需要注意的bind(this)隱患,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03解決React報錯Rendered more hooks than during
這篇文章主要為大家介紹了React報錯Rendered more hooks than during the previous render解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12react-redux多個組件數(shù)據(jù)共享的方法
這篇文章主要介紹了react-redux多個組件數(shù)據(jù)共享的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08