为什么我的 React 子组件会重新渲染
Why is my React subcomponent re-rendering
我有这个应用程序,我在页面加载时提取大量数据,然后使用 react-tables 呈现 table。每行都有一个合规性列,其中包含有关是否合规的状态标签。此列还包含一个按钮,按下该按钮会使用推荐的合规值更新状态 (queuedPortRecommendations
)。
table 需要很长时间才能呈现,正如预期的那样,它大约有 1400 行。但是我不明白为什么它会在 addQueuedChange
函数被调用时重新渲染?
我尝试从 useMemo
的 deps 数组中删除 addQueuedChange
,但随后并没有向数组中添加字符串,而是将其替换。
我知道很难想出一个直接的答案,但如果有任何提示或想法,我将不胜感激。
App.tsx
const App = () => {
const [data, setData] = useState<Port[]>([]);
const [queuedPortRecommendations, setQueuedPortRecommendations] = useState<string[]>([]);
useEffect(() => {
const doFetch = async () => {
const response = await fetch(`${BACKEND_BASE}/api/v1/ports?allData=false`);
const body: BackendResponse = await response.json()
const { ports, portStats } = body.data;
setData(ports);
}
doFetch()
}, [data]);
const addQueuedChange = (portRecommendation: string) => {
setQueuedPortRecommendations([...queuedPortRecommendations, portRecommendation]);
};
const columns = useMemo(
() => [
{
Header: 'Device name',
accessor: 'device.hostname'
},
{
Header: 'Compliance checks',
accessor: (port: Port) => {
return (
<PortDescriptionRecommendation
key={`${port.device}-${port.ifName}-port-recommendation`}
port={port}
addQueuedChange={addQueuedChange}
/>
);
},
}
],
[addQueuedChange]
)
return <TableContainer columns={columns} data={data} />;
PortDescriptionRecommendation
const PortDescriptionRecommendation = ({ port, addQueuedChange }: {port: Port, addQueuedChange: (portRecommendation: string) => void }) => {
const {remoteHostName, remotePort, tag, type} = port.portRecommendation;
const remotePortDescription = remotePort ? ` (${remotePort})` : '';
const handleAddToQueueButtonClicks = () => {
addQueuedChange(portConfig);
}
let portConfig = `
conf t
interface ${port.ifName}
description ${tag}${remoteHostName || ''}${remotePortDescription}
end
wr mem`;
return (
<button data-tip={portConfig} className={'btn btn-sm btn-info'} onClick={handleAddToQueueButtonClicks}>
Add recommendation to list
</button>
);
}
您应该使用 useCallback
来避免 re-rendering,每次您更新应用状态时,都会创建一个新的 addQueuedChange
实例。这就是为什么。而且你不必将它添加到 dependencies 数组。
首先,您想将 useCallback
添加到 addQueuedChange
,这样它就不会在每次渲染时创建新的 columns
。
虽然它可能无法完全解决问题:
当App
分量变为re-renders时。 TableContainer
也是 re-rendered。这是默认的 React 行为:当父级更新时,它 re-renders 所有子级。要更改此行为,您应该使用 React.memo.
一般来说,当您使用大 table 时,您希望使用 React.memo
。根据我的经验,我通常也会记住 table 的行。
确保 addQueuedChange
不依赖于 portRecommendation
const addQueuedChange = useCallback(
(portRecommendation: string) => {
setQueuedPortRecommendations((prevPortRecommendation) => [
...prevPortRecommendation,
portRecommendation,
]);
},
[setQueuedPortRecommendations]
);
我有这个应用程序,我在页面加载时提取大量数据,然后使用 react-tables 呈现 table。每行都有一个合规性列,其中包含有关是否合规的状态标签。此列还包含一个按钮,按下该按钮会使用推荐的合规值更新状态 (queuedPortRecommendations
)。
table 需要很长时间才能呈现,正如预期的那样,它大约有 1400 行。但是我不明白为什么它会在 addQueuedChange
函数被调用时重新渲染?
我尝试从 useMemo
的 deps 数组中删除 addQueuedChange
,但随后并没有向数组中添加字符串,而是将其替换。
我知道很难想出一个直接的答案,但如果有任何提示或想法,我将不胜感激。
App.tsx
const App = () => {
const [data, setData] = useState<Port[]>([]);
const [queuedPortRecommendations, setQueuedPortRecommendations] = useState<string[]>([]);
useEffect(() => {
const doFetch = async () => {
const response = await fetch(`${BACKEND_BASE}/api/v1/ports?allData=false`);
const body: BackendResponse = await response.json()
const { ports, portStats } = body.data;
setData(ports);
}
doFetch()
}, [data]);
const addQueuedChange = (portRecommendation: string) => {
setQueuedPortRecommendations([...queuedPortRecommendations, portRecommendation]);
};
const columns = useMemo(
() => [
{
Header: 'Device name',
accessor: 'device.hostname'
},
{
Header: 'Compliance checks',
accessor: (port: Port) => {
return (
<PortDescriptionRecommendation
key={`${port.device}-${port.ifName}-port-recommendation`}
port={port}
addQueuedChange={addQueuedChange}
/>
);
},
}
],
[addQueuedChange]
)
return <TableContainer columns={columns} data={data} />;
PortDescriptionRecommendation
const PortDescriptionRecommendation = ({ port, addQueuedChange }: {port: Port, addQueuedChange: (portRecommendation: string) => void }) => {
const {remoteHostName, remotePort, tag, type} = port.portRecommendation;
const remotePortDescription = remotePort ? ` (${remotePort})` : '';
const handleAddToQueueButtonClicks = () => {
addQueuedChange(portConfig);
}
let portConfig = `
conf t
interface ${port.ifName}
description ${tag}${remoteHostName || ''}${remotePortDescription}
end
wr mem`;
return (
<button data-tip={portConfig} className={'btn btn-sm btn-info'} onClick={handleAddToQueueButtonClicks}>
Add recommendation to list
</button>
);
}
您应该使用 useCallback
来避免 re-rendering,每次您更新应用状态时,都会创建一个新的 addQueuedChange
实例。这就是为什么。而且你不必将它添加到 dependencies 数组。
首先,您想将 useCallback
添加到 addQueuedChange
,这样它就不会在每次渲染时创建新的 columns
。
虽然它可能无法完全解决问题:
当App
分量变为re-renders时。 TableContainer
也是 re-rendered。这是默认的 React 行为:当父级更新时,它 re-renders 所有子级。要更改此行为,您应该使用 React.memo.
一般来说,当您使用大 table 时,您希望使用 React.memo
。根据我的经验,我通常也会记住 table 的行。
确保 addQueuedChange
不依赖于 portRecommendation
const addQueuedChange = useCallback(
(portRecommendation: string) => {
setQueuedPortRecommendations((prevPortRecommendation) => [
...prevPortRecommendation,
portRecommendation,
]);
},
[setQueuedPortRecommendations]
);