欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React避坑指南之useEffect 依賴引用類型問題分析

 更新時間:2023年03月14日 09:40:47   作者:Wzh小吳  
這篇文章主要介紹了React避坑指南之useEffect 依賴引用類型問題分析,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

前言

如果你是一個入行不久的前端開發(fā),面試中多半會遇到一個問題:

你認(rèn)為使用React要注意些什么?

這個問題意在考察你對React的使用深度,因為沉浸式地寫過一個項目就會發(fā)現(xiàn),不同于一些替你做決定的框架,“潛規(guī)則”豐富的React遠比看上去要難相處。
React中主要有兩類坑點,一種是讓你措手不及,結(jié)果對不上預(yù)期,嚴(yán)重影響開發(fā)進度,另一種更為頭痛,表面風(fēng)平浪靜,水下暗流涌動。
官方文檔的觸角只伸到Demo級別,并不涉及花樣百出的最差實踐,所以下一批開發(fā)者又會掉入相同的陷阱。隱藏的坑點需要開發(fā)者親自下地掃雷,經(jīng)驗主義發(fā)揮了重要作用,尤其是在Hooks使用中。
為了避免更多的心智負(fù)擔(dān),這個系列的文章會介紹一些React使用的常見陷阱,帶你追溯原因和探索解決方案,幫助新手迅速跳過坑點。

問題提出

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>
  );
};

在這個案例中,初始化了三個狀態(tài),和對應(yīng)的三個副作用函數(shù)useEffect,理想狀態(tài)是狀態(tài)的值更新時才觸發(fā)useEffect。
多次點擊Update Count更新State,因為更新后的值還是1,所以第一個useEffect執(zhí)行第一次后不會重復(fù)執(zhí)行,這符合預(yù)期。但是重復(fù)點擊Update Person和Update Array時,卻不是這樣,盡管值相同,但useEffect每一次都會觸發(fā)。當(dāng)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;
}

在上面的代碼中,我們著重關(guān)注areHookInputsEqual的實現(xiàn),這個函數(shù)對比了前后兩次傳入的依賴項,決定了后續(xù)副作用函數(shù)create()是否會執(zhí)行??梢悦黠@看到,useEffect對于依賴項執(zhí)行的是淺比較,即Object.is (arg1, arg2),這可能是出于性能考慮。對于原始類型這沒有問題,但對于引用類型(數(shù)組、對象、函數(shù)等),這意味著即使內(nèi)部的值保持不變,引用本身也會發(fā)生變化,導(dǎo)致 useEffect執(zhí)行副作用。

方案探索

1.飲鴆止渴

縫縫補補只是為了等一個人替你推倒重蓋

最直接的思路是把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]);

2.前置攔截

第二種思路:

在你決定要出手之前,我已經(jīng)幫你決定了 —— 格林公式引申公理

我們可以把問題盡可能前置,手動加一層深對比,如何發(fā)現(xiàn)引用值沒有變化,就不執(zhí)行狀態(tài)更新的邏輯,也就不會觸發(fā)useEffect重復(fù)執(zhí)行。

<button onClick={() => {
    const newPerson = { name: 'Bob', age: 18 };
    if (!isEqual(newPerson, person)) {
      setPerson(newPerson)}
    }
  }
>Update person</button>

但這樣顯然不太優(yōu)雅,且每一次寫setState時心智負(fù)擔(dān)太重,對比邏輯可不可以封裝起來。

3.他山之石

實際上自定義的Hooks就是為了解決方法級別的邏輯復(fù)用,這里我們利用useRef綁定的值可以跨渲染周期的特點,實現(xiàn)一個自定義的useCompare。

const useCompare = (value, compare) => {
  const ref = useRef(null);
  if (!compare(value, ref.current)) {
    ref.current = value;
  }
  return ref.current;
}

經(jīng)過ref記錄的上一次結(jié)果,我們同時擁有了前后兩次更新的狀態(tài),如果發(fā)現(xiàn)值不同,再讓ref綁定新的引用類型地址。

import { isEqual } from 'lodash';
 
const comparePerson = useCompare(person, isEqual);
 
useEffect(() => {
    console.log('Component re-rendered by comparePerson');
}, [comparePerson]);
 
// 重復(fù)執(zhí)行
useEffect(() => {
  console.log('Component re-rendered by person');
}, [person]);

需要注意的是,這里使用了lodash的isEqual函數(shù)實現(xiàn)深對比,看似省心實際是一個成本極其不穩(wěn)定的選擇,如果對象過于龐大,可能得不償失,可以傳入簡化的compare函數(shù),有取舍的比較常變的key值。
而且每次又到單獨調(diào)用useCompare生成新的對象,這里的邏輯也值得被封裝。

