【问题标题】:React efficiently update object in array with useState hook使用 useState 挂钩有效地更新数组中的对象
【发布时间】:2019-04-21 23:45:13
【问题描述】:

我有一个 React 组件,它呈现一个中等大小的输入列表(100 多个项目)。它在我的计算机上呈现正常,但在我的手机上存在明显的输入延迟。 React DevTools 显示整个父对象在每次按键时都会重新渲染。

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

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

function MyInput({obj, onChange}) {
  return (
    <div>
      <label>
        {obj.label}
        <input type="text" value={obj.value} onChange={onChange} />
      </label>
    </div>
  );
}

// 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 (
    <div>
      {objs.map((obj) => <MyInput obj={obj} onChange={handleChange(obj)} />)}  
    </div>
  );
}


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

【问题讨论】:

  • 看起来你是在调用你的函数而不是传递它。 onChange={handleChange(obj)} 应该是 onChange={() =&gt; handleChange(obj)}
  • &lt;MyInput /&gt; 的密钥也丢失了
  • @DCTID handleChange 正在对事件函数进行柯里化。所以在调用的时候,会返回一个函数来处理事件函数。

标签: reactjs react-hooks


【解决方案1】:

问题与:

function handleChange(obj) {
  return (event) => setObjects(objs.map((o) => {
    if (o === obj) return {...obj, value: event.target.value}
    return o;
  }));
}

在此,您将更新objs 数组。这显然没问题,但是 React 不知道发生了什么变化,所以在所有的 Children 上触发了 Render。

如果你的函数组件在给定相同的 props 的情况下呈现相同的结果,你可以将它包装在对 React.memo 的调用中以提高性能。

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

const MyInput = React.memo(({obj, onChange}) => {
    console.log(`Rerendered: ${obj.label}`);
    return <div style={{display: 'flex'}}>
        <label>{obj.label}</label>
        <input type="text" value={obj.value} onChange={onChange} />
    </div>;
}, (prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);

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

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

基本上说,如果obj 属性上的标签和值与之前obj 属性上的属性相同,则不要重新渲染。

最后,setObjects 很像 setState,也是异步的,不会立即反映和更新。因此,为了避免 objs 不正确的风险,并使用旧值,您可以将其更改为回调,如下所示:

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,显示是否重新渲染。


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

您将所有值存储在一个数组中,这意味着您不知道需要更新哪个元素而不遍历整个数组,比较对象是否匹配。

如果你从一个对象开始:

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

经过一些修改,你的句柄函数会变成

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 为例。

【讨论】:

  • 我刚刚完成测试以发布几乎相同的答案。好答案!
  • 这个答案太棒了。备忘录终于有意义了。我仍然无法让它工作。比较函数没有被调用。我将尝试获取 CodePen,但需要注意的是,我仍在使用具有如下更新功能的数组:setObjects(prev =&gt; prev.fill({...prev[i], value},i,i+1)) 在获得新值后将特定索引处的项目替换为自身。
  • 这是一个不起作用的版本。 codepen.io/anon/pen/YMmVaB?editors=0011 真正的关键是它可以在 react 16.7.0-alpha 中工作,但我更新到 16.8 并且没有骰子。这是否意味着它的反应错误?
  • updateObjInArr 正在改变prev 状态。所以 React 认为状态永远不会改变,因为你不小心更新了之前的状态以匹配新的状态。把它改成arr.slice().fill(......就可以了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-06
  • 1970-01-01
  • 2021-10-09
  • 1970-01-01
相关资源
最近更新 更多