仅在某些值为true时运行useEffect,但除非依赖项发生更改,否则不要重新运行

时间:2020-07-05 19:55:32

标签: javascript reactjs react-hooks

我发现只有在某些值正确的情况下,人们才想运行useEffect的类似问题,但解决方案始终遵循以下原则:

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

与此相关的问题是,即使isTrue没有变化,如果someFunction从true变为false到true,它将重新运行dep。我只想在someFunction更改的情况下重新运行dep

我想到的唯一方法就是使用引用(例如)

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的方式,因此它们不会导出比较函数。

4 个答案:

答案 0 :(得分:2)

如果我正确理解了您的查询,则需要根据isTrue的值执行someFn()

尽管有许多方法可以使用自定义钩子来比较旧值和新值,并且可以在这里解决您的问题,但我认为有一个更简单的解决方案。

只需从依赖项数组中删除isTrue。这样可以确保useEffect仅在dep更改时运行。如果此时i sTrue == true,则什么也不会执行,否则您的代码将被执行。

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

答案 1 :(得分:1)

如何使用ref作为看门人

i.nextInt(7)

Edit optimistic-wu-cbfd5

答案 2 :(得分:1)

如果我已正确理解,那么您正在寻找的行为是:

  • 如果isReady首次为真,请运行fn()
  • 如果isReady从false变为true:
  1. deps自上次准备就绪以来已更改,请运行fn()
  2. deps自上次准备就绪以来未更改,请勿运行fn()
  • 如果deps更改且isReady为真,请运行fn()

我通过将钩子插入一些UI代码来解决了这个问题,如果不正确,请告诉我,我将对其进行更改/删除。在我看来,isReadydeps没有直接的关系,因为它们之间的关联度不足以处理单个useEffect。他们负责不同的事情,必须分开处理。我会尝试在逻辑上重构更高的东西,以便在可能的情况下,在React渲染周期之外,它们可以更好地协同工作,但我知道并非总是如此。

如果是这样,使用门和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>

答案 3 :(得分:0)

在这种情况下,请尝试针对每个状态划分useEffect,只是基于您的代码的一个主意

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

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