4.回歸本質(zhì)

停止曲線救國,直面問題本身。

說了這么多,實際還是useEffect中對比邏輯問題,本著支持拓展但不支持修改的原則,我們需要支持一個新的useEffect支持深度對比。我們將useRef實現(xiàn)的記憶引用傳入useEffect的對比邏輯中:

import { useEffect, useRef } from 'react';
import isEqual from 'lodash.isequal';
 
const useDeepCompareEffect = (callback, dependencies, compare) => {
  // 默認(rèn)的對比函數(shù)采用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 發(fā)生深層級的改變時才會執(zhí)行
    console.log('data 發(fā)生了改變', data);
  }, [data]);
 
  return <div>Hello World</div>;
}

考慮到前文提到的復(fù)雜對象的深對比隱患,我依然結(jié)和個人意志,在useDeepCompareEffect中加了一個可選參數(shù)compare函數(shù),把isEqual作為一種默認(rèn)模式。于是,我們終于有了一勞永逸的方法。

總結(jié)

實際上,react-use和a-hooks等第三方庫都已經(jīng)實現(xiàn)了useDeepCompareEffect,也可以發(fā)現(xiàn)自定義hooks解決問題將會是目前體系下一種復(fù)用性極高的實踐。通過這些方法的推導(dǎo),也可以看出我們獲取方案的思路,希望對新手的成長有所幫助。

到此這篇關(guān)于React避坑指南之useEffect 依賴引用類型問題分析的文章就介紹到這了,更多相關(guān)React useEffect 依賴引用類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React實現(xiàn)單向數(shù)據(jù)流的方法

    React實現(xiàn)單向數(shù)據(jù)流的方法

    本文主要介紹了React實現(xiàn)單向數(shù)據(jù)流的方法
    2023-04-04
  • redux的原理、工作流程及其應(yīng)用方式

    redux的原理、工作流程及其應(yīng)用方式

    這篇文章主要介紹了redux的原理、工作流程及其應(yīng)用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • react中通過props實現(xiàn)父子組件間通信的使用示例

    react中通過props實現(xiàn)父子組件間通信的使用示例

    在React中,父組件可以通過props屬性向子組件傳遞數(shù)據(jù),子組件可以通過props屬性接收父組件傳遞過來的數(shù)據(jù),本文就來介紹一下如何實現(xiàn),感興趣的可以了解一下
    2023-10-10
  • react-router v4如何使用history控制路由跳轉(zhuǎn)詳解

    react-router v4如何使用history控制路由跳轉(zhuǎn)詳解

    這篇文章主要給大家介紹了關(guān)于react-router v4如何使用history控制路由跳轉(zhuǎn)的相關(guān)資料,文中通過示例代碼介紹的的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • 基于PixiJS實現(xiàn)react圖標(biāo)旋轉(zhuǎn)動效

    基于PixiJS實現(xiàn)react圖標(biāo)旋轉(zhuǎn)動效

    PixiJS是一個開源的基于web的渲染系統(tǒng),為游戲、數(shù)據(jù)可視化和其他圖形密集型項目提供了極快的性能,這篇文章主要介紹了用PixiJS實現(xiàn)react圖標(biāo)旋轉(zhuǎn)動效,需要的朋友可以參考下
    2022-05-05
  • React中事件綁定this指向三種方法的實現(xiàn)

    React中事件綁定this指向三種方法的實現(xiàn)

    這篇文章主要介紹了React中事件綁定this指向三種方法的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • React UI組件庫ant-design的介紹與使用

    React UI組件庫ant-design的介紹與使用

    Ant Design是阿里螞蟻金服團隊基于React開發(fā)的ui組件,主要用于中后臺系統(tǒng)的使用,這篇文章主要介紹了React UI組件庫ant-design的介紹與使用,需要的朋友可以參考下
    2023-12-12
  • 基于react組件之間的參數(shù)傳遞(詳解)

    基于react組件之間的參數(shù)傳遞(詳解)

    下面小編就為大家?guī)硪黄趓eact組件之間的參數(shù)傳遞(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • React之虛擬DOM的實現(xiàn)原理

    React之虛擬DOM的實現(xiàn)原理

    這篇文章主要介紹了React之虛擬DOM的實現(xiàn)原理分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • 解決react中useState狀態(tài)異步更新的問題

    解決react中useState狀態(tài)異步更新的問題

    本文主要介紹了react中useState狀態(tài)異步更新的問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評論