Как стать автором
Обновить

Комментарии 10

Дело тут, конечно, не в нативных/синтетических событиях, а в списке зависимостей useEffect. В первом случае Вы создаете clickHandler при каждом рендере и удаляете/добавляете нативный обработчик (внутри onclick={clickHandler}) Аналогичный код с нативными событиями будет, если в useEffect убрать [] и выполнять его на каждый render.

Какие события можно считать синтетическими? В общем-то все, которые поддерживаются на данный момент и их обработчики содержатся (важно) в определении DOM-дерева функционального или классового компонента (в JSX-коде).

В статье показана разница между синтетическими и нативными событиями. При описании проблемы я указываю на то, что обработка синтетических событий определена в JSX-коде и все эти обработчики захватывают актуальный полный контекст компонента (внутри них актуальные ссылки на состояния).

Пример с useEffect без массива зависимостей тут не очень подходит, т.к. переподписка на события будет при вообще любом рендере (необязательно при изменении стейта value). В общем-то можно использовать в массиве зависимостей и просто стейт value:

// ...

useEffect(() => {
    // Подписка на обработку нативного события
    container.current.addEventListener("click", clickHandler);

    return () => {
      // Отписка на обработку нативного события
      container.current.removeEventListener("click", clickHandler);
    }
  }, [value]);

// ...

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

Я про первый кейс с синтетическими событиями

 /**
   * Обработчик синтетического события
   * @param {*} e Объект синтетического события
   */
  const clickHandler = (e) => {
    // Установка текущего значения рандомайзера
    setCurrentValue(value);
  };

Тут мемоизации нет, поэтому value актуальный. Если тут добавить useCallback(..., []) то возникнет та же проблема - value будет старым. И точно так же, если добавить value в deps все станет хорошо.

События тут вообще ни при чем. Тут все упирается в непонимание того как работает компонент. При каждом его обновлении, вызывается его функция, и все, что объявлено в этой функции будет заново объявлено. Все ссылки станут новыми, все старые ссылки никуда не делись, и продолжают висеть в памяти. В примере из статьи, этой самой ссылкой является clickHandler . Следовательно, надо вручную отследить, отписать от неактуальной и подписать на актуальную. Сделать все то, что делает сам реакт под капотом.

С первым утверждением частично согласен. Конкретно события - ни при чём. Тут причастны обработчики данных событий, о чём, собственно, и написано в статье.

Ранее я упомянул, что обработчики синтетических событий обязательно должны находиться в определении DOM-элементов (в JSX-коде). И именно благодаря тому, что они определены в JSX-коде "синтетический" код работает правильно, а "нативный" нет. Поскольку обработчики синтетических событий захватывают контекст компонента полностью, а обработчики нативные - нет (лишь частично).

В целом то, что Вы описали (проблема с функциональным компонентом) есть в статье, только я оперирую введённым понятием "контекст React-компонента" и не упомянул ничего про память, думаю это хорошее дополнение. Суть проблемы не меняется - обработчики синтетических событий находятся в JSX-коде и при перерисовке они захватывают все актуальные состояния, а при обработке нативных событий этого не происходит и внутри обработчиков мы всегда имеем начальное значение состояния. Да и не всегда оно начальное. Если использовать hot reload, то можно добиться такого поведения, что в примере из статьи значение currentValue установится на какое-нибудь рандомное единожды, но не более.

Я использовал термин "контекст React-компонента" потому что обработчики событий всё-таки захватывают, например, refs. Поэтому нельзя сказать что вообще все ссылки в этом обработчике не захватываются. Захватываются, только ссылки refs. Ну и ещё можно дополнить этот список "исключений" по мере необходимости.

Ну, а сам "не захват" части контекста компонента происходит как раз из-за того, что Вы описали - ссылки на функцию не теряются, а новые уже не добавляются в обработчик нативного события. Отсюда и есть необходимость переподписки.

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

onClick в обработке, в react, ничем не отличается от className . Как и не отличаются div и Counter. Проверится предыдущее значение, провериться следующее значение, если какой-то параметр изменился, то функция компонента выполнится и результат уйдет на рендер. То, как результат попадет в реальный DOM, уже не так важно.

Ивенты тут вообще ни при чем, потому что вместо clickHandler может быть какой-то onSomeAction , который должен вызываться, если в компоненте что-то поменялось, и это никаким образом не будет связано с ивентами. Но работать будет идентично, и проблемы будут идентичные.

Стиль изложения методички из университета

предлагаю читателю поразмышлять
внимательный читатель заметит...

Содержание нам том же "высоком" уровне. Видно что автор "разбирается" в теме.

  1. Изобретена какая-то своя терминология: Обработчики событий почему-то называются событиями.

  2. Браузерному Event отказывают в праве называться нативным потому что там могут быть другие свойства не из спецификации (а обоснование этого "предлагаю читателю поразмышлять"). Что за своя терминология? Нативность и спецификация вообще разные свойства. Как цвет и вкус.

  3. Синтетические события - это круто и лучше чем нативные потому что синтетические нас защищают от утечки памяти. Классный довод, хорошо бы ещё раскрыть, а то мантра какая-то (читателю поразмышлять?)

  4. вот смотрите этот код работает хорошо, потому что он синтетический. А этот не работает, потому что он не синтетический. Довод офигенный. Оказывается дело не в том что автор написал код с тривиальным багом который линтер с плагином для react не пропустит.

  5. "Внимательный читатель..." + "Обработчики нативных событий не захватывают состояния, но захватывают refs". Вау блин. А "Синтетические" (в терминологии автора конечно) захватывают что ли? Оба варианта замыканий работают одинаково (это javascript всё таки, автор понимает как он работает?) Нативный обработчик автор значит замемоизировал с багом (тем который видит eslint) указав неверные dependencies, а синтетический обработчик он бодро пересоздает каждую перерисовку и радуется

