Я использую обработчик событий для части компонента, которую впоследствии необходимо удалить, поэтому функция обработчика не может измениться, иначе ее ссылка изменится, и я не смогу удалить прослушиватель. Поэтому при установке состояния внутри обработчика событий я должен использовать функцию обновления для установки состояния, например:
const handleMouseDown = () => {
const handleMouseMove = () => {
// Using updater function to update state
setValues((values) => {
const newValue = calculateNewValue(values);
const newValues = [...values, newValue];
return newValues;
});
};
const handleMouseUp = () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}
Проблема в том, что мне нужно установить другое состояние на основе newValues
, и я хочу воспользоваться преимуществами пакетной обработки React, чтобы все обновлялось после одного повторного рендеринга. Следующий код работает, но вызывает два повторных рендеринга:
const handleMouseDown = () => {
const handleMouseMove = () => {
setValues((values) => {
const newValue = calculateNewValue(values);
const newValues = [...values, newValue];
// Works, but triggers 2 re-renders: one because values
// changed, and another one because otherValues changed
setOtherValues(calculateOtherValues(newValues));
return newValues;
});
};
const handleMouseUp = () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
Для моих целей не подойдет следующее:
Сохранение newValues
перед использованием setValues
не работает, поскольку оно заключено в обработчик событий, который не создается повторно при каждом рендеринге, так как мне нужно иметь возможность удалить его впоследствии, как я сказал вначале. Это означает, что использование values
будет ссылаться на его старое содержимое (оно будет ссылаться на состояние, которое оно имело на момент создания обработчика):
const handleMouseDown = () => {
const handleMouseMove = () => {
// Doesn't work because values will always reference the state
// it had when this function was created.
const newValue = calculateNewValue(values);
const newValues = [...values, newValue];
setValues(newValues);
setOtherValues(calculateOtherValues(newValues));
};
const handleMouseUp = () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
Не использовать состояния. Чтобы упростить задачу, я использовал calculateOtherValues
вместо вставки всей своей логики. Это сразу же приводит к вопросу: зачем использовать состояние, если его можно вычислить, исходя из values
? Ну, в этом конкретном обработчике otherValues
может быть и вычисляется на основе values
, но это не тот случай в одном из других моих обработчиков, где values
нужно вычислять на основе otherValues
, поэтому мне нужно сохранить его как состояние .
Я пытаюсь найти способ заставить его работать без использования дополнительного повторного рендеринга, как во втором примере кода.
Обновлено: исправление опечатки и добавление немного большего контекста:
Компонент, который я создаю, представляет собой специальный слайдер с несколькими маркерами/ползунками и предназначен для выбора процентов, а не самих положений ползунка. Итак, если вы хотите, например, распределить голоса за определенных кандидатов, вы можете переместить маркеры и получить очень наглядное представление процентов, которые получает каждый кандидат.
Обработчики событий структурированы таким образом, что когда пользователь щелкает один из этих дескрипторов, документ начинает прослушивать движение мыши. Я добавляю в документ прослушиватель, а не дескриптор, потому что хочу, чтобы пользователь мог перемещать мышь за пределы дескриптора (который небольшой) и продолжать перетаскивать дескриптор. Документ также начинает прослушивать освобождение мыши, и когда это происходит, вышеупомянутые прослушиватели удаляются.
Очень важно, чтобы функции прослушивателя сохраняли одну и ту же ссылку, чтобы я мог удалить их после отпускания мыши. Я видел такое поведение вложенности событий в некоторых других подобных компонентах в Интернете, но я, конечно, приветствую любые предложения :)
Я понимаю, что это не лучшая практика, но не думаю, что это является причиной моей проблемы. Мне просто нужно найти обходной путь или переосмыслить свои состояния, чтобы я мог обновлять оба состояния с помощью функции обновления и запускать только один повторный рендеринг.
Этому вопросу не хватает контекста, но я вижу красный флаг (очистить прослушиватель внутри обратного вызова прослушивателя), который должен быть выполнен внутри useEffect (см. здесь пример Heritage.reactjs.org/docs/…). Возможно, это вызывает нежелательные вызовы setState.
Вы пытаетесь зарегистрировать обработчик mouseup
через . addEvenetListener
(опечатка в EvenEt
).
Спасибо за комментарии. Я добавил немного больше контекста и исправил опечатку. Сейчас я рассматриваю идею AbortController.
Привет @TGekko, твое предложение сработало и решило мою проблему. Если хотите, можете опубликовать это как ответ, и я отмечу его как решенное. Спасибо!!
Я рад, что мое предложение оказалось для вас полезным!
Похоже, вашу проблему можно решить, добавив AbortSignal к вашему прослушивателю событий.
Используя AbortController, вы можете удалить конкретный прослушиватель событий в любое время. Таким образом, вам не придется пытаться сохранить функцию для прослушивателя:
var controller = new AbortController();
var action = () => console.info('Hello!');
addEventListener('click', action, { signal: controller.signal });
Чтобы удалить прослушиватель событий, вам нужно всего лишь прервать прослушиватель с помощью контроллера:
controller.abort();
Надеюсь, это поможет решить проблему, связанную с изменением функции прослушивателя.
Поможет ли добавление AbortSignal к вашему прослушивателю событий ? Тогда вам понадобится только AbortController, чтобы удалить прослушиватель событий:
js var controller = new AbortController(); var action = () => console.info('Hello!'); addEventListener('click', action, { signal: controller.signal }); controller.abort();