简体   繁体   English

将 React 功能组件与 React.memo 一起使用时如何解决闭包问题?

[英]How to solve closures issue when using React functional component with React.memo?

I am using React functional components alongside React.memo, but I am experiencing an issue, which in my opinion derives from JavaScript closures.我在 React.memo 旁边使用了 React 功能组件,但我遇到了一个问题,在我看来,这源于 JavaScript 闭包。 (I am also using Immer for immutability, but I believe it does not affect the situation.) (我也在使用 Immer 来实现不变性,但我相信它不会影响这种情况。)

Below is a simplified version of the situation:以下是情况的简化版本:

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)}
    />
  ));
};

I am using React.memo to avoid unnecessary rerenders of the ChildComponents whose value didn't change, but this way the ChildComponent is storing an old version of the onChange function, which (in my understanding due to JavaScript closures) references an old version of the data.我正在使用 React.memo 来避免不必要地重新渲染其值没有改变的 ChildComponents,但是通过这种方式 ChildComponent 存储了旧版本的 onChange function,它(在我的理解中由于 JavaScript 闭包)引用了旧版本的数据。

The result of this is that when I change initially the value of the first ChildComponent and then the value of another ChildComponent, the value of the first ChildComponent is restored to the initial value.这样做的结果是,当我最初更改第一个 ChildComponent 的值,然后更改另一个 ChildComponent 的值时,第一个 ChildComponent 的值恢复为初始值。

A reproduction of the situation can be found in this sandbox .这个沙箱中可以找到这种情况的再现。

After searching the web, a solution that I found to this issue is to use a ref inside the onChange function, to get the up-to-date data, like this:在搜索 web 后,我发现解决此问题的方法是在 onChange function 中使用 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)}
    />
  ));
};

A sandbox with this solution can be found here .可以在此处找到具有此解决方案的沙箱。

This solves the issue.这解决了这个问题。 But I wanted to ask, is this a correct solution ?但我想问,这是一个正确的解决方案吗? Or am I using React and React Hooks the wrong way?还是我以错误的方式使用 React 和 React Hooks?

Although using a reference is a valid solution, you should use a functional setState .尽管使用引用是一种有效的解决方案,但您应该使用功能性setState

If the new state is computed using the previous state, you can pass a function to setState.如果使用以前的 state 计算新的 state,则可以将 function 传递给 setState。 The function will receive the previous value, and return an updated value. function 将接收先前的值,并返回更新的值。

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

编辑 sparkling-haze-e9y22

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM