将数组传递给 useEffect 依赖项列表

IT技术 reactjs react-hooks
2021-04-17 12:26:41

有一些数据来自每 5 秒的长轮询,我希望我的组件在每次数组的一项(或数组长度本身)发生变化时分派一个动作。在将数组作为依赖项传递给 useEffect 时,如何防止 useEffect 进入无限循环,但如果任何值发生变化,仍然设法调度某些操作?

useEffect(() => {
  console.log(outcomes)
}, [outcomes])

其中outcomes是一组 ID,例如[123, 234, 3212]. 数组中的项目可能会被替换或删除,因此数组的总长度可能 - 但不必 - 保持不变,因此outcomes.length作为依赖传递不是这种情况。

outcomes 来自 reselect 的自定义选择器:

const getOutcomes = createSelector(
  someData,
  data => data.map(({ outcomeId }) => outcomeId)
)
5个回答

您可以JSON.stringify(outcomes)作为依赖项列表传递

在这里阅读更多

useEffect(() => {
  console.log(outcomes)
}, [JSON.stringify(outcomes)])
这很完美,但被 eslint 抱怨了
2021-06-13 12:26:41
是的,我想我真的会试一试。阻碍我的是“react-hooks/exhaustive-deps”关于oucomes从 deps 中丢失的警告我知道添加它没有意义,因为我们添加了JSON.stringify(outcomes)但仍然
2021-06-14 12:26:41
@LoiNguyenHuynh:我知道为什么这个答案有效,我认为这是一个很好的答案,但我对 React 团队表示敬意,因为 React 是一个了不起的框架,可以使用……传递数组或对象来构建 Web 应用程序useEffect/useMemo/useCallback 的依赖项真的不断回到我身边,我开始厌倦使用变通办法。有什么办法可以在某个地方讨论改进吗?我不知道从哪里开始。
2021-06-19 12:26:41
这导致 ESLint 警告缺失的依赖项和复杂的表达式 - 两者都在 react-hooks/exhaustive-deps 规则下,我真的不想忽略。也许自定义深度比较钩子可以解决这个问题?我不知道 ESLint 是否会识别自定义钩子。
2021-06-20 12:26:41
我很确定这是 OP 想要的答案。这个想法也不编造的。它来自Dan Abramov(React 核心团队成员)如果有人发现我的答案有问题,请告诉我问题是什么,以便我可以改进该答案以使其更好。
2021-06-21 12:26:41

另一个 ES6 选项是使用模板文字使其成为字符串。类似于JSON.stringify(),除了结果不会被包裹在[]

useEffect(() => {
  console.log(outcomes)
}, [`${outcomes}`])

另一种选择是,如果数组大小不变,则将其分布在:

useEffect(() => {
  console.log(outcomes)
}, [ ...outcomes ])
请注意,如果在数组中混合字符串和数字,则检查可能会失败,因为`${[1,2,3]}``${[1,2,'3']}`. 但是在同一个数组中存储不同类型的值无论如何都是一个坏主意。
2021-06-02 12:26:41

使用JSON.stringify()或 任何深度比较方法可能效率低下,如果您提前知道对象的形状,您可以编写自己的效果钩子,根据自定义相等函数的结果触发回调。

useEffect通过检查依赖项数组中的每个值是否与前一个渲染中的值相同,如果其中一个不是,则执行回调。因此,我们只需要保留我们有兴趣使用的数据实例,useRef并且仅在自定义相等检查返回false以触​​发效果时才分配一个新实例

function arrayEqual(a1: any[], a2: any[]) {
  if (a1.length !== a2.length) return false;
  for (let i = 0; i < a1.length; i++) {
    if (a1[i] !== a2[i]) {
      return false;
    }
  }
  return true;
}

type MaybeCleanUpFn = void | (() => void);

function useNumberArrayEffect(cb: () => MaybeCleanUpFn, deps: number[]) {
  const ref = useRef<number[]>(deps);

  if (!arrayEqual(deps, ref.current)) {
    ref.current = deps;
  }

  useEffect(cb, [ref.current]);
}

用法

function Child({ arr }: { arr: number[] }) {
  useNumberArrayEffect(() => {
    console.log("run effect", JSON.stringify(arr));
  }, arr);

  return <pre>{JSON.stringify(arr)}</pre>;
}

更进一步,我们还可以通过创建一个接受自定义相等函数的效果钩子来重用钩子。

type MaybeCleanUpFn = void | (() => void);
type EqualityFn = (a: DependencyList, b: DependencyList) => boolean;

function useCustomEffect(
  cb: () => MaybeCleanUpFn,
  deps: DependencyList,
  equal?: EqualityFn
) {
  const ref = useRef<DependencyList>(deps);

  if (!equal || !equal(deps, ref.current)) {
    ref.current = deps;
  }

  useEffect(cb, [ref.current]);
}

用法

useCustomEffect(
  () => {
    console.log("run custom effect", JSON.stringify(arr));
  },
  [arr],
  (a, b) => arrayEqual(a[0], b[0])
);

现场演示

编辑 59467758/passing-array-to-useeffect-dependency-list

作为loi-nguyen-huynh 的回答的附录,对于任何遇到eslint Exclusive-deps 警告的人,这可以通过首先将字符串化的 JSON 分解为变量来解决:

const depsString = JSON.stringify(deps);
React.useEffect(() => {
    ...
}, [depsString]);

我建议查看这个 OSS 包,它是为解决您描述的确切问题而创建的(深入比较依赖数组中的值而不是浅层):

https://github.com/kentcdodds/use-deep-compare-effect

用法/API 完全相同,useEffect但会进行深入比较。

但是,我会提醒您不要在不需要它的地方使用它,因为它有可能由于不必要的深度比较而导致性能下降,而浅层比较会这样做。