这是我之前提出的问题的更简洁版本。希望它得到更好的解释和更容易理解。
这是一个小应用程序,它有 3 个需要数字的输入(请忽略您也可以输入非数字,这不是重点)。它计算所有显示数字的总和。如果您将其中一个输入更改为另一个数字,则总和将更新。
这是它的代码:
import { useCallback, useEffect, useState } from 'react';
function App() {
const [items, setItems] = useState([
{ index: 0, value: "1" },
{ index: 1, value: "2" },
{ index: 2, value: "3" },
]);
const callback = useCallback((item) => {
let newItems = [...items];
newItems[item.index] = item;
setItems(newItems);
}, [items]);
return (
<div>
<SumItems items={items} />
<ul>
{items.map((item) =>
<ListItem key={item.index} item={item} callback={callback} />
)}
</ul>
</div>
);
}
function ListItem(props) {
const [item, setItem] = useState(props.item);
useEffect(() => {
console.log("ListItem ", item.index, " mounting");
})
useEffect(() => {
return () => console.log("ListItem ", item.index, " unmounting");
});
useEffect(() => {
console.log("ListItem ", item.index, " updated");
}, [item]);
const onInputChange = (event) => {
const newItem = { ...item, value: event.target.value };
setItem(newItem);
props.callback(newItem);
}
return (
<div>
<input type="text" value={item.value} onChange={onInputChange} />
</div>);
};
function SumItems(props) {
return (
<div>Sum : {props.items.reduce((total, item) => total + parseInt(item.value), 0)}</div>
)
}
export default App;
这是启动时的控制台输出,在将第二个输入 2 更改为 4 之后:
ListItem 0 mounting App.js:35
ListItem 0 updated App.js:43
ListItem 1 mounting App.js:35
ListItem 1 updated App.js:43
ListItem 2 mounting App.js:35
ListItem 2 updated App.js:43
ListItem 0 unmounting react_devtools_backend.js:4049:25
ListItem 1 unmounting react_devtools_backend.js:4049:25
ListItem 2 unmounting react_devtools_backend.js:4049:25
ListItem 0 mounting react_devtools_backend.js:4049:25
ListItem 1 mounting react_devtools_backend.js:4049:25
ListItem 1 updated react_devtools_backend.js:4049:25
ListItem 2 mounting react_devtools_backend.js:4049:25
如您所见,当更新单个输入时,所有子项都不会重新渲染,它们首先被卸载,然后重新安装。太浪费了,所有的输入都已经是正确的状态,只需要更新总和。想象一下有数百个这样的输入。
如果只是重新渲染的问题,我可以看看 memoization。但这行不通,因为callback
正是因为items
变化而更新。不,我的问题是关于所有孩子的下马。
问题 1:可以避免卸载吗?
如果我相信Kent C. Dodds 的这篇文章,答案是否定的(强调我的):
React 的 key prop 让你能够控制组件实例。每次 React 渲染你的组件时,它都会调用你的函数来检索它用来更新 DOM 的新 React 元素。如果您返回相同的元素类型,它会保留这些组件/DOM 节点,即使所有 * 道具都已更改。
(...)
唯一的例外是 key prop。这允许您返回完全相同的元素类型,但强制 React 卸载前一个实例,并安装一个新实例。这意味着当时组件中存在的所有状态都将被完全删除,并且该组件已“重新初始化”以用于所有意图和目的。
问题 2:如果这是真的,那么我应该考虑什么设计来避免因为每个输入组件中发生异步处理而在我的真实应用程序中看起来不必要并导致问题?