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