Это какая-то вредная статья. Прочтёт её джун, пойдёт на собеседование и начнёт такую же чушь нести наивно :(

Вообще, можно разделить критику на две группы (так проще): токсичная и здоровая.

Токсичная критика в целом не несёт ничего хорошего как для автора статьи, как для самой статьи, так и для автора токсичного комментария изложенного выше.

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

Я считаю, что Ваша критика не совсем здорова. Ваши высмеивания издержек из моего труда (которые, к слову, ошибочны) это демонстрируют.

Обругать можно что угодно и быть токсичным проще простого. Вообще как "нефиг делать". Вы попробуйте быть терпеливым, сдержанным, уметь направить других людей в нужном направлении. Это сложнее всего. И к этому, я считаю, нужно стремиться.

Теперь к самому комментарию.

Изобретена какая-то своя терминология: Обработчики событий почему-то называются событиями

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

Синтетические события - это круто и лучше чем нативные потому что синтетические нас защищают от утечки памяти. Классный довод, хорошо бы ещё раскрыть, а то мантра какая-то (читателю поразмышлять?)

Где я написал, что синтетические события это "круто" и что они "лучше" нативных? Вообще не к этой статье относится. Может прочитали что-то одно, потом пришли почитать эту статью и у Вас всё смешалось? Такое бывает, это нормально. Я указал на чёткую разницу между синтетическими и нативными событиями. И то, что для решения описанной мной проблемы пользователь может захотеть переписать функциональные компоненты на классовые - это просто факт. Ну нет охоты у читателя переподписку делать, проще классовые компоненты сделать. Этот пункт Вы просто сами придумали (кроме утечки памяти, это в статье действительно было).

Браузерному Event отказывают в праве называться нативным потому что там могут быть другие свойства не из спецификации (а обоснование этого "предлагаю читателю поразмышлять"). Что за своя терминология? Нативность и спецификация вообще разные свойства. Как цвет и вкус

Где я отказывал в праве браузерному событию называться нативным? Я просто высказал своё мнение. Внимание, это было моё мнение, а не навязывание какой-то позиции. Я никакой позиции своей тут не навязываю. Спецификация это одно, а реализация этой спецификации под разные браузеры - это другое. Именно это я и отразил в своём скромном мнении о нативных событиях.

Нативность - не мой термин. Он существует с тех пор, как люди стали разграничивать высокоуровневые вещи и низкоуровневые в контексте программной (и не только) реализации.

вот смотрите этот код работает хорошо, потому что он синтетический. А этот не работает, потому что он не синтетический. Довод офигенный. Оказывается дело не в том что автор написал код с тривиальным багом который линтер с плагином для react не пропустит.

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

Кстати, если Вы называете эту особенность "тривиальным багом", то Вам стоит лучше разобраться с этой механикой, потому что это не баг и он не тривиальный.

"Внимательный читатель..." + "Обработчики нативных событий не захватывают состояния, но захватывают refs". Вау блин. А "Синтетические" (в терминологии автора конечно) захватывают что ли? Оба варианта замыканий работают одинаково (это javascript всё таки, автор понимает как он работает?) Нативный обработчик автор значит замемоизировал с багом (тем который видит eslint) указав неверные dependencies, а синтетический обработчик он бодро пересоздает каждую перерисовку и радуется

Речи о замыканиях тут вообще не шло. Речь шла о захвате контекста компонента. Не более. На счёт "мемоизации с багом" - ну, как свой eslint настроите, так он и будет работать. У меня всё работает так, как я и ожидаю. Рекомендую ознакомиться с темой мемоизации и кэширования в React.js (+ чистые функции), может быть Вы к такому коду отнесётесь более сдержанно, потому что в массив зависимостей useEffect'а добавлять обёрнутую в useCallback функцию можно, иначе ничего бы не работало :).

Это какая-то вредная статья. Прочтёт её джун, пойдёт на собеседование и начнёт такую же чушь нести наивно :(

Не согласен с Вами. Одна из целей статьи - "повышение осведомлённости" о такой проблеме. В своё время я часто забывал об этой проблеме и совершал ошибки. Если бы где-то было понятное описание этой проблемы и способу её решения - я был бы рад. Не нашёл таких статей - написал сам. Теперь любой желающий может найти эту статью в поисковике и ознакомиться с проблемой, её решением и исключениях. Может быть, даже получит мотивацию для дальнейшего исследования этого вопроса.

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

Если уж совсем не поняли - спросите в комментариях, уточните что автор имел ввиду в конечном счёте. А то есть риск формирования у Вас выводов, которые максимально бесполезны и вредны. И Вам и людям, которые их прочитают.

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

А Ваш комментарий в текущем виде... ну, не знаю, пользы от него нет.

Ругать можно что угодно, сложно найти в чём угодно хорошее, как и положительно влиять на авторов статей, чтобы они писали их лучше :)

Проблема тут сугубо в мемоизации, неправильных зависимостях в useEffect. Просто надо следовать правилу, что все что используется в функции внутри и будет меняться, то должно быть помещено в зависимости. Это просто причина обновить мемоизацию. В ином случае нужно понимать, что функция будет старая.

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

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

Чтобы эффект не вызывался при изменении value и не делал бесполезные переподписки, можно воспользоваться хуком useEvent.

const clickHandler = useEvent(() => {
    console.log("Проверка");
    // Установка текущего значения рандомайзера
    setCurrentValue(value);
  });

useEffect(() => {
  // ...
}, [clickHandler]);

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории