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

时间:2019-11-22 11:27:05

标签: reactjs closures react-ref react-functional-component

我正在与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的方式错误?

1 个答案:

答案 0 :(得分:1)

尽管使用引用是有效的解决方案,但您应该使用functional 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;
    })
  );
};

Edit sparkling-haze-e9y22