React useEffect():比较两个对象数组是否相等的最有效方法

4

我有一个useEffect(),当对象数组发生更改时应该触发。

  useEffect(() => {
    setNotesToRender(props.notes)
  }, [props.notes]);

useEffect 依赖项无法比较对象数组,因此上例中的 useEffect() 触发次数比需要的要多。我希望只在数组不同的情况下触发 useEffect()。

触发更新的数组示例:

array1 = [{id:1,title:'one'},{id:2,title:'two'},{id:3,title:'three'}]
array2 = [{id:1,title:'one'},{id:2,title:'two'},{id:3,title:'four'}]

迄今为止我测试过的内容包括:

  1. props.notes.length --> 可以工作,但不可靠——显而易见的原因 :)
  2. ...props.notes --> 如果数组为空则无法工作,并可能导致严重的性能问题?

比较两个对象数组的最有效方式是什么?这些数组可能包含超过1000个对象。

此致敬礼/K

更新 稍后我需要使用文本搜索过滤掉某些“notes”,这就是为什么我使用了React状态。这在我的示例代码中没有显示出来。


https://www.npmjs.com/package/react-fast-compare - Vaibhav Vishal
1
也许您可以将对象数组字符串化并添加为依赖项。我不确定这是否是有效的解决方案。 - Vivek
1
JSON.stringify很快,但如果对象中键的顺序不同,则无法工作。例如{a:1,b:2}将不等于{b:2,a:1}。 - Vaibhav Vishal
1
props.notes 依赖项开始是正确的,然后在 effect 中对每个元素进行更深层次的相等性检查,以调用 setNotesToRender。例如,是否有任何元素属性的差异,还是只有特定的属性?您可能还需要实现 usePrevious react hook,以便可以进行更深层次的值比较,因为 react 使用引用相等性。 - Drew Reese
3个回答

4

什么是最有效的方法

对于这种特定形状的数据,最有效的方式可能是自定义比较。您可以使用JSON.stringify 或其他一些通用比较函数,但由于明显的原因,这不太可能是最有效的。

因此,在您的示例中,一个简单的比较函数可能如下 ->

const array1 = [{id:1,title:'one'},{id:2,title:'two'},{id:3,title:'three'}];
const array2 = [{id:1,title:'one'},{id:2,title:'two'},{id:3,title:'four'}];

const arrayIsEqual = (a1, a2) => 
  a1 === a2 ||
  a1.length === a2.length &&
  a1.every((f,i) => 
    f.id === a2[i].id &&
    f.title === a2[i].title
  )
  
  
//tests
const a1clone = [...array1];

//different arrays not equal
console.log(arrayIsEqual(array1, array2)); //false

//different arrays equal
console.log(arrayIsEqual(array1, a1clone)); //true

//different arrays same values, but different order
a1clone.reverse();
console.log(arrayIsEqual(array1, a1clone)); //false

//same arrays, fastest check
console.log(arrayIsEqual(array1, array1)); //true


1
非常感谢您提供的出色答案和示例!(^__^)/ 我采用了您的方法,效率非常高!非常感谢!/K - Kermit

4

首先,1k个对象并不算多,但回到问题本身:最简单的方法是使用

JSON.stringify(object1) === JSON.stringify(object2);

但在性能成为问题时,它并不是最有效的方式。我能想到的最聪明的方法是使用类似于version的元属性,每当我们以任何方式更新对象时,也增加版本号。

然后,你只需要基于id + version映射检查两个数组即可。

如果不希望使用第二种选项,可以使用lodash.deepCompare或创建自己的函数,并在迭代时逐个检查对象,参见如何使数据输入只读,但显示日历?


2
“meta property like version and whenever we update the object”,这是一种不错的做法,有点类似于数据库处理乐观锁定的方式。如果数据量很大,那么这肯定是最有效的方法。 - Keith

3

没错,useEffect 不能比较对象数组,所以让 useEffect 钩子运行。

但是你可以添加一个 if 语句来判断是否 JSON.stringified notesToRender 和 JSON.stringified props.notes 相等。(或者你可以使用 lodash/isEqual)以防止触发设置状态方法。

然而,如果你认为这仍然不是一个有效的解决方案,我们需要看看为什么你需要首先使用 useEffect

从你的代码中,我想你是基于传入的 props 设置了一个 useState 钩子。

再次思考一下,为什么不考虑直接渲染 props 呢。

从官方 React 文档页面:
https://reactjs.org/docs/thinking-in-react.html#step-3-identify-the-minimal-but-complete-representation-of-ui-state

  1. 它是通过 props 从父组件传递过来的吗?如果是,那么它可能不是状态。
  2. 它在时间上保持不变吗?如果是,那么它可能不是状态。
  3. 你可以基于组件中的任何其他状态或 props 计算它吗?如果可以,则它不是状态。

嗨,Kevin!感谢您的回复!是的,在我的示例代码中不需要状态。我忘了提到后来需要使用文本搜索过滤掉某些“注释”,这就是将它们存储在状态中的原因。/K - Kermit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接