首页 > 解决方案 > 子组件的渲染会触发不需要的卸载

问题描述

我正在开发计划应用程序。主页有一个表格,其中行是项目,列是周。每个单元格是一个人针对相应项目和周的工作量。在表格的顶部,有一行给出了每周的总剩余可用性。它看起来像这样:

应用截图

当用户在输入字段中键入工作负载时,该值会在短暂延迟(去抖动)后自动保存到服务器。然后调用回调来触发表顶部每周总计的更新。

这是此页面的组件层次结构:

WorkPlanPage
 - ProjectWithWorkloads
   - WorkloadInput

我将在下面显示的代码有 2 个问题:

  1. 调用回调时,保存的工作负载列表WorkPlanPage会更新,这会触发所有子组件的重新渲染。在这样做时,WorkloadInputs 都被卸载和重新安装。如果一个(非常快速的)用户正在编辑其中,则对服务器的编辑和相关调用将丢失(去抖动被取消)。
  2. 我无法阻止重新渲染所有单元格,因为每次工作负载更改时都会更新回调。所以 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>
  );
};

标签: reactjslifecycle

解决方案


推荐阅读