您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“React之useEffect依賴引用類型問題怎么解決”,內容詳細,步驟清晰,細節處理妥當,希望這篇“React之useEffect依賴引用類型問題怎么解決”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
const Issue = function () { const [count, setCount] = useState(0); const [person, setPerson] = useState({ name: 'Alice', age: 15 }); const [array, setArray] = useState([1, 2, 3]); useEffect(() => { console.log('Component re-rendered by count'); }, [count]); useEffect(() => { console.log('Component re-rendered by person'); }, [person]); useEffect(() => { console.log('Component re-rendered by array'); }, [array]); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(1)}>Update Count</button> <button onClick={() => setPerson({ name: 'Bob', age: 30 })}>Update Person</button> <button onClick={() => setArray([1, 2, 3, 4])}>Update Array</button> </div> ); };
在這個案例中,初始化了三個狀態,和對應的三個副作用函數useEffect,理想狀態是狀態的值更新時才觸發useEffect。
多次點擊Update Count更新State,因為更新后的值還是1,所以第一個useEffect執行第一次后不會重復執行,這符合預期。但是重復點擊Update Person和Update Array時,卻不是這樣,盡管值相同,但useEffect每一次都會觸發。當useEffect中的副作用計算量較大時,必然會引起性能問題。
為了追溯這個原因,可以首先熟悉一下useEffect的源碼:
function useEffect(create, deps) { const fiber = get(); const { alternate } = fiber; if (alternate !== null) { const oldProps = alternate.memoizedProps; const [oldDeps, hasSameDeps] = areHookInputsEqual(deps, alternate.memoizedDeps); if (hasSameDeps) { pushEffect(fiber, oldProps, deps); return; } } const newEffect = create(); pushEffect(fiber, newEffect, deps); } function areHookInputsEqual(nextDeps, prevDeps) { if (prevDeps === null) { return false; } for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (Object.is(nextDeps[i], prevDeps[i])) { continue; } return false; } return true; }
在上面的代碼中,我們著重關注areHookInputsEqual
的實現,這個函數對比了前后兩次傳入的依賴項,決定了后續副作用函數create()
是否會執行。可以明顯看到,useEffect對于依賴項執行的是淺比較,即Object.is (arg1, arg2)
,這可能是出于性能考慮。對于原始類型這沒有問題,但對于引用類型(數組、對象、函數等),這意味著即使內部的值保持不變,引用本身也會發生變化,導致 useEffect執行副作用。
縫縫補補只是為了等一個人替你推倒重蓋
最直接的思路是把useEffect的依賴項從引用類型換成基本類型:
useEffect(() => { console.log('Component re-rendered by person'); }, [JSON.stringify(person)]); useEffect(() => { console.log('Component re-rendered by array'); }, [JSON.stringify(array)]);
表面上可行,實際后患無窮(具體參考JSON.stringify為什么不能用來深拷貝),為了避坑而挖另外的坑,顯然不是我們期待的解決方案。
對比之下,這樣的寫法可以容忍,但是person對象如果增加了其他屬性,你要確保自己還記得更新依賴,否則依然是掩蓋問題。
useEffect(() => { console.log('Component re-rendered by person'); }, [person.name, person.age]);
第二種思路:
在你決定要出手之前,我已經幫你決定了 —— 格林公式引申公理
我們可以把問題盡可能前置,手動加一層深對比,如何發現引用值沒有變化,就不執行狀態更新的邏輯,也就不會觸發useEffect重復執行。
<button onClick={() => { const newPerson = { name: 'Bob', age: 18 }; if (!isEqual(newPerson, person)) { setPerson(newPerson)} } } >Update person</button>
但這樣顯然不太優雅,且每一次寫setState時心智負擔太重,對比邏輯可不可以封裝起來。
實際上自定義的Hooks就是為了解決方法級別的邏輯復用,這里我們利用useRef綁定的值可以跨渲染周期的特點,實現一個自定義的useCompare。
const useCompare = (value, compare) => { const ref = useRef(null); if (!compare(value, ref.current)) { ref.current = value; } return ref.current; }
經過ref記錄的上一次結果,我們同時擁有了前后兩次更新的狀態,如果發現值不同,再讓ref綁定新的引用類型地址。
import { isEqual } from 'lodash'; const comparePerson = useCompare(person, isEqual); useEffect(() => { console.log('Component re-rendered by comparePerson'); }, [comparePerson]); // 重復執行 useEffect(() => { console.log('Component re-rendered by person'); }, [person]);
需要注意的是,這里使用了lodash的isEqual函數實現深對比,看似省心實際是一個成本極其不穩定的選擇,如果對象過于龐大,可能得不償失,可以傳入簡化的compare函數,有取舍的比較常變的key值。
而且每次又到單獨調用useCompare生成新的對象,這里的邏輯也值得被封裝。
停止曲線救國,直面問題本身。
說了這么多,實際還是useEffect中對比邏輯問題,本著支持拓展但不支持修改的原則,我們需要支持一個新的useEffect支持深度對比。我們將useRef實現的記憶引用傳入useEffect的對比邏輯中:
import { useEffect, useRef } from 'react'; import isEqual from 'lodash.isequal'; const useDeepCompareEffect = (callback, dependencies, compare) => { // 默認的對比函數采用lodash.isEqual, 支持自定義 if (!compare) compare = isEqual; const memoizedDependencies = useRef([]); if (!compare (memoizedDependencies.current, dependencies)) { memoizedDependencies.current = dependencies; } useEffect(callback, memoizedDependencies.current); }; export default useDeepCompareEffect; function App({ data }) { useDeepCompareEffect(() => { // 這里的代碼只有在 data 發生深層級的改變時才會執行 console.log('data 發生了改變', data); }, [data]); return <div>Hello World</div>; }
考慮到前文提到的復雜對象的深對比隱患,我依然結和個人意志,在useDeepCompareEffect中加了一個可選參數compare函數,把isEqual作為一種默認模式。
讀到這里,這篇“React之useEffect依賴引用類型問題怎么解決”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。