[在React.memo中使用React功能组件时如何解决闭包问题?

问题描述 投票:1回答:1

我正在与React.memo一起使用React功能组件,但是我遇到了一个问题,我认为这是由JavaScript闭包引起的。(我也使用Immer来实现不变性,但我相信它不会影响这种情况。)

下面是情况的简化版本:

import React, { useState } from 'react';
import produce from "immer";

const initialData = [
  {id: 1, value: 0},
  {id: 2, value: 0},
  {id: 3, value: 0}
]

const ChildComponent = memo(
  ({ value, onChange }) => (
    <p>
      <input value={value} onChange={onChange} type="number" />
    </p>
  ),
  (prevProps, nextProps) => prevProps.value === nextProps.value
);

const ParentComponent = props => {
  const [data, setData] = useState(initialData);

  const onDataChange = id => event => {
    setData(
      produce(data, draft => {
        const record = draft.find(entry => entry.id === id);
        record.value = event.target.value;
      })
    );
  };

  return data.map(record => (
    <ChildComponent
      key={record.id}
      value={record.value}
      onChange={onDataChange(record.id)}
    />
  ));
};

我正在使用React.memo来避免不必要地重新渲染其值不变的ChildComponents,但是通过这种方式,ChildComponent存储了旧版本的onChange函数,根据我的理解,这是由于JavaScript闭包而引用了一个旧版本数据的版本。

结果是,当我最初更改第一个ChildComponent的值,然后再更改另一个ChildComponent的值时,第一个ChildComponent的值恢复为初始值。

情况的再现可以在this sandbox中找到。

搜索网络后,我发现此问题的解决方案是在onChange函数中使用ref来获取最新数据,如下所示:

const ParentComponent = props => {
  const [data, setData] = useState(initialData);
  const dataRef = useRef();
  dataRef.current = data;

  const onDataChange = id => event => {
    setData(
      produce(dataRef.current, draft => {
        const record = draft.find(entry => entry.id === id);
        record.value = event.target.value;
      })
    );
  };

  return data.map(record => (
    <ChildComponent
      key={record.id}
      value={record.value}
      onChange={onDataChange(record.id)}
    />
  ));
};

可以找到具有此解决方案的沙箱here

这解决了问题。但是我想问,这是否是正确的解决方案?还是我使用React和React Hooks的方式错误?

reactjs closures react-ref react-functional-component
1个回答
1
投票

尽管使用引用是有效的解决方案,但您应该使用functional setState

如果新状态是使用先前状态计算的,则可以将函数传递给setState。该函数将接收先前的值,并返回更新的值。

setState

const onDataChange = id => event => { event.persist(); setData(data => produce(data, draft => { const record = draft.find(entry => entry.id === id); record.value = event.target.value; }) ); };

© www.soinside.com 2019 - 2024. All rights reserved.