我有一个位于父组件循环内的子组件。当其中一个子组件正在更新父组件的状态时,它会重新渲染所有子组件,因为它是循环的。如何避免每次迭代重新渲染。
function Parent() {
const [selectedChild, setSelectedChild] = useState([]);
const onChangeHandle = (event, id) => {
const checked = event.target.checked;
let updatedArray = [...selectedChild];
if(checked){
if(!selectedChild.includes(id)){
updatedArray.push(id);
}
}
else{
var index = updatedArray.indexOf(id)
if (index !== -1) {
updatedArray.splice(index, 1);
}
}
setSelectedChild(updatedArray);
}
const dummy = (id) => {
return selectedChild.includes(id);
}
return (
<div>
<table>
<tbody>
{[1,2,3].map((value, index) => {
return (
<Child
key={index}
index={index}
value={value}
handle={onChangeHandle}
isSelected={dummy}
/>
)
})}
</tbody>
</table>
<div>
{selectedChild}
</div>
</div>)
}
function Child({index, value, handle, isSelected }) {
console.log('rendering')
return (
<tr>
<td>
<input
type="checkbox"
checked={isSelected(index)}
onChange={(event) => handle(event, index)}/>
</td>
<td>hello {index} {value}</td>
</tr>
)
}
export default function App() {
return (
<div className="App">
<Parent />
</div>
);
}
当前行为:
在上面的代码中,当我单击子组件之一中的复选框时,它正在更新父组件状态(selectedChild
)。所以循环正在执行,所有子项(所有表行)都在重新渲染。
预期行为: 只有该特定行必须重新渲染
答案 0 :(得分:3)
为此,如果道具保持不变,您可以使用 React.memo 来记住您的组件。但是鉴于您的代码,您需要进行一些额外的更改:
你必须应用 useCallback
来记住 onChangeHandle 函数;
要正确记住 onChangeHandle,您需要重构它。你不能直接传递 selectedChild
,否则它会记住它的值。使用 setSelectedChild
作为参数传递一个接受 selectedChild
的函数。
你的孩子应该接收 isSelected
作为布尔值而不是函数。否则 props 将保持不变并且 Child 永远不会更新;
import React, { useState, memo, useCallback } from "react";
function Parent() {
const [selectedChild, setSelectedChild] = useState([]);
const onChangeHandle = useCallback((event, id) => {
setSelectedChild(selectedChild => {
const checked = event.target.checked;
let updatedArray = [...selectedChild];
if (checked) {
if (!selectedChild.includes(id)) {
updatedArray.push(id);
}
} else {
var index = updatedArray.indexOf(id);
if (index !== -1) {
updatedArray.splice(index, 1);
}
}
return updatedArray;
});
}, []);
const dummy = id => {
return selectedChild.includes(id);
};
const renderChildren = () =>
[1, 2, 3].map((value, index) => {
return (
<Child
key={index}
index={index}
value={value}
handle={onChangeHandle}
isSelected={dummy(index)}
/>
);
});
return (
<div>
<table>
<tbody>{renderChildren()}</tbody>
</table>
<div>{selectedChild}</div>
</div>
);
}
const Child = memo(({ index, value, handle, isSelected }) => {
console.log("rendering");
return (
<tr>
<td>
<input
type="checkbox"
checked={isSelected}
onChange={event => handle(event, index)}
/>
</td>
<td>
hello {index} {value}
</td>
</tr>
);
});
export default function App() {
return (
<div className="App">
<Parent />
</div>
);
}
https://stackblitz.com/edit/so-memo-children?file=src/App.js
答案 1 :(得分:1)
基本答案是在 React.memo
上使用 Child
。
const Child = memo(function Child(...) {...})
但是为了使 memo
工作,组件需要接收相同的 props,如果它不应该被重新渲染。这意味着在 useCallback
上使用 onChangeHandle
:
const onChangeHandle = useCallback((event, id) => {...}, [])
但是由于 onChangeHandle
使用的 selectedChild
总是随着复选框的变化而变化,因此您还需要使用 useRef
来引用它:
const selectedChildRef = useRef();
selectedChildRef.current = selectedChild;
并在 onChangeHandle
内使用 reffed 版本。
需要做的最后一件事是将 isSelected
属性从函数更改为一个标志,因为它需要在每次复选框更改时运行:
isSelected={selectedChild.includes(index)}
答案 2 :(得分:0)
您可以在 Child 的定义中实现 shouldComponentUpdate (doc: https://reactjs.org/docs/react-component.html#shouldcomponentupdate) 以更好地控制何时重新渲染。但这仅适用于您遇到性能问题的情况 - 通常您不必担心,让它们全部重新渲染是标准的。