将数组传递给 useEffect 依赖列表

use*_*101 27 reactjs react-hooks

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

useEffect(() => {
  console.log(outcomes)
}, [outcomes])
Run Code Online (Sandbox Code Playgroud)

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

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

const getOutcomes = createSelector(
  someData,
  data => data.map(({ outcomeId }) => outcomeId)
)
Run Code Online (Sandbox Code Playgroud)

Huỳ*_*yễn 53

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

在这里阅读更多

useEffect(() => {
  console.log(outcomes)
}, [JSON.stringify(outcomes)])
Run Code Online (Sandbox Code Playgroud)

  • 我很确定这就是OP想要的答案。这个想法也**不是我杜撰的**。它来自 [Dan Abramov(React 核心团队成员)](https://github.com/facebook/react/issues/14476#issuecomment-471199055)。如果有人发现我的答案有问题,请告诉我问题是什么,以便我可以改进该答案以使其更好。 (11认同)
  • @LoiNguyenHuynh:我知道为什么这个答案有效,我认为这是一个很好的答案,但我对 React 团队充满敬意,因为 React 是一个令人惊叹的框架,可以用来构建 Web 应用程序......传递数组或对象useEffect/useMemo/useCallback 的依赖项确实不断地回到我身边,我开始厌倦使用解决方法。我们有什么办法可以在某个地方讨论改进吗?我不知道从哪里开始。 (11认同)
  • 这会导致 ESLint 警告缺少的依赖项和复杂的表达式 - 两者都在 react-hooks/exhaustive-deps 规则下,我真的不想忽略。也许自定义的深度比较钩子可以解决这个问题?我不知道 ESLint 是否会识别自定义钩子。 (11认同)
  • 我们不应该使用 .sort() 然后 stringify 吗?如果项目发生变化但长度保持不变怎么办? (3认同)
  • 使用此方法时,我是唯一收到 ESLint 警告“React Hook useEffect 缺少依赖项:结果”的人吗?解决方法是 `const str = JSON.stringify(outcomes); useEffect(() => { const results = JSON.parse(str); console.log(outcomes)}, [str])` 但它有点冗长。 (3认同)
  • 这不应该是答案。正如其他评论中指出的,它没有修复 eslint 警告。React 团队多次表示,如果您需要禁用警告,那么您就做错了。 (3认同)
  • 是的,我想我真的会尝试一下。阻碍我的是“react-hooks/exhaustive-deps”关于 deps 中缺少“oucomes”的警告。我知道添加它是没有意义的,因为我们添加了 ```JSON.stringify(outcomes)``` 但仍然 (2认同)
  • 我认为我的问题仍然存在:字符串化可以具有相同的项目,但顺序不同,并且仍然会触发 useEffect,对吗? (2认同)

Nea*_*arl 19

使用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]);
}
Run Code Online (Sandbox Code Playgroud)

用法

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

  return <pre>{JSON.stringify(arr)}</pre>;
}
Run Code Online (Sandbox Code Playgroud)

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

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]);
}
Run Code Online (Sandbox Code Playgroud)

用法

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

现场演示

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

  • 感谢您提供详尽的代码和解释@NearHuscarl,但我忍不住感到厌恶并拒绝为应该开箱即用的东西编写这么多代码。AFAIC 我将 JSON.stringify 它并消除警告。 (5认同)
  • 这个答案是错误的,“useRef().current”不能在依赖数组中使用:https://medium.com/welldone-software/usecallback-might-be-what-you-meant-by-useref -useeffect-773bc0278ae (4认同)
  • 这是解决问题的正确方法(使用`useRef`)。 (3认同)

iPz*_*ard 5

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

useEffect(() => {
  console.log(outcomes)
}, [`${outcomes}`])
Run Code Online (Sandbox Code Playgroud)

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

useEffect(() => {
  console.log(outcomes)
}, [ ...outcomes ])
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果在数组中混合字符串和数字,检查可能会失败,因为“\`${[1,2,3]}\”与“\`${[1,2,'3”相同']}\``。但无论如何,在同一个数组中存储不同类型的值都是一个坏主意。 (5认同)
  • 不幸的是,扩展向我返回了一个警告:`React Hook useEffect 在其依赖项数组中有一个扩展元素。这意味着我们无法静态验证您是否传递了正确的依赖项` (3认同)