reactjs - 子组件的渲染会触发不需要的卸载
问题描述
我正在开发计划应用程序。主页有一个表格,其中行是项目,列是周。每个单元格是一个人针对相应项目和周的工作量。在表格的顶部,有一行给出了每周的总剩余可用性。它看起来像这样:
当用户在输入字段中键入工作负载时,该值会在短暂延迟(去抖动)后自动保存到服务器。然后调用回调来触发表顶部每周总计的更新。
这是此页面的组件层次结构:
WorkPlanPage
- ProjectWithWorkloads
- WorkloadInput
我将在下面显示的代码有 2 个问题:
- 调用回调时,保存的工作负载列表
WorkPlanPage
会更新,这会触发所有子组件的重新渲染。在这样做时,WorkloadInput
s 都被卸载和重新安装。如果一个(非常快速的)用户正在编辑其中,则对服务器的编辑和相关调用将丢失(去抖动被取消)。 - 我无法阻止重新渲染所有单元格,因为每次工作负载更改时都会更新回调。所以 memoization 不起作用,因为回调属性在每次工作负载更新时都会更新。这个带回调的父子关系问题似乎很基础,但我想不出一种方法来避免重新渲染所有孩子,当有很多孩子时,这可能是一个问题(50个项目,15周显示,因此 750 个输入字段。
这是 3 个组件的代码,只有相关位:
工作计划页面
const WorkPlanPage = () => {
// ...
const { data: leaves } = useQuery(
['leaves', { employeeId: employeeId, start: firstWeek, end: lastWeek }],
() => requestEmployeeLeaves(employeeId, firstWeek, lastWeek
{ refetchOnWindowFocus: false, initialData: [], enabled: (employeeId > 0) && (currentDate !== undefined) }
);
// this callback changes every time allWorkloads changes. The callback changes
// allWorkloads which in turn triggers the re-rendering of all the children.
const handleWorkloadChange = useCallback((updated: Workload) => {
if (allWorkloads === undefined) {
return;
}
// first remove the workload we're about to update
let newWorkloads = allWorkloads.filter(workload => {
return (
workload.project !== updated.project ||
workload.employee !== updated.employee ||
workload.date !== updated.date
);
});
newWorkloads.push(updated);
queryClient.setQueryData(
['workloads', { employeeId: employeeId, start: firstWeek, end: lastWeek }],
newWorkloads);
}, [allWorkloads, employeeId, firstWeek, lastWeek, queryClient]);
// ...
return (
<div>
<div>{/* Other parts of the page omitted */}</div>
<WeeklyAvailabilies
workloads={allWorkloads ? allWorkloads : []}
leaves={leaves ? leaves : []}
weeks={weeks} />
{displayedProjects?.map((project: Project) => (
<ProjectWithWorkload
key={project.id + "-" + employeeId}
employeeId={employeeId}
project={project}
weeks={weeks}
workloads={workloadsForProjects.get(project.id)}
onWorkloadChange={handleWorkloadChange}
/>
))}
</div>
</div>
);
}
ProjectWithWorkloads(行)
const ProjectWithWorkload = ({
employeeId,
project,
weeks,
workloads,
onWorkloadChange
}) => {
return (
<div>
<div>{/* Removed project header */}</div>
<div>
{workloads.map((workload: Workload) => (
<div key={workload.project + workload.date}>
<WorkloadInput
project={project}
workload={workload}
onWorkloadChange={onWorkloadChanged}
/>
</div>
))}
</div>
</div>
);
}
工作负载输入(单元格)
const WorkloadInput = ({ project, workload, onWorkloadChange }) => {
const [effort, setEffort] = useState<string>("");
// ...
const handleEffortChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const valueAsString = event.currentTarget.value;
const valueAsNumber = parseFloat(valueAsString);
setEffort(event.currentTarget.value);
if (valueAsString === "") {
debouncedSaveWorkload(0);
} else if (!isNaN(valueAsNumber)) {
debouncedSaveWorkload(valueAsNumber);
}
};
const debouncedSaveWorkload = useMemo(() =>
_.debounce(saveWorkload, 500), [saveWorkload]
);
// cancel debounce on unmounting
useEffect(() => {
return () => {
debouncedSaveWorkload.cancel();
}
}, [debouncedSaveWorkload]);
const saveWorkload = useCallback((newEffort: number) => {
updateWorkload({
...workload, effort: newEffort
}).then((updatedWorkload) => {
onWorkloadChange(updatedWorkload); // where the callback is called
});
};
return (
<div>
<input
type="text"
value={workload}
onChange={handleEffortChange}
/>
</div>
);
};
解决方案
推荐阅读
- python - 尽管输入了正确的信息,程序总是诉诸“错误登录”
- json - 谁能告诉我时间点是什么以及如何在这个 json 响应中使用它?
- reactjs - React redux 从输入中丢失一个字符
- laravel - laravel updateOrCreate 查询空数据
- android - Kotlin Android Room 数据库删除查询问题
- python - 在组合框中获取所有用户选择的值
- java - entityManager.find 对数据库产生了多少次命中?
- sql - PL/SQL 块以显示表详细信息
- javascript - 如何使用 Javascript 实时从 Firebase-Database 获取数据
- c - 如何将 Gstreamer RTSP 连接到 iPhone?