React避免子組件無(wú)效刷新的三種解決方案
前言
一個(gè)很常見的場(chǎng)景,React
中父組件和子組件在一起,子組件不依賴于父組件任何數(shù)據(jù),但是會(huì)一起發(fā)生變化。
在探究原理之前,先回憶一下,React
中的Diff算法會(huì)將更新前后的兩棵虛擬DOM樹做對(duì)比,但這并不會(huì)決定組件是否更新,只會(huì)決定是否要復(fù)用老的節(jié)點(diǎn)。
舉個(gè)簡(jiǎn)單的例子:
import { useState } from 'react'; const Child = () => { console.log('child render'); return null; }; const App = () => { const [name, setName] = useState(1); return ( <div onClick={() => setName(2)}> <Child /> </div> ); };
Child
組件沒有接收來(lái)自父組件的值,每次點(diǎn)擊父組件元素讓name
更新,Child
組件會(huì)更新嗎?答案是會(huì)的,你一定會(huì)好奇,子組件沒有接收任何的props
,為什么也會(huì)更新呢?
首先,父組件經(jīng)過(guò)了Diff階段,會(huì)判斷Child組件是否發(fā)生變化,在本案例中Child
內(nèi)部的元素結(jié)構(gòu)和狀態(tài)無(wú)任何變化,React
還會(huì)對(duì)比Child
組件前后的props是否相同,在本案例中,前后props
不相同。
說(shuō)到這里,你一定忍不住了,我都沒傳props
,為啥不相同?原因是React
內(nèi)部對(duì)于props
的對(duì)比只進(jìn)行了淺層比較,通過(guò) !== 來(lái)判斷,這樣即使沒傳props
,每次生成的props
對(duì)象都是新的指針,即使為空,也會(huì)生成不同的props
空對(duì)象,就像這樣:
const oldProps = current.memoizedProps; // 更新前老的props const newProps = workInProgress.pendingProps; // 待比較更新后的props if (oldProps !== newProps) { didReceiveUpdate = true; // 標(biāo)記為發(fā)生變化,需要更新 }
那有什么方法可以避免這樣的無(wú)效更新呢?一共有三種方案。
- 使用
React.memo
,可以指定在Diff時(shí)對(duì)于被memo
包裹的組件只做淺層比較; - 使用
React.useMemo
或React.useCallback
來(lái)包住子組件,讓每次更新子組件都為同一個(gè)JSX對(duì)象,這也props
的比較就會(huì)相同; - 將子組件作為
children
來(lái)傳遞;
React.memo
對(duì)于方案1,React.memo
的原理其實(shí)來(lái)源于源碼中的shallowEqual
函數(shù),該函數(shù)會(huì)接收兩個(gè)對(duì)象,分別對(duì)應(yīng)老的props
和新的props
,一共有四種比較策略,如果四種策略都通過(guò),則判定新舊為同一個(gè)對(duì)象,不做更新,復(fù)用老的節(jié)點(diǎn)。
- 判斷兩者是否為同一對(duì)象,不是同一對(duì)象則返回false;
- 判斷兩者的值不為
object
或?yàn)?code>null,則返回false; - 對(duì)比兩者key的數(shù)量,不一致則返回false;
- 對(duì)比兩者key的值是否相同,不一致則返回false;
源碼如下:
function shallowEqual(objA: mixed, objB: mixed): boolean { // 一樣的對(duì)象返回true if (Object.is(objA, objB)) { return true; } // 不是對(duì)象或者為null返回false if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); // key數(shù)量不同返回false if (keysA.length !== keysB.length) { return false; } // 對(duì)應(yīng)key的值不相同返回false for (let i = 0; i < keysA.length; i++) { if (!hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; }
可以看到淺比較props
的實(shí)現(xiàn)原理很簡(jiǎn)單,對(duì)應(yīng)著上述四種策略。
React.useMemo & React.useCallBack
對(duì)于方案2,如果你不了解React.useMemo
和React.useCallback
,沒有關(guān)系,先看一下這段代碼塊:
import { useMemo } from 'react'; const Child = () => { console.log('child render'); return null; }; const App = () => { const [name, setName] = useState(1); const child = useMemo(() => <Child />, []); return <div onClick={() => setName(2)}>{child}</div>; };
React.useMemo
接收兩個(gè)參數(shù),第一個(gè)參數(shù)為返回值,第二個(gè)參數(shù)為依賴項(xiàng),當(dāng)依賴項(xiàng)數(shù)組中的值發(fā)生變化,則返回值會(huì)重新計(jì)算,也就是說(shuō)第二個(gè)依賴項(xiàng)傳空數(shù)組,則依賴項(xiàng)永遠(yuǎn)都不會(huì)發(fā)生變化,則Child
組件經(jīng)過(guò)React.useMemo
包裹后一直不會(huì)被React
去計(jì)算Diff,就實(shí)現(xiàn)了父組件更新,子組件不觸發(fā)更新。
但對(duì)于React.useMemo
的使用,如果傳給了子組件的值,但是未聲明依賴項(xiàng),會(huì)導(dǎo)致子組件一直不發(fā)生變化,就像這樣:
import { useMemo } from 'react'; const Child = ({ name }) => { console.log('child render'); return name; }; const App = () => { const [name, setName] = useState(1); const child = useMemo(() => <Child name={name} />, []); return <div onClick={() => setName(2)}>{child}</div>; };
像這種情況,父組件將name
傳給了子組件,但是由于子組件未聲明name
為改變依賴項(xiàng),因此當(dāng)name
發(fā)生變化,子組件依然會(huì)永遠(yuǎn)返回初始值1,因此對(duì)于React.useMemo
的緩存策略在優(yōu)化時(shí)也需要充分考慮意外事故發(fā)生。
向上提煉 & 向下移動(dòng)
對(duì)于方案3,可以簡(jiǎn)單理解成向上提煉和向下移動(dòng)state,先看一個(gè)案例:
const App = () => { const [color, setColor] = useState('red'); return ( <div> <input value={color} onChange={(e) => setColor(e.target.value)} /> <p style={{ color }}>Hello, world!</p> <Child /> </div> ); };
Input
的onChange
事件是一個(gè)頻繁觸發(fā)的顏色指示器,一秒會(huì)觸發(fā)上百次,而Child
組件是一個(gè)固定渲染不依賴父組件狀態(tài)的子組件,如何通過(guò)狀態(tài)向下移動(dòng)的方式來(lái)避免Child
組件被渲染呢?
const App = () => { return ( <div> <Form /> <Child /> </div> ); };
我們只需要將這段性能消耗大的代碼抽離到單獨(dú)的一個(gè)Form
組件中,同時(shí)把color
狀態(tài)單獨(dú)交給Form
組件去管理,這樣App
父組件一直沒有發(fā)生重渲染,Child
子組件也不會(huì)被影響,只有Form
子組件在單獨(dú)發(fā)生交互,這種方案更像是一個(gè)狀態(tài)下移 + 隔離
。
還有一種解法就是狀態(tài)提升,我們可以把這段性能消耗嚴(yán)重的代碼同樣單獨(dú)封裝成一個(gè)組件,將Child
子組件的內(nèi)容傳遞給Form子組件,就像這樣:
const Form = ({ children }) => { const [color, setColor] = useState('red'); return ( <div style={{ color }}> <input value={color} onChange={(e) => setColor(e.target.value)} /> {children} </div> ); }; const App = () => { return ( <div> <Form> <p>Hello, world</p> <Child /> </Form> </div> ); };
其實(shí)思路是和狀態(tài)向下提升是一樣的,把性能消耗嚴(yán)重的一部分單獨(dú)抽離到一個(gè)組件中,將相對(duì)不期望被影響的一部分通過(guò)特定形式渲染,因此Child
子組件在這種情況也不會(huì)被重新渲染。
結(jié)尾
本文主要記錄了博主在日常開發(fā)用到比較多的三種優(yōu)化策略,微笑的細(xì)節(jié)差帶來(lái)的優(yōu)化提升手段,希望對(duì)你有幫助哦~
以上就是React避免子組件無(wú)效刷新的三種解決方案的詳細(xì)內(nèi)容,更多關(guān)于React避免子組件無(wú)效刷新的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React中useState值為對(duì)象時(shí)改變值不渲染問題
這篇文章主要介紹了React中useState值為對(duì)象時(shí)改變值不渲染問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02react最流行的生態(tài)替代antdpro搭建輕量級(jí)后臺(tái)管理
這篇文章主要為大家介紹了react最流行的生態(tài)替代antdpro搭建輕量級(jí)后臺(tái)管理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08react-native 配置@符號(hào)絕對(duì)路徑配置和絕對(duì)路徑?jīng)]有提示的問題
本文主要介紹了react-native 配置@符號(hào)絕對(duì)路徑配置和絕對(duì)路徑?jīng)]有提示的問題,文中通過(guò)圖文示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01React 使用Hooks簡(jiǎn)化受控組件的狀態(tài)綁定
這篇文章主要介紹了React 使用Hooks簡(jiǎn)化受控組件的狀態(tài)綁定,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03