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

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

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

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

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

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

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

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

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

Поможет ли добавление AbortSignal к вашему прослушивателю событий ? Тогда вам понадобится только AbortController, чтобы удалить прослушиватель событий: js var controller = new AbortController(); var action = () => console.info('Hello!'); addEventListener('click', action, { signal: controller.signal }); controller.abort();

TGekko 21.08.2024 05:35

Этому вопросу не хватает контекста, но я вижу красный флаг (очистить прослушиватель внутри обратного вызова прослушивателя), который должен быть выполнен внутри useEffect (см. здесь пример Heritage.reactjs.org/docs/…). Возможно, это вызывает нежелательные вызовы setState.

Arnau 21.08.2024 09:30

Вы пытаетесь зарегистрировать обработчик mouseup через . addEvenetListener (опечатка в EvenEt).

Davi 21.08.2024 09:35

Спасибо за комментарии. Я добавил немного больше контекста и исправил опечатку. Сейчас я рассматриваю идею AbortController.

JeroSquartini 24.08.2024 20:36

Привет @TGekko, твое предложение сработало и решило мою проблему. Если хотите, можете опубликовать это как ответ, и я отмечу его как решенное. Спасибо!!

JeroSquartini 25.08.2024 01:14

Я рад, что мое предложение оказалось для вас полезным!

TGekko 25.08.2024 01:48
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
6
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

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

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

controller.abort();

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

Другие вопросы по теме