簡體   English   中英

僅當某些值為 true 時才運行 useEffect,但除非依賴關系發生變化,否則不要重新運行

[英]Run useEffect only if certain values are true, but don't re-run unless the dependencies change

我發現了類似的問題,人們希望僅在某些值為真時才運行useEffect ,但解決方案始終遵循以下原則:

useEffect(() => {
  if (!isTrue) {
    return;
  }
  someFunction(dep);
}, [dep, isTrue]);

問題是如果isTrue從真到假再到真,它會重新運行someFunction ,即使dep沒有改變。 僅當dep更改時,我才想重新運行someFunction

我能想到的唯一方法是使用 refs 進行破解,例如:

function useEffectIfReady(fn, deps = [], isReady = true) {
  const ref = useRef({
    forceRerunCount: 0,
    prevDeps: [],
  });

  if (isReady && (ref.current.forceRerunCount === 0
    || deps.length !== ref.current.prevDeps.length
    || !deps.every((d, idx) => Object.is(d, ref.current.prevDeps[idx])))) {
    ref.current.forceRerunCount++;
    ref.current.prevDeps = deps;
  }

  useEffect(() => {
    if (ref.current.forceRerunCount === 0) {
      return;
    }

    return fn();
  }, [ref.current.forceRerunCount]);
});

有沒有更清潔的方法? 我不喜歡自己比較依賴項的想法,因為 React 可能會改變他們比較依賴項的方式,並且 AFAIK 他們不會導出他們的比較 function。

如果我正確理解了您的查詢,您需要根據 isTrue 的值執行someFn()

盡管有一些方法可以使用自定義掛鈎來比較新舊值,並且可以在這里解決您的問題,但我認為有一個更簡單的解決方案。

簡單地說,從依賴數組中刪除isTrue 這將確保您的useEffect僅在dep更改時運行。 如果此時, i sTrue == true則不會執行任何操作,否則您的代碼將被執行。

useEffect(() => {
  if (!isTrue) {
    return;
  }
  someFunction(dep);
}, [dep]);

使用 ref 作為看門人怎么樣

function useEffectIfReady(fn, deps = [], isReady = true) {
  const readyWasToggled = useRef(isReady);

  /* 
  There are 2 states:
    0 - initial 
    1 - ready was toggled
  */
  const getDep = () => {
    if (readyWasToggled.current) {
      return 1;
    }
    if (isReady) {
      readyWasToggled.current = true;
    }
    return 0;
  };

  useEffect(() => {
    if (!isReady) {
      return;
    }
    return fn();
  }, [...deps, fn, getDep()]);
}

編輯樂觀-wu-cbfd5

如果我理解正確,那么您正在尋找的行為是:

  • 如果isReady第一次變為真,則運行fn()
  • 如果isReady從 false 翻轉為 true:
  1. 自上次准備以來, deps發生了變化,運行fn()
  2. deps自上次准備以來沒有改變,不要運行fn()
  • 如果deps發生變化並且isReady為真,則運行fn()

我通過將你的鈎子插入一些 UI 代碼來解決這個問題,如果它不正確,請告訴我,我會更改/刪除它。 在我看來, isReadydeps沒有直接的關系,因為它們的相關性不足以讓單個useEffect處理。 他們負責不同的事情,必須分開處理。 如果可能的話,我會嘗試在邏輯中重構更高層的東西,以便它們在反應渲染周期之外更好地協同工作,但我知道情況並非總是如此。

如果是這樣,使用門和refs來控制它們之間的流動對我來說最有意義。 如果你只將一個dep傳遞給鈎子,它會使事情變得更簡單:

function useGatedEffect(fn, dep, isReady = true) {
  const gate = useRef(isReady);
  const prev = useRef(dep);

  useEffect(() => {
    gate.current = isReady;
    if (gate.current && prev.current !== null) {
      fn(prev.current);
      prev.current = null;
    }
  }, [isReady, fn]);

  useEffect(() => {
    gate.current ? fn(dep) : (prev.current = dep);
  }, [dep, fn]);
}

但是,如果您必須傳入未指定數量的依賴項,那么您將不得不進行自己的相等性檢查。 如果您嘗試將散布對象傳遞給useEffect ,React 會拋出警告,並且無論如何都將它們硬編碼以提高可讀性。 據我所知,這與您的原始鈎子做同樣的事情,但將(簡單)比較邏輯分開,這有望使其更容易重構和維護:

 const { useState, useEffect, useRef, useCallback } = React; function useGatedEffect(fn, deps = [], isReady = true) { const gate = useRef(isReady); const prev = useRef(deps); const [compared, setCompared] = useState(deps); useEffect(() => { depsChanged(compared, deps) && setCompared(deps); }, [compared, deps]); useEffect(() => { gate.current = isReady; if (gate.current && prev.current.length.== 0) { fn(...prev;current). prev;current = [], } }, [isReady; fn]). useEffect(() => { gate?current. fn(..:compared). (prev;current = compared), }, [compared; fn]), function depsChanged(prev. next) { return next,some((item; i) => prev[i],== item); } } function App() { const [saved, setSaved] = useState(""); const [string, setString] = useState("hello"); const [ready. setReady] = useState(false); const callback = useCallback(dep => { console;log(dep), setString(dep); }, []), useGatedEffect(callback; [saved]. ready). return ( <div> <h1>{string}</h1> <input type="text" onChange={e => setSaved(e:target.value)} /> <button onClick={() => setReady(;ready)}> Set Ready. {(,ready).toString()} </button> </div> ); } ReactDOM.render(<App/>, document.getElementById('root'));
 <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root"></div>

嘗試在這種情況下為每個 state 拆分 useEffect,這只是基於您的代碼的想法

useEffect(() => {
  // your codes here
  if (!isTrue) {
    return;
  }
}, [isTrue]);

useEffect(() => {
  // your another set of codes here
  someFunction(dep);
}, [dep])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM