如何解决在 React.memo 中使用 React 功能组件时的闭包问题?
How to solve closures issue when using React functional component with React.memo?
我将 React 功能组件与 React.memo 一起使用,但我遇到了一个问题,在我看来,该问题源于 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 closures) 引用旧版本的数据。
这样做的结果是,当我最初更改第一个 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?
虽然使用引用是一个有效的解决方案,但您应该使用 functional setState
。
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
const onDataChange = id => event => {
event.persist();
setData(data =>
produce(data, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};
我将 React 功能组件与 React.memo 一起使用,但我遇到了一个问题,在我看来,该问题源于 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 closures) 引用旧版本的数据。
这样做的结果是,当我最初更改第一个 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?
虽然使用引用是一个有效的解决方案,但您应该使用 functional setState
。
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
const onDataChange = id => event => {
event.persist();
setData(data =>
produce(data, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};