关于reactjs:React使用useState挂钩有效地更新数组中的对象

React efficiently update object in array with useState hook

我有一个React组件,可以渲染一个相当大的输入列表(100个项)。它可以在我的计算机上正常显示,但是手机上的输入延迟明显。 React DevTools显示每次按键时整个父对象都将重新呈现。

有没有更有效的方法来解决这个问题?

https://codepen.io/anon/pen/YMvoyy?editors=0011

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function MyInput({obj, onChange}) {
  return (
   
      <label>
        {obj.label}
        <input type="text" value={obj.value} onChange={onChange} />
      </label>
   
  );
}

// Passed in from a parent component
const startingObjects =
  new Array(100).fill(null).map((_, i) => ({label: i, value: 'value'}));

function App() {
  const [objs, setObjects] = React.useState(startingObjects);
  function handleChange(obj) {
    return (event) => setObjects(objs.map((o) => {
      if (o === obj) return {...obj, value: event.target.value}
      return o;
    }));
  }
  return (
   
      {objs.map((obj) => <MyInput obj={obj} onChange={handleChange(obj)} />)}  
   
  );
}


const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);


该问题与:

1
2
3
4
5
6
function handleChange(obj) {
  return (event) => setObjects(objs.map((o) => {
    if (o === obj) return {...obj, value: event.target.value}
    return o;
  }));
}

在此,您将更新objs数组。显然这很好,但是React不知道发生了什么变化,因此在所有子项上触发了渲染。

如果给定相同的道具,功能组件呈现相同的结果,则可以将其包装在对React.memo的调用中以提高性能。

https://reactjs.org/docs/react-api.html#reactmemo

1
2
3
4
5
6
7
const MyInput = React.memo(({obj, onChange}) => {
    console.log(`Rerendered: ${obj.label}`);
    return
        <label>{obj.label}</label>
        <input type="text" value={obj.value} onChange={onChange} />
    ;
}, (prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);

但是,React.Memo仅在尝试确定是否应渲染时才进行浅表比较,因此我们可以将自定义比较函数作为第二个参数传递。

1
(prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);

基本上来说,如果obj道具上的标签和值与先前obj道具上的先前属性相同,请不要重新渲染。

最后,setObjects非常类似于setState,也是异步的,并且不会立即反映和更新。因此,为避免objs错误和使用旧值的风险,可以将其更改为如下所示的回调:

1
2
3
4
5
6
7
8
9
function handleChange(obj) {
  return (event) => {
    const value = event.target.value;
    setObjects(prevObjs => (prevObjs.map((o) => {
      if (o === obj) return {...obj, value }
      return o;
    })))
  };
}

https://codepen.io/anon/pen/QPBLwy?editors=0011拥有所有这些内容以及console.logs,显示是否重新渲染了某些内容。

Is there a more efficient way to approach this?

您将所有值存储在一个数组中,这意味着您不知道在不迭代整个数组的情况下需要比较哪个元素,就比较对象是否匹配。

如果从对象开始:

1
2
const startingObjects =
  new Array(100).fill(null).reduce((objects, _, index) => ({...objects, [index]: {value: 'value', label: index}}), {})

进行一些修改后,您的handle函数将更改为

1
2
3
4
5
6
function handleChange(obj, index) {
  return (event) => {
    const value = event.target.value;
    setObjects(prevObjs => ({...prevObjs, [index]: {...obj, value}}));
  }
}

https://codepen.io/anon/pen/LvBPEB?editors=0011作为示例。