繁体   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