#reactjs #lifecycle
Вопрос:
Я работаю над приложением для планирования. На главной странице есть таблица, в которой строками являются проекты, столбцами-недели. Каждая ячейка — это рабочая нагрузка человека для соответствующего проекта и недели. В верхней части таблицы есть строка, в которой указана общая оставшаяся доступность за каждую неделю. Это выглядит так :
Когда пользователь вводит рабочую нагрузку в поле ввода, значение автоматически сохраняется на сервере после небольшой задержки (отмена). Затем вызывается обратный вызов, чтобы запустить обновление итоговых данных за неделю в верхней части таблицы.
Вот иерархия компонентов для этой страницы:
WorkPlanPage
- ProjectWithWorkloads
- WorkloadInput
Есть 2 проблемы с кодом, которые я покажу ниже :
- При вызове обратного вызова обновляется список выполняемых рабочих нагрузок
WorkPlanPage
, что запускает повторную визуализацию всех дочерних компонентов. При этомWorkloadInput
все s размонтируются и монтируются заново. Если бы кто-то находился в процессе редактирования (очень быстрым) пользователем, редактирование и связанный с ним вызов на сервер были бы потеряны (отмена отмены отменяется). - Я не могу предотвратить повторное отображение всех ячеек, потому что обратный вызов обновляется каждый раз при изменении рабочей нагрузки. Таким образом, запоминание не будет работать, потому что функция обратного вызова обновляется при каждом обновлении рабочей нагрузки. Эта проблема взаимоотношений родителей и детей с обратным вызовом кажется очень простой, и все же я не могу придумать способ избежать повторного отображения всех детей, что может быть проблемой, когда детей много (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) amp;amp; (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 (rows)
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>
);
};
Комментарии:
1. Для оптимизации вы хотите только повторно отобразить a
cell
или весьrow
? И, судя по всему, в настоящее время он обновляет все строки, это правильно?2. @MwamiTovi Я ожидал бы, что только строка, в которой была обновлена рабочая нагрузка, должна быть повторно отображена. Среди этой строки я ожидал бы, что только измененная ячейка будет повторно отображена. Любое повторное отображение других ячеек в настоящее время запускает отмену функции
debounceSaveWorkload
отмены . Это приведет к потере предстоящих сохранений на сервере. Это происходит, когда пользователь редактирует рабочую нагрузку в течение недели и быстро обновляет рабочую нагрузку на следующем этапе до завершения первого сетевого цикла.3. @MwamiTovi И, кстати, меня не совсем беспокоит кажущееся отсутствие оптимизации. Меня больше беспокоит то, что полная повторная визуализация отменяет отмену во всех других ячейках с неожиданным эффектом, который я описал. Просто так получилось, что если только измененная клетка перерисовывается, я убиваю двух зайцев одним выстрелом. Но этого не может произойти (afai может видеть), потому
handleWorkloadChange
что обратный вызов меняется после каждого обновления , а также массив рабочей нагрузкиWorkPlanPage
, запускающий весь повторный рендеринг.4. Хммм…. вы довольно хорошо объяснили, какова ваша цель здесь. Изучите его более внимательно и вернетесь с некоторыми предложениями.
5. Ждать… Если
onWorkloadChange
вcell
компоненте есть на самом делеhandleWorkloadChange
… почему бы не сделатьhandleWorkloadChange
так, чтобы это полностью обрабатывалось внутриcell
компонента? Таким образом, компонентParent
иrows
должен быть исключен из любых повторных визуализаций. Вы уже пробовали это сделать? И посмотрите на исключениеallWorkloads
(которое , я полагаю, должно быть каким-тоstate
, верно?)handleWorkloadChange
изcell
компонента. Таким образом, вместо этого вы можете сохранитьallWorkloads
родительский компонент, но его следует изменять только в том случае, если вам нужно повторно отобразитьParent
его .