Установка состояния на основе другого состояния, которое обновляется с помощью обратного вызова

Я использую обработчик событий для части компонента, которую впоследствии необходимо удалить, поэтому функция обработчика не может измениться, иначе ее ссылка изменится, и я не смогу удалить прослушиватель. Поэтому при установке состояния внутри обработчика событий я должен использовать функцию обновления для установки состояния, например:

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, поэтому мне нужно сохранить его как состояние .

Я пытаюсь найти способ заставить его работать без использования дополнительного повторного рендеринга, как во втором примере кода.

Обновлено: исправление опечатки и добавление немного большего контекста:

Компонент, который я создаю, представляет собой специальный слайдер с несколькими маркерами/ползунками и предназначен для выбора процентов, а не самих положений ползунка. Итак, если вы хотите, например, распределить голоса за определенных кандидатов, вы можете переместить маркеры и получить очень наглядное представление процентов, которые получает каждый кандидат.

Обработчики событий структурированы таким образом, что когда пользователь щелкает один из этих дескрипторов, документ начинает прослушивать движение мыши. Я добавляю в документ прослушиватель, а не дескриптор, потому что хочу, чтобы пользователь мог перемещать мышь за пределы дескриптора (который небольшой) и продолжать перетаскивать дескриптор. Документ также начинает прослушивать освобождение мыши, и когда это происходит, вышеупомянутые прослушиватели удаляются.

Очень важно, чтобы функции прослушивателя сохраняли одну и ту же ссылку, чтобы я мог удалить их после отпускания мыши. Я видел такое поведение вложенности событий в некоторых других подобных компонентах в Интернете, но я, конечно, приветствую любые предложения :)

Я понимаю, что это не лучшая практика, но не думаю, что это является причиной моей проблемы. Мне просто нужно найти обходной путь или переосмыслить свои состояния, чтобы я мог обновлять оба состояния с помощью функции обновления и запускать только один повторный рендеринг.

🤔 А знаете ли вы, что...
JavaScript является одним из трех основных языков веб-разработки, вместе с HTML и CSS.


1
73
1

Ответ:

Решено

Похоже, вашу проблему можно решить, добавив AbortSignal к вашему прослушивателю событий.

Используя AbortController, вы можете удалить конкретный прослушиватель событий в любое время. Таким образом, вам не придется пытаться сохранить функцию для прослушивателя:

var controller = new AbortController();
var action = () => console.info('Hello!');
addEventListener('click', action, { signal: controller.signal }); 

Чтобы удалить прослушиватель событий, вам нужно всего лишь прервать прослушиватель с помощью контроллера:

controller.abort();

Надеюсь, это поможет решить проблему, связанную с изменением функции прослушивателя.