React: [ESLint] Отзыв о правиле lint 'Exustive-deps'

Созданный на 21 февр. 2019  ·  111Комментарии  ·  Источник: facebook/react

Общие ответы

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡

Мы проанализировали комментарии к этому сообщению, чтобы дать некоторые рекомендации: https://github.com/facebook/react/issues/14920#issuecomment -471070149.

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡


Что это

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

autofix demo

Установка

yarn add eslint-plugin-react-hooks<strong i="18">@next</strong>
# or
npm install eslint-plugin-react-hooks<strong i="19">@next</strong>

Конфигурация ESLint:

{
  "plugins": ["react-hooks"],
  // ...
  "rules": {
    "react-hooks/rules-of-hooks": 'error',
    "react-hooks/exhaustive-deps": 'warn' // <--- THIS IS THE NEW RULE
  }
}

Простой тестовый пример для проверки работы правила:

function Foo(props) {
  useEffect(() => {
    console.log(props.name);
  }, []); // <-- should error and offer autofix to [props.name]
}

Правило lint жалуется, но мой код в порядке!

Если это новое правило react-hooks/exhaustive-deps lint срабатывает для вас, но вы считаете, что ваш код правильный , опубликуйте сообщение в этом выпуске.


ПЕРЕД КОММЕНТАРИЕЙ

Пожалуйста, укажите эти три вещи:

  1. CodeSandbox, демонстрирующий минимальный пример кода, который все еще выражает ваше намерение (не «foo bar», а реальный шаблон пользовательского интерфейса, который вы реализуете).
  2. Объяснение шагов, которые делает пользователь, и того, что вы ожидаете увидеть на экране.
  3. Объяснение предполагаемого API вашего хука / компонента.

please

Но мой случай прост, я не хочу включать эти вещи!

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

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

ESLint Rules Discussion

Самый полезный комментарий

Мы прошли через это сегодня с @threepointone . Вот краткое изложение:

Исправлено в правиле Lint

Посторонние useEffect зависимости

Правило не мешает вам больше добавлять "посторонние" депеши в useEffect , поскольку существуют допустимые сценарии.

Функции в том же компоненте, но определенные вне эффекта

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

Стоит исправить в пользовательском коде

Сброс состояния при изменении реквизита

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

«Моя нефункциональная ценность постоянна»

Крючки подталкивают вас к правильности везде, где это возможно. Если вы все же указываете зависимости (которые в некоторых случаях можно опустить), мы настоятельно рекомендуем включить даже те, которые, по вашему мнению, не изменятся. Да, в этом примере useDebounce задержка вряд ли изменится. Но это все равно ошибка, но Hook не справляется с ней. Это проявляется и в других сценариях. (Например, хуки гораздо более совместимы с горячей перезагрузкой, потому что каждое значение обрабатывается как динамическое.)

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

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Тогда это явно не может измениться, если вы не поместите его в рендер. (Что не было бы идиоматическим использованием вашего хука.) Но утверждение, что <Slider min={50} /> никогда не может измениться, на самом деле неверно - кто-то может легко изменить его на <Slider min={state ? 50 : 100} /> . На самом деле, кто-то мог это сделать:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Если кто-то переключит isCelsius в состояние, компонент, предполагающий, что min никогда не изменится, не сможет обновиться. В этом случае неочевидно, что Slider будет тем же самым (но это будет потому, что у него такая же позиция в дереве). Так что это серьезное оружие с точки зрения внесения изменений в код. Важным моментом в React является то, что обновления отображаются так же, как и исходные состояния (обычно вы не можете сказать, какие из них какие). Независимо от того, визуализируете ли вы значение prop B или переходите от значения prop A к B - оно должно выглядеть и вести себя одинаково.

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

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

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

«Значение моей функции постоянно»

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

Если это действительно постоянно , то указать его в DEPS не пострадали. Например, в случае, когда функция setState внутри настраиваемого хука возвращается вашему компоненту, а затем вы вызываете ее из эффекта. Правило lint недостаточно умен, чтобы понять подобное косвенное обращение. Но с другой стороны, любой может обернуть этот обратный вызов позже, прежде чем вернуться, и, возможно, сослаться на другую опору или состояние внутри него. Тогда это не будет постоянным! И если вам не удастся обработать эти изменения, у вас будут неприятные устаревшие ошибки свойств / состояний. Так что указать это по умолчанию лучше.

Однако ошибочно считать, что значения функций обязательно постоянны. Они чаще всего постоянны в классах из-за привязки методов, хотя это создает свой собственный диапазон ошибок . Но в целом любая функция, которая закрывает значение в функциональном компоненте, не может считаться постоянной. Правило lint теперь лучше подсказывает вам, что делать. (Например, переместить его внутрь эффекта - простейшее исправление - или заключить в оболочку useCallback .)

Есть проблема в противоположном спектре этого, когда вы получаете бесконечные циклы (значение функции всегда изменяется). Мы поймаем это в правиле lint сейчас, когда это возможно (в том же компоненте), и предложим исправление. Но сложно пройти что-то на несколько уровней ниже.

Вы все равно можете обернуть его в useCallback чтобы устранить проблему. Помните, что технически изменение функции допустимо , и вы не можете игнорировать этот случай, не рискуя ошибиться. Например, onChange={shouldHandle ? handleChange : null} или рендеринг foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> в том же месте. Или даже fetchComments который закрывает состояние родительского компонента. Это может измениться. С классами его поведение изменится незаметно, но ссылка на функцию останется прежней. Таким образом, ваш ребенок пропустит это обновление - у вас действительно нет другого выхода, кроме передачи ребенку дополнительных данных. С функциональными компонентами и useCallback сам идентификатор функции изменяется - но только при необходимости. Так что это полезное свойство, а не просто препятствие, которого следует избегать.

Мы должны добавить лучшее решение для обнаружения бесконечных асинхронных циклов. Это должно смягчить самый запутанный аспект. В будущем мы могли бы добавить для этого обнаружение. Вы также можете написать что-то вроде этого самостоятельно:

useWarnAboutTooFrequentChanges([deps]);

Это не идеально, и нам нужно больше подумать о том, как с этим справиться. Я согласен случаи , как это довольно противно. Исправление без нарушения правила заключалось бы в том, чтобы сделать rules статическим, например, изменив API на createTextInput(rules) и обернув register и unregister в useCallback . Еще лучше, удалите register и unregister и замените их отдельным контекстом, в котором вы помещаете только dispatch . Тогда вы можете гарантировать, что у вас никогда не будет другого идентификатора функции после его чтения.

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

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

Реакция на сложные изменения стоимости

Мне странно, почему в этом примере используется эффект для чего-то, что по сути является обработчиком событий. Ведение того же «журнала» (я предполагаю, что это может быть отправка формы) в обработчике событий кажется более подходящим. Это особенно верно, если мы подумаем о том, что происходит, когда компонент отключается. Что, если он отключится сразу после того, как эффект был запланирован? Такие вещи, как отправка формы, в этом случае не должны просто «не происходить». Таким образом, похоже, что эффект может быть неправильным выбором.

Тем не менее, вы все равно можете делать то, что пробовали, - вместо этого сделав fullName равным setSubmittedData({firstName, lastName}) , а затем [submittedData] - это ваша зависимость, из которой вы можете прочитать firstName и lastName .

Интеграция с императивным / устаревшим кодом

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


Надеюсь, я никого не забыл! Сообщите мне, если я сделал или что-то неясно. Вскоре мы постараемся превратить уроки из этого в некоторые документы.

Все 111 Комментарий

В этом примере есть ответ: https://github.com/facebook/react/issues/14920#issuecomment -466145690

CodeSandbox

Это неконтролируемый компонент Checkbox, который принимает defaultIndeterminate prop для установки неопределенного статуса при первоначальном рендеринге (что можно сделать только в JS с использованием ссылки, потому что нет атрибута indeterminate element). Этот параметр предназначен для работы как defaultValue , где его значение используется только при первоначальном рендеринге.

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

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

Re: https://github.com/facebook/react/issues/14920#issuecomment -466144466

@billyjanitsch Будет ли это работать вместо этого? https://codesandbox.io/s/jpx1pmy7ry

Я добавил useState для indeterminate который инициализируется как defaultIndeterminate . Затем эффект принимает в качестве аргумента [indeterminate] . В настоящее время вы не меняете его, но если бы вы сделали это позже, я думаю, это тоже сработает? Таким образом, код немного лучше предвидит возможные варианты использования в будущем.

Итак, у меня есть следующий (край?) Случай, когда я передаю некоторый html и использую его с dangerouslySetInnerHtml для обновления моего компонента (некоторого редакционного контента).
Я использую не опору html а ссылку, в которой я могу использовать ref.current.querySelectorAll чтобы творить чудеса.
Теперь мне нужно добавить html к моим зависимостям в useEffect хотя я не использую его явно. Это тот случай, когда я действительно должен отключить правило?

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

Это разбавленный пример реального компонента:
https://codesandbox.io/s/8njp0pm8v2

Я использую response-redux, поэтому при передаче создателя действия в реквизитах из mapDispatchToProps и использовании этого создателя действия в хуке я получаю предупреждение exhaustive-deps .

Итак, очевидно, что я могу добавить действие redux в массив зависимостей, но поскольку действие redux является функцией и никогда не изменяется, это не обязательно, верно?

const onSubmit = React.useCallback(
  () => {
    props.onSubmit(emails);
  },
  [emails, props]
);

Я ожидаю, что линт исправит глубину в [emails, props.onSubmit] , но сейчас он всегда исправляет глубину в [emails, props] .

  1. CodeSandbox, демонстрирующий минимальный пример кода, который все еще выражает ваше намерение (не «foo bar», а реальный шаблон пользовательского интерфейса, который вы реализуете).

https://codesandbox.io/s/xpr69pllmz

  1. Объяснение шагов, которые делает пользователь, и того, что вы ожидаете увидеть на экране.

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

  1. Объяснение предполагаемого API вашего хука / компонента.

У моего компонента есть единственная опора, onSubmit: (emails: string[]) => void . Он будет вызываться с состоянием emails когда пользователь отправит форму.


РЕДАКТИРОВАТЬ: ответил в https://github.com/facebook/react/issues/14920#issuecomment -467494468

Это потому, что технически props.foo() передает сам props как this foo вызову foo может неявно зависеть от props . Однако для этого случая нам понадобится сообщение получше. Лучшая практика - это всегда деструктуризация.

CodeSandbox

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

Привет,

Не уверен, что здесь не так с моим кодом:

const [client, setClient] = useState(0);

useEffect(() => {
    getClient().then(client => setClient(client));
  }, ['client']);

У меня React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

@joelmoss @sylvainbaronnet Я ценю ваш отзыв, но я скрыл ваши комментарии, потому что они не включали информацию, которую мы запрашивали в начале проблемы. Это делает это обсуждение излишне трудным для всех, потому что отсутствует контекст. Я был бы рад продолжить разговор, если вы снова опубликуете и включите всю необходимую информацию (см. Верхний пост). Спасибо за понимание.

  1. CodeSandbox
  2. Пользователь может выбрать имя, и это заставит изменить фамилию. Пользователь может выбрать фамилию, и это не приводит к принудительному изменению имени.
  3. Существует настраиваемая ловушка, которая возвращает часть состояния, компонент выбора, который обновляет это состояние, и метод обновления ловушки состояния, который обновляет состояние программно. Как было показано, я не всегда хочу использовать функцию обновления, поэтому я оставил ее последним элементом в возвращаемом массиве.

Я считаю, что код как есть, не должен ошибаться. Прямо сейчас в строке 35 говорится, что setLastName следует включить в массив. Есть мысли, что с этим делать? Я делаю что-то неожиданное?

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

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

@joelmoss Думаю, мой репортаж касается и твоего случая.

Да, CodeSandbox все равно поможет. Представьте, пожалуйста, на что похоже переключение контекста между фрагментами кода людей в течение всего дня. Это огромные умственные потери. Ни один из них не выглядит одинаково. Когда вам нужно вспомнить, как люди используют создателей действий в Redux или какую-то другую концепцию, внешнюю по отношению к React, это еще сложнее. Проблема может показаться вам очевидной, но мне совсем не очевидно, что вы имели в виду.

@gaearon Я понимаю, что это имеет смысл, на самом деле это сработало для меня, потому что я хотел достичь следующих целей:

Если вы хотите запустить эффект и очистить его только один раз (при монтировании и размонтировании), вы можете передать пустой массив ([]) в качестве второго аргумента.

Большое спасибо за разъяснения. (и извините за не по теме)

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

https://codesandbox.io/s/2x4q9rzwmp?fontsize=14

  • Объяснение шагов, которые делает пользователь, и того, что вы ожидаете увидеть на экране.

В этом примере все отлично работает! Кроме проблемы с линтером.

  • Объяснение предполагаемого API вашего хука / компонента.

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

Вот что я получаю от линтера при запуске его в коде песочницы:

25:5   warning  React Hook useEffect has a missing dependency: 'fetchPodcastsFn'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Мой вопрос: так ли он должен себя вести (либо правило линтера, либо правило массива хуков)? Есть ли более идиоматический способ описать этот эффект?

@svenanders , мне интересно, почему вы не хотите включать fetchPodcastsFn ? Это потому, что вы обнаруживаете, что он меняется при каждом рендере? Если это так, вы, вероятно, захотите запомнить эту функцию или сделать ее статической (в случае, если у нее нет параметров).

Во-первых, речь идет о ясности. Когда я смотрю на функцию, я хочу легко понять, когда она должна сработать. Если я увижу _one_ id в массиве, все ясно. Если я вижу этот идентификатор и кучу функций, все становится более запутанным. Мне нужно потратить время и силы, возможно, даже на отладку функций, чтобы понять, что происходит.
Мои функции не меняются во время выполнения, поэтому я не знаю, будет ли их мемоизация иметь значение (в частности, это действие отправки, которое запускает эпопею, в конечном итоге приводя к изменению состояния).

https://codesandbox.io/s/4xym4yn9kx

  • Шаги

Пользователь получает доступ к маршруту на странице, но он не суперпользователь, поэтому мы хотим перенаправить его со страницы. props.navigate вводится через библиотеку маршрутизатора, поэтому на самом деле мы не хотим использовать window.location.assign для предотвращения перезагрузки страницы.

Все работает!

  • Предполагаемый API

Я правильно ввел зависимости, как в песочнице кода, но линтер сообщает мне, что в списке зависимостей должно быть props вместо props.navigate . Это почему?

Твит со скриншотами! https://twitter.com/ferdaber/status/1098966716187582464

РЕДАКТИРОВАТЬ: одна веская причина, по которой это может быть ошибкой, заключается в том, что navigate() - это метод, который полагается на привязанный this , и в этом случае технически, если что-то внутри props изменяется, тогда материал внутри this также изменится.

CodeSandbox: https://codesandbox.io/s/711r1zmq50

Предполагаемый API:

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

Шаги:

Пример позволяет выполнять поиск в Marvel Comic API и использует useDebounce для предотвращения запуска вызовов API при каждом нажатии клавиши.

IMO добавление «всего», что мы используем, в массив зависимостей неэффективно.

Например, рассмотрим хук useDebounce . В реальном мире (по крайней мере, в нашем) delay не изменится после первого рендеринга, но мы проверяем, изменилось ли оно или нет при каждом повторном рендеринге. Итак, в этом хуке вторым аргументом useEffect лучше быть [value] вместо [value, delay] .

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

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

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

Я вижу в исходном коде, что есть некоторое намерение позволить людям указывать регулярное выражение для захвата пользовательских хуков по имени:

https://github.com/facebook/react/blob/ba708fa79b3db6481b7886f9fdb6bb776d0c2fb9/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L490 -L492

Что бы команда React подумала о дополнительном соглашении об именах для пользовательских хуков с массивами зависимостей? Хуки уже следуют соглашению о префиксе use , чтобы этот плагин обнаружил их как ловушку. Соглашение, которое я бы предложил для обнаружения пользовательских хуков, которые полагаются на определенные зависимости, было бы своего рода постфиксом, что-то вроде WithDeps , что означает, что полное имя пользовательского хука может быть чем-то вроде useCustomHookWithDeps . Постфикс WithDeps сообщит плагину, что последний аргумент массива является одной из зависимостей.

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

Это предупреждение и автоматическое удаление пользовательской проверки равенства.

const myEqualityCheck = a => a.includes('@');

useCallback(
  () => {
    // ...
  },
  [myEqualityCheck(a)]
);

Что касается useEffect, я не думаю, что должно появиться предупреждение о «ненужной зависимости», потому что эти «зависимости» изменят способ срабатывания эффекта.

Допустим, у меня есть два счетчика, родительский и дочерний:

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

Реализация:

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);

  useEffect(
    () => {
      setChild(0);
    },
    [parent] // "unnecessary dependency: 'parent'"
  );

Правило исчерпывающей информации дает предупреждение React Hook useEffect has an unnecessary dependency: 'parent'. Either exclude it or remove the dependency array.

screen shot 2019-02-25 at 11 06 40 am

Я не думаю, что линтер должен делать это предложение, потому что он меняет поведение приложения.

  1. CodeSandbox: https://codesandbox.io/s/ol6r9plkv5
  2. Шаги: при изменении parent child сбрасывается до нуля.
  3. Намерение: вместо того, чтобы отмечать зависимость как ненужную, линтер должен распознать, что parent изменяет поведение useEffect и не должен рекомендовать мне его удалять.

[РЕДАКТИРОВАТЬ]

В качестве обходного пути я могу написать что-нибудь вроде

const [parent, setParent] = useState(0)
const [child, setChild] = useState(0)
const previousParent = useRef(parent)

useEffect(() => {
  if (parent !== previousParent.current) {
    setChild(0)
  }

  previousParent.current = parent
}, [parent])

Но в оригинальном фрагменте есть «поэзия», которая мне нравится.

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

На самом деле это шаблон, который я использовал пару раз, чтобы аннулировать некоторый внутренний кеш:

useEffect(() => {
  if (props.invalidateCache) {
    cache.clear()
  }
}, [props.invalidateCache])

Если мне не следует использовать его таким образом, просто дайте мне знать и проигнорируйте этот комментарий.

@MrLeebo
Что о

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);
  const updateChild = React.useCallback(() => setChild(0), [parent]);

  useEffect(
    () => {
      updateChild();
    },
    [updateChild] 
  );

Я бы тоже не понял твоего отрывка. Зависимость может предполагаться только на основе вашего кода, но это может быть ошибкой. Хотя мое предложение не обязательно решает эту проблему, оно делает его, по крайней мере, более явным.

Похоже, это ранее было решено с помощью getDerivedStateFromProps ? Как мне реализовать getDerivedStateFromProps? помощь?

@nghiepit Я скрыл ваш комментарий, потому что вы проигнорировали обязательный контрольный список в первом посте (например, CodeSandbox). Пожалуйста, следуйте контрольному списку и опубликуйте сообщение еще раз. Спасибо.

@ eps1lon У вас будет такое же предупреждение о useCallback , и я не согласен с тем, что этот шаблон в целом является улучшением по сравнению с оригиналом, но я не хочу сорвать тему, чтобы поговорить об этом. Чтобы уточнить, я думаю, что ненужное правило зависимости следует ослабить для useEffect или useLayoutEffect , потому что они могут содержать эффективную логику, но правило должно оставаться в силе для useCallback , useMemo и т. Д.

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

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

https://codesandbox.io/s/5v9w81j244

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

Основная идея хука useDelayedItems - это массив элементов и объект конфигурации (задержка в мс, начальный размер страницы). Первоначально мне будет передано подмножество элементов в зависимости от моего настроенного размера страницы. После прохождения config.delay элементы теперь будут полным набором элементов. Когда ему передается новый набор элементов, ловушка должна повторно запустить эту логику "задержки". Идея состоит в очень грубой и глупой версии отложенного рендеринга больших списков.

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

РЕДАКТИРОВАТЬ : Я добавил несколько поясняющих комментариев в поле codeandbox относительно намерения поведения. Надеюсь, информации достаточно, но дайте мне знать, если что-то по-прежнему вызывает затруднения.

Как насчет одного значения, которое объединяет другие значения?

Пример: fullName является производным от firstName и lastName . Мы хотим активировать эффект только при изменении fullName (например, когда пользователь нажимает «Сохранить»), но также хотим получить доступ к значениям, которые он создает в эффекте.

Демо

Добавление firstName или lastName к зависимостям приведет к поломке, поскольку мы хотим запустить эффект только после изменения fullName .

@aweary Я не уверен, какую пользу вы получаете от косвенного изменения useEffect prop. Кажется, что ваш onClick должен обрабатывать этот "эффект".

https://codesandbox.io/s/0m4p3klpyw

Что касается отдельных значений, которые объединяют другие значения, useMemo , вероятно, будет тем, что вы хотите. Отсроченный характер расчета в вашем примере означает, что он не соответствует точно 1: 1 с вашим связанным поведением.

Я создам ссылки на коды и ящик для этих примеров и отредактирую этот пост.

У меня очень простое правило: если текущая вкладка изменилась, прокрутите вверх:
https://codesandbox.io/s/m4nzvryrxj

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

И я получил React Hook useEffect has an unnecessary dependency: 'activeTab'. Either exclude it or remove the dependency array

Кроме того, когда я переносил некоторые компоненты, я хочу воспроизвести поведение componentDidMount:

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

это правило сильно на это жалуется. Я не решаюсь добавлять все зависимости в массив useEffect.

Наконец, если вы объявите новую встроенную функцию перед useEffect, например:
https://codesandbox.io/s/nr7wz8qp7l

const foo = () => {...};
useEffect(() => {
    foo();
  }, []);

Вы получаете: React Hook useEffect has a missing dependency: 'foo'. Either include it or remove the dependency array
Я не уверен, как я отношусь к добавлению foo в список зависимостей. В каждом рендере foo есть новая функция, и всегда будет запускаться useEffect?

@ ksaldana1 недостаточно поместить побочный эффект в обработчик событий. Это приведет к возникновению побочного эффекта до того, как фактическое обновление будет зафиксировано, что может привести к тому, что побочный эффект будет срабатывать чаще, чем вы хотите.

Это также не будет работать при использовании useReducer потому что обновленное состояние не будет доступно внутри обработчика событий.

Что касается отдельных значений, которые объединяют другие значения, useMemo , вероятно, будет тем, что вы хотите.

Если бы в этом примере использовалось useMemo он бы сломался, потому что useMemo будет получать новый fullName каждый раз при изменении firstName или lastName . Поведение здесь таково, что fullName не обновляется, пока не будет нажата кнопка «Сохранить».

@aweary Хотели бы что-то вроде этой работы? https://codesandbox.io/s/lxjm02j50m
Мне очень любопытно посмотреть, какова рекомендуемая реализация.

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

Запуск и эффект в обработчике:

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

Использование useReducer state в обработчике событий:

обновленное состояние не будет доступно.

Спасибо!

@bugzpodder вроде не имеет отношения, но вызов прокрутки должен быть внутри useLayoutEffect вместо useEffect . В настоящее время наблюдается заметное мерцание при переходе к новому маршруту.

Не уверен, намеренно это или нет:

Когда вы вызываете функцию из props, линтер предлагает добавить весь объект props в качестве зависимости.

Укороченная версия:

useEffect(function() {
  props.setLoading(true)
}, [props.setLoading])

// ⚠️ React Hook useEffect has a missing dependency: 'props'.

Полная версия: Codesandbox

Попробую еще раз ... но на этот раз попроще;)

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

Итак, я, очевидно, могу добавить функцию в массив зависимостей, но поскольку это функция и никогда не изменяется, это не обязательно, верно?

-> Песочница

Надеюсь, это все, что вам нужно, но я просто раздвоил песочницу

Спасибо.

Итак, я, очевидно, могу добавить функцию в массив зависимостей, но поскольку это функция и никогда не изменяется, это не обязательно, верно?

Это неверно; функции могут постоянно меняться при повторном рендеринге родительского объекта.

@siddharthkp

Когда вы вызываете функцию из props, линтер предлагает добавить весь объект props в качестве зависимости.

Это потому, что технически props.foo() передает сам props как this foo вызову foo может неявно зависеть от props . Однако для этого случая нам понадобится сообщение получше. Лучшая практика - это всегда деструктуризация.

функции могут постоянно меняться при повторном рендеринге родительского объекта.

Конечно, но если бы функция была определена в другом месте, а не в родительском компоненте, она бы никогда не изменилась.

Конечно, но если бы функция была определена в другом месте, а не в родительском компоненте, она бы никогда не изменилась.

Если вы импортируете его напрямую, правило lint не попросит вас добавить его к эффекту deps. Только если он находится в области рендеринга. Если он находится в области рендеринга, то правило lint не знает, откуда оно взялось. Даже если это не динамично сегодня, это может быть завтра, когда кто-то изменит родительский компонент. Так что указание этого - правильное значение по умолчанию. В любом случае не помешает указать, статичен ли он.

спасибо @gaearon

Это потому, что технически props.foo() передает сам props как this foo вызову foo может неявно зависеть от props . Однако для этого случая нам понадобится сообщение получше. Лучшая практика - это всегда деструктуризация.

Это тоже отвечает на мой вопрос. Спасибо! 😄

https://codesandbox.io/s/98z62jkyro

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

Бывший:

 useEffect(
    () => {
      register(props.name, props.rules || []);
      console.log("register");
      return function cleanup() {
        console.log("cleanup");
        unregister(props.name);
      };
    },
    [props.name, props.rules, register, unregister]
  );

Когда props.rules заставляет быть частью зависимостей, кажется, что это приводит к бесконечному циклу рендеринга. Props.rules - это набор функций для регистрации в качестве валидаторов. Я обнаружил эту проблему только при предоставлении массивов в качестве зависимостей. В моемcodeandbox вы можете видеть, что он зацикливается, открывая консоль.

Просто наличие props.name в качестве зависимости заставляет его работать так, как задумано. Применение зависимостей, как уже указывалось, изменит поведение приложения, и в этом случае побочные эффекты будут серьезными.

@bugzpodder

Re: https://github.com/facebook/react/issues/14920#issuecomment -467212561

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Это похоже на законный случай. Я собираюсь ослабить предупреждение, чтобы допустить посторонние эффекты только для эффектов. (Но не для useMemo или useCallback .)

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

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

Наконец, если вы объявите новую встроенную функцию перед useEffect, например: https://codesandbox.io/s/nr7wz8qp7l

В этом случае простое решение - переместить doSomething в эффект. Тогда вам не нужно его декларировать. В качестве альтернативы вы можете useCallback около doSomething . Я готов ослабить правило, чтобы разрешить пропуск функций, которые используют только объявленные зависимости. Но это может сбивать с толку, если у вас есть функция, вызывающая другую функцию, и вы добавляете опору или состояние в одну из этих функций. Внезапно все эффекты, использующие его, временно должны быть обновлены. Это может сбивать с толку.

Я не знаю, является ли это запросом функции для нового правила или чем-то, что можно улучшить в exhaustive-deps

import React from 'react'
import emitter from './thing'

export const Foo = () => {
  const onChange = () => {
    console.log('Thing changed')
  }

  React.useEffect(() => {
    emitter.on('change', onChange)
    return () => emitter.off('change', onChange)
  }, [onChange])

  return <div>Whatever</div>
}

Поскольку функция onChange создается при каждом рендеринге, аргумент useEffect hook [onChange] является избыточным и может быть удален:

   React.useEffect(() => {
     emitter.on('change', onChange)
     return () => emitter.off('change', onChange)
-  }, [onChange])
+  })

Линтер может это обнаружить и посоветовать удалить аргумент массива.

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

Только что выпущен [email protected] с несколькими исправлениями и улучшенными сообщениями для этого правила. Ничего особенного, но нужно решить несколько дел. Остальное я посмотрю на следующей неделе.

Я также разместил здесь первый возможный шаг для исключения "безопасных" функций: https://github.com/facebook/react/pull/14996. (См. Тесты.) Если у вас есть идеи о полезной эвристике и о том, как направлять людей к правильным исправлениям, прокомментируйте PR.

@gaearon Отличная идея. Это определенно будет полезно для улучшения стиля при использовании хуков 🙏

@gaearon Это все равно не работает, если действие исходит от опоры. Рассмотрим этот пример:

image

В котором setScrollTop - это редукционное действие.

В этом примере в компоненте Slider я использую useEffect чтобы дождаться, пока DOM не станет доступным, чтобы я мог смонтировать компонент noUiSlider. Поэтому я передаю [sliderElement] чтобы гарантировать, что ссылка будет доступна в DOM при запуске эффекта. Мы также выполняем рендеринг наших компонентов на сервере, поэтому это также обеспечивает доступность DOM перед рендерингом. Другие реквизиты, которые я использую в useEffect (т.е. min, max, onUpdate и т.д.), являются константами, и поэтому я не вижу необходимости передавать их в эффект.

screen shot 2019-03-02 at 5 17 09 pm


Вот эффект, показанный в codeandbox:

const { min, max, step } = props;
const sliderElement = useRef();

useEffect(() => {
  if (!sliderElement.current) {
    return;
  }

  const slider = sliderElement.current;
  noUiSlider.create(slider, {
    connect: true,
    start: [min, max],
    step,
    range: {
      min: [min],
      max: [max],
    },
  });

  if (props.onUpdate) {
    slider.noUiSlider.on('update', props.onUpdate);
  }

  if (props.onChange) {
    slider.noUiSlider.on('change', props.onChange);
  }
}, [sliderElement]);

@ WebDeg-Brian Я не могу вам помочь без полной демонстрации CodeSandbox. Извините. См. Верхний пост.

Я написал немного о распространенном заблуждении «функции никогда не меняются»:

https://overrected.io/how-are-function-components-different-from-classes/

Не совсем та же тема, но имеет отношение к этому правилу.

Привет @gaearon , вот пример, который вы просили меня разместить здесь (из твитера) :)

В основном я пытаюсь преобразовать ловушку реакции моей библиотеки на хуки.
Это просто ловушка для событий вне / внутри элемента.

Моя проблема в том, что если useEffect не зависит от значения состояния ( trapped ), иногда оно устарело.
Я написал несколько комментариев и журналов для демонстрации. Посмотрите на файл useTrap.js , комментарии и журналы находятся в функциях useEffect и preventDefaultHelper .

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

  1. CodeSandbox
  2. Шаги:
    Пользователь щелкает внутри поля, чтобы сделать его активным, и снаружи, чтобы сделать его неактивным, пользователь также может щелкнуть правой кнопкой мыши, хотя при первом щелчке он не должен вызывать контекстное меню ( e.preventDefault ).
    Когда я говорю «первый щелчок», я имею в виду первый щелчок, который меняет состояние.
    Если поле активно, щелчок правой кнопкой мыши за его пределами изменит состояние на «неактивно» и откроет контекстное меню. еще один щелчок за пределами не повлияет на состояние, поэтому должно появиться контекстное меню.

Я надеюсь, что здесь все ясно, и вариант использования понятен, пожалуйста, дайте мне знать, если мне нужно предоставить дополнительную информацию. Спасибо!

Привет, @gaearon , я добавляю сюда свой собственный хук, как вы мне предложили в Твиттере! Я изо всех сил пытаюсь найти правильную форму, которая не пропускала бы зависимости.

Это подробный пример, надеюсь, я смогу объяснить это ясно и понятно.

Это текущее состояние: react-async-utils / src / hooks / useAsyncData.ts

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

triggerAsyncData обновляет asyncData состояние асинхронно в соответствии с функцией getData которая возвращает Promise . triggerAsyncData может быть вызван как эффект, так и "вручную" пользователем ловушки.

Вызовы

  1. Зависимости эффекта, вызывающего triggerAsyncData являются внутренними. triggerAsyncData - это зависимость эффекта, но она создается при каждом рендеринге. Линия мыслей на данный момент:

    1. Просто добавьте его как зависимость => Но тогда эффект будет работать на каждом рендере.

    2. Добавьте его как зависимость, но используйте useMemo / useCallback с triggerAsyncData => useMemo / useCallback следует использовать только для оптимизации производительности НАСКОЛЬКО МНЕ ИЗВЕСТНО.

    3. Включите его в эффект => Тогда я не смогу вернуть его пользователю.

    4. Вместо использования triggerAsyncData в качестве зависимости используйте зависимости triggerAsyncData качестве зависимостей => Лучший вариант, который я нашел до сих пор. Но это нарушает правило «исчерпывающей зависимости».

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

    1. Оставьте ответственность за пользователя. Они предоставят соответствующие значения, используя useMemo / useCallback при необходимости => Боюсь, что довольно часто они этого не делают. А если и так, то довольно многословно.

    2. Разрешите дополнительный аргумент для настраиваемого хука, чтобы предоставить зависимости от входных данных, и используйте его вместо самих входов => Круто, менее многословно, больше контроля для пользователя. Я использую это. Но это нарушает правило «исчерпывающей зависимости». (На самом деле я использую это и возвращаюсь к обычным значениям, если не указан дополнительный аргумент. Я считаю это мощным шаблоном).

  3. Плохо управляемые зависимости для настраиваемой ловушки создают бесконечный асинхронный цикл из-за внутреннего эффекта. Я использовал защитное программирование, чтобы предотвратить это, но оно добавляет «фальшивку»? зависимость ( asyncData ). Это снова нарушает правило «исчерпывающей зависимости».

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

Спасибо за всю тяжелую работу!

Привет, @gaearon, спасибо за вашу тяжелую работу.

  1. Минимальный пример асинхронной выборки данных Пример CodeSandbox .

  2. Ожидается, что пользователь увидит 5 строк заголовка lorem ipsum, извлеченных из json api .

  3. Я создал собственный хук для выборки данных с предполагаемым API:

const { data, isLoading, isError } = useDataApi('https://jsonplaceholder.typicode.com/posts')

Внутреннее устройство пользовательского хука useDataApi :

...
  const fetchData = async () => {
    let response;
    setIsError(false);
    setIsLoading(true);

    try {
      response = await fetch(url).then(response => response.json());

      setData(response);
    } catch (error) {
      setIsError(true);
    }

    setIsLoading(false);
  };

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );
...

Проблема в этом коде

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );

где react-hooks/exhaustive-deps выдает предупреждение о том, что я должен добавить fetchData в свой массив зависимостей, а url следует удалить.

Если я изменю этот хук на

  useEffect(
    () => {
      fetchData();
    },
    [fetchData]
  );

затем он постоянно запускает запросы и никогда не останавливается, что, конечно, является большой проблемой. Я не уверен, что мой код содержит ошибки или react-hooks/exhaustive-deps дает ложное срабатывание.

Любая помощь приветствуется. Огромное спасибо.

PS Я прочитал ваш комментарий о том, что useEffect не подходит для выборки данных , однако в документах утверждается, что Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects что дало мне уверенность в том, что данные useEffect отлично подходят для выборки данных. Так что теперь я немного запутался 😕

@ jan-stehlik вы должны заключить fetchData в useCallback. Я раздвоил ваши коды и ящик с необходимыми изменениями здесь https://codesandbox.io/s/pjmjxprp0m

Очень полезно, большое спасибо @viankakrisna !

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

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

Интересно.

Вы используете handleEvent внутри своего эффекта. Но вы этого не заявляете. Вот почему значения, которые он читает, устарели.

Что вы имеете в виду, говоря: _ "Но вы этого не заявляете" _?
Он объявляется под эффектом (как и все другие обработчики).

Или вы имеете в виду, что обработчик использует значение состояния, а эффект присоединяет обработчик, тогда это означает, что эффект зависит от этого значения состояния?

Или вы имеете в виду, что обработчик использует значение состояния, а эффект присоединяет обработчик, тогда это означает, что эффект зависит от этого значения состояния?

Да, если вы используете функцию, вы должны либо объявить ее в deps (и в этом случае обернуть ее useCallback чтобы не воссоздавать ее), либо все, что использует функция.

Хорошо, это для меня новость! Спасибо за этот вклад @gaearon :)
Я просто хочу, чтобы это было предельно ясно для меня (и других?) ...

Если эффект вызывает, передает или делает что-либо с функцией, нам нужно передать его массиву deps:
Сама функция ИЛИ Переменные, которые использует эта функция.
Если функция объявлена ​​внутри функционального компонента / пользовательского хука, рекомендуется обернуть ее useCallback чтобы она не создавалась повторно каждый раз, когда запускается наш компонент или пользовательский хук.

Я должен сказать, что не видел этого в документации .
Как вы думаете, можно ли добавить его в раздел _Note_?

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

Редактировать
Еще одна вещь, в моем примере, каковы последствия для useCallback когда он обертывает handleEvent (или любые другие обработчики по этой причине). это сам event ?

Я должен сказать, что не видел этого в документации.

В документации говорится, что «каждое значение, указанное внутри функции эффекта, также должно появиться в массиве input». Функции тоже являются ценностями. Я согласен, что нам нужно лучше задокументировать это - об этом и идет речь :-) Я собираю варианты использования для новой страницы документации, посвященной этому.

Еще одна вещь, в моем примере, каковы последствия для useCallback, когда он обертывает handleEvent (или любые другие обработчики по этой причине). это само событие?

Не уверен, что вы имеете в виду. Это любые значения, на которые ссылается функция вне ее. Как и в useEffect .

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

Что касается useCallback , я использовал его так:

const memoHandleEvent = useCallback(
    handleEvent
);

и, конечно, передал memoHandleEvent как зависимость для useEffect а также addEventListener вместо реальной функции handleEvent . похоже, работает, надеюсь, это правильный и идиоматический способ сделать это.

Обратите внимание: useCallback без второго аргумента ничего не делает.

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

Обратите внимание, что useCallback без второго аргумента ничего не делает.

Аргг! : гримасничать: смеяться

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

Ой, это та же ссылка сверху. я только что обновил его :)

Пожалуйста, не обновляйте ссылки CodeSandbox: они нужны PI, как они были изначально - иначе я не смогу проверить на них правило lint. Не могли бы вы создать две отдельные песочницы, если у вас есть два разных решения? Так что я могу проверить каждую.

Ой, извините! : PI превышено количество песочниц в моем аккаунте. позвольте мне удалить некоторые, и я создам еще один (и отменим изменения в оригинале).

@gaearon это вторая ссылка на решение с useCallback

У меня есть сценарий, который я считаю нормальным, но линтер жалуется. Мой образец:

CodeSandbox

Образец пытается представить, когда пользователь нажимает кнопку, а затем делается запрос на запрос новых данных на основе ранее предоставленного идентификатора и функции. Если идентификатор изменяется, он не должен запрашивать новые данные; только новый запрос на перезагрузку должен запускать новый запрос данных.

Пример здесь немного надуманный. В моем реальном приложении React находится в DIV, который является небольшой частью гораздо более крупного веб-приложения. Функция, которая передается, осуществляется через Redux и mapDispatchToProps, где создание действия принимает идентификатор и делает запрос ajax для выборки данных и обновления хранилища. Опора refreshRequest передается через React.createElement. В моей первоначальной реализации у меня был код, который выглядел в компоненте класса следующим образом:

componentDidUpdate (prevProps) { const { getData, someId, refreshRequest} = this.props; if (prevProps.refreshRequest!== this.props.refreshRequest) { getData(someId); } }

Я пытаюсь реализовать то же поведение с помощью хука эффекта. Но как написано в образце линтер жалуется:

предупреждение React Hook useEffect имеет недостающие зависимости: 'getData' и 'someId'. Либо включите их, либо удалите массив зависимостей

Если я добавлю все, что хочет линтер, то, если пользователь щелкнет любую кнопку в примере, сработает useEffect. Но я хочу, чтобы он запускался только при нажатии кнопки «Запросить новые данные».

Надеюсь, это имеет смысл. Буду рад уточнить подробности, если что-то неясно. Спасибо!

Я только что опубликовал [email protected] котором есть экспериментальная поддержка для обнаружения простых зависимостей функций (которые, как правило, не очень полезны без useCallback ). Вот гифка:

demo

Был бы рад, если бы вы все могли попробовать это в своих проектах и ​​посмотреть, каково это! (Прокомментируйте этот конкретный поток в https://github.com/facebook/react/pull/15026.)

Завтра попробую на примерах из этой ветки.

Я еще не пробовал, но интересно, имеет ли он дело с подъемом. Это просто «минимальный пример», который я придумал, поэтому он ни для чего не полезен, но я часто использую приподнятые объявления, чтобы упростить просмотр оператора return .

function Component() {
  useEffect(() => {
    handleChange
  }, [handleChange])

  return null

  function handleChange() {}
}

Было бы неплохо, если бы в правиле была возможность настроить другую функцию, которая будет вести себя как useEffect в отношении линтера. Например, я добавил это, чтобы я мог легко использовать асинхронные функции для эффектов, которые выполняют вызов AJAX, однако при этом я теряю все преимущества линтинга exhaustive-deps :

const useAsyncEffect = (fn, ...args) => {
    /* eslint-disable react-hooks/exhaustive-deps */
    useEffect(() => {
        fn();
    }, ...args);
};

Изменить: Неважно, я только что заметил, что это уже возможно с помощью параметра правила additionalHooks .

Всем привет,
У меня есть пример https://codesandbox.io/s/znnmwxol7l

Ниже я хочу:

function App() {
  const [currentTime, setCurrentTime] = React.useState(moment());

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime.format("MMMM")] // <= this proplem [currentTime]
  );

  return (
    <div className="App">
      <h1>Current month: {currentMonth}</h1>
      <div>
        <button
          onClick={() => setCurrentTime(currentTime.clone().add(1, "days"))}
        >
          + 1 day
        </button>
      </div>
      <div>{currentTime.toString()}</div>
    </div>
  );
}

Но вот что я получаю:

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime] // <= this proplem
  );

Каждый раз, когда currentTime меняется, затем currentMonth пересчитывать ненужные

Или как-нибудь я могу сделать это ниже:

  const currentMonth = React.useMemo(
    () => {
      return currentTime.format("MMMM");
    },
    [myEqualityCheck(currentTime)]
  );

Извините, если на этот вопрос уже был дан ответ, не смог увидеть его в ветке выше:
Чтобы запустить ловушку useEffect только при монтировании, нужно определить пустой массив input в качестве второго параметра. Однако при этом исчерпывающие жалобы на включение этих входных аргументов изменили бы эффект, чтобы он также запускался при обновлении.
Какой подход к запуску useEffect только при монтировании с включенной исчерпывающей зависимостью?

@einarq Я думаю, вам нужно убедиться, что все указанные значения в вашем монтируемом useEffect никогда не изменятся. Этого можно добиться с помощью других хуков, например useMemo . После этого, независимо от того, добавляет ли это правило ESlint все ссылки в массив (с автофиксацией) или нет, код будет выполнен только один раз.

@einarq Как сказано в другом месте в этой ветке, мы не сможем помочь вам без CodeSandbox. Потому что ответ действительно зависит от вашего кода.

@nghiepit Ваш пример для меня не имеет смысла. Если ваш ввод - currentTime.format("MMMM") то useMemo ничего не оптимизирует для вас, потому что вы уже вычислили его . Так что вы просто вычисляете это дважды без надобности.

Можно ли указать, какой индекс аргумента является обратным вызовом для параметра additionalHooks ? Я вижу, что прямо сейчас мы предполагаем в коде, что это будет первый https://github.com/facebook/react/blob/9b7e1d1389e080d19e71680bbbe979ec58fa7389/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps51.js#L L1081

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

@CarlosGines Не могли бы вы создать CodeSandbox, как просили в сообщении OP. Я не зря спрашиваю - а то мне сложно проверить изменения. Особенно, когда он написан на TypeScript.

Я только что опубликовал [email protected] с, надеюсь, более полезными сообщениями и более умной эвристикой. Пожалуйста, попробуйте его на своих проектах, прежде чем он станет стабильным.

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

это должно быть [email protected] ?

да.

Собственно только что опубликовал [email protected] с парой небольших изменений.

@gaearon Удалось ли вам eslint работать внутриcodeandbox?

@einarq может что-то вроде этого сработает?

const useDidMount = fn => {
  const callbackFn = useCallback(fn)
  useEffect(() => {
    callbackFn()
  }, [callbackFn])
}

На самом деле мне неловко сказать, что я не могу заставить плагин работать с простым приложением CRA (на основе моего фрагмента ).
вот разветвленное репо из codeandbox.

Я видел эту заметку в документации :

Примечание. Если вы используете приложение Create React, дождитесь соответствующего выпуска сценариев реакции, которые включают это правило, вместо того, чтобы добавлять его напрямую.

Но разве сейчас нет возможности проверить это с помощью CRA? 👂

Да, вам придется извлечь и добавить его в конфигурацию ESLint, пока мы не добавим его туда. Вы можете извлечь ветку и не объединять ее. :-)

Привет,

Прежде всего: Большое спасибо за тесное сотрудничество с сообществом и за отличную работу, которую вы делаете в целом!

У нас возникла проблема с настраиваемым хуком, который мы построили на основе Fetch API. Я создал Codesandbox, чтобы продемонстрировать проблему.

Пример на Codesandbox

https://codesandbox.io/s/kn0km7mzv

Примечание. Проблема (см. Ниже) приводит к бесконечному циклу, и я не хочу выполнять DDoS jsonplaceholder.typicode.com который я использую для демонстрации проблемы. Поэтому я включил простой ограничитель запросов с использованием счетчика. Это не обязательно для демонстрации проблемы, но было неправильно запускать неограниченное количество запросов к этому замечательному проекту.

Объяснение

Идея состоит в том, чтобы упростить обработку трех возможных состояний запроса API: загрузка, успех и ошибка. Таким образом, мы создали специальный хук useFetch() который возвращает 3 свойства isLoading , response и error. It makes sure that either response or error is set and updates isLoading . As the name implies, it uses the Fetch` API.

Для этого он использует 3 крючка useState :

  const [isLoading, setIsLoading] = useState(false);
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);

и крючок useEffect :

useEffect(
    () => {
      fetch(url, fetchConfig)
        .then(/* Handle fetch response... See Codesandbox for the actual implementation */)
    },
    [url]
  );

Он работает нормально, пока массив зависимостей useEffect содержит только [url] . Если мы добавим fetchConfig (= [url, fetchConfig] ), это приведет к бесконечному циклу. Для нашего конкретного случая использования было бы достаточно повторно запустить эффект только при изменении url но линтер не позволяет использовать только [url] (проверено с v1.4.0 и v1.5.0-beta.1 ).

В конце настраиваемой ловушки в виде объекта возвращаются 3 переменные состояния:

  return {
    error,
    isLoading,
    response
  };

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

Что если fetchConfig изменится? Почему вы ожидаете, что он будет статичным?

Все в порядке. Я выпустил [email protected] с исправлениями и улучшениями прошлой недели.
Эта ветка была чрезвычайно полезна в поиске различных шаблонов, вызывающих проблемы.

Большое спасибо всем вам. (Особенно те, кто поставлял песочницы. :-)


Я не буду отвечать каждому лично подробно, потому что случаев очень много .

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

После публикации примеров я буду здесь комментировать и редактировать каждый комментарий, содержащий CodeSandbox, добавляя ссылку на соответствующий ответ / пример, демонстрирующий правильное исправление. Надеюсь, это поможет вам и будущим читателям.

Ваше здоровье!

❤️

@timkraut, вы должны иметь возможность добавить fetchConfig в deps, и компонент должен обернуть его памяткой, чтобы ссылка оставалась.
Пример: https://codesandbox.io/s/9l015v2x4w


Моя проблема в том, что теперь компонент должен знать детали реализации хука ...

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

Пример

https://codesandbox.io/s/40v54jnkyw

В этом примере я пытаюсь периодически автосохранять входные значения.

Объяснение

Каждые 5 секунд код пытается автоматически сохранить текущие значения (пока просто распечатайте их на экране). Линтеру потребуется, чтобы все входные значения были включены в массив зависимостей, что изменило бы частоту срабатывания эффекта.

  useEffect(
    () => {
      if (autoSaveTick % 5 === 0) {
        setAutosaveValue(
          `${input1Value.value}, ${input2Value.value}, ${input3Value.value}`
        );
      }
    },
    [autoSaveTick]
  );

Альтернатива, которую я вижу, - это реализация, аналогичная определенным хукам useTick и useInterval в примере, где есть обратный вызов, назначенный ref. Похоже, это приведет к ненужным переопределениям функций только ради согласования с правилом lint.

Я ищу лучшие практики в написании эффекта, который запускается при изменении одной переменной (переменная A), которая использует другие значения переменных (переменная B и переменная C) во время предыдущего изменения переменной (переменная A).

Что если fetchConfig изменится? Почему вы ожидаете, что он будет статичным?

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

Моя проблема в том, что теперь компонент должен знать детали реализации хука ...

Это как раз та проблема, которая есть у нас: / В нашей реализации мы даже использовали дополнительное свойство, чтобы упростить установку тела, поэтому нам пришлось бы использовать 2 вызова useMemo() всякий раз, когда мы используем этот хук. Пока не знаю, как преодолеть это ограничение. Если у вас есть идея, дайте мне знать!

Как можно запустить useEffect, у которого есть зависимости только один раз, без жалобы правила при передаче пустого массива?

Скажем, что-то вроде:

useEffect(() => { init({ dsn, environment}) }, [])

Моя проблема в том, что теперь компонент должен знать детали реализации хука ...

Я бы не сказал, что это детали реализации. Это ваш API. Если объект передан, мы предполагаем, что он может измениться в любой момент.

Если на практике он всегда статичен, вы можете сделать его аргументом для фабрики хуков. Например, createFetch(config) который возвращает useFetch() . Вы называете фабрику на верхнем уровне.

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

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

@asylejmani Вы не предоставили никаких подробностей о ваших

Суть правила состоит в том, чтобы сообщить вам, что environment и dsn могут изменяться со временем, и ваш компонент должен с этим справиться. Таким образом, либо ваш компонент глючит (потому что он не обрабатывает изменения этих значений), либо у вас есть уникальный случай, когда это не имеет значения (в этом случае вы должны добавить правило lint игнорировать комментарий, объясняющий, почему).

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

@gaearon извините за то, что не стал яснее (это всегда звучит ясно в голове) :) Мой вопрос больше похож на то, как вы можете достичь componentDidMount с помощью useEffect.

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

@asylejmani Я думаю, что самая большая проблема с методами жизненного цикла класса, такими как componentDidMount заключается в том, что мы склонны думать об этом как об изолированном методе, но на самом деле это часть потока.
Если вы ссылаетесь на что-то в componentDidMount вам, скорее всего, потребуется обработать это и в componentDidUpdate , иначе ваш компонент может получить ошибки.
Это то, что пытается исправить правило: вам нужно обрабатывать значения с течением времени.

  1. компонент установлен, сделайте что-нибудь с опорой
  2. компонент обновлен (например: значение свойства изменилось), сделайте что-нибудь с новым значением свойства

Номер 1 - это место, где вы помещаете логику в componentDidMount / useEffect body.
Номер 2 - это место, где вы помещаете логику в componentDidUpdate / useEffect deps

Правило жалуется, что вы не выполняете часть потока номер 2

@gaearon извините за то, что не стал яснее (это всегда звучит ясно в голове) :) Мой вопрос больше похож на то, как вы можете достичь componentDidMount с помощью useEffect.

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

Я думаю, что я и @asylejmani здесь на одной странице, но я предполагаю, что вы говорите @gaearon о том, что мы, вероятно,
Это справедливое заявление? Полагаю, я думаю, что предоставление пустого массива похоже на высказывание «Я знаю, что делаю», но я понимаю, почему вы все еще хотите сохранить это правило.

Извините, что пока не предоставил песочницу. На днях я начал с примера Create React App, не смог понять, как запустить eslint в песочнице, а затем потерял песочницу при перезагрузке браузера без предварительного сохранения (предполагалось, что временная среда CodeSandbox сохранена, мое плохое).
Потом мне пришлось лечь спать, и с тех пор не было времени.

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

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

@asylejmani - ваш вариант использования похож на https://github.com/facebook/react/issues/14920#issuecomment -466378650? Я не думаю, что в этом случае правило может понять сценарий, поэтому нам просто нужно вручную отключить его для этого типа кода. Во всех остальных случаях правило работает так, как должно.

Не уверен, что это имеет смысл, но один сценарий, который для меня довольно распространен, выглядит примерно так:

useEffect(() => {
    if(!dataIsLoaded) { // flag from redux
        loadMyData(); // redux action creator
    }
}, []);

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

Это характерно для redux, и ваше правило eslint не может этого знать, поэтому я понимаю, почему оно должно предупреждать. Все еще не знаете, следует ли просто отключить правило при предоставлении пустого массива? Мне нравится, что правило говорит мне о пропущенных deps, если я предоставил некоторые, но не все, или если я не предоставил их вообще. Пустой массив означает для меня нечто иное. Но это может быть только я :)

Спасибо за ваш тяжелый труд! И чтобы сделать нашу жизнь разработчиков лучше :)

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

Пример использования

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

Я считаю, что все мое замешательство было связано с [] против [some], и, конечно же, спасибо отличную работу :)

Я думаю, что я и @asylejmani здесь на одной странице, но я предполагаю, что вы говорите @gaearon о том, что мы, вероятно,

да. Если ваш компонент не обрабатывает обновления опоры, он обычно глючит. Дизайн useEffect заставляет вас противостоять ему. Вы, конечно, можете обойти это, но по умолчанию побуждает вас обрабатывать эти случаи. Этот комментарий хорошо это объясняет: https://github.com/facebook/react/issues/14920#issuecomment -470913287.

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

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

Все еще не знаете, следует ли просто отключить правило при предоставлении пустого массива?

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

>

Имеет смысл, спасибо

8 марта 2019 года в 15:27 Дэн Абрамов [email protected] написал:

Я думаю, что я и @asylejmani здесь на одной странице, но я предполагаю, что вы говорите @gaearon о том, что мы, вероятно,

да. Если ваш компонент не обрабатывает обновления опоры, он обычно глючит. Дизайн useEffect заставляет вас противостоять ему. Вы, конечно, можете обойти это, но по умолчанию побуждает вас обрабатывать эти случаи. Этот комментарий хорошо это объясняет: # 14920 (комментарий).

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

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

Все еще не знаете, следует ли просто отключить правило при предоставлении пустого массива?

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

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub или отключите обсуждение.

@aweary

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

Я не понимаю, что это значит. Вы можете привести пример?

Мы прошли через это сегодня с @threepointone . Вот краткое изложение:

Исправлено в правиле Lint

Посторонние useEffect зависимости

Правило не мешает вам больше добавлять "посторонние" депеши в useEffect , поскольку существуют допустимые сценарии.

Функции в том же компоненте, но определенные вне эффекта

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

Стоит исправить в пользовательском коде

Сброс состояния при изменении реквизита

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

«Моя нефункциональная ценность постоянна»

Крючки подталкивают вас к правильности везде, где это возможно. Если вы все же указываете зависимости (которые в некоторых случаях можно опустить), мы настоятельно рекомендуем включить даже те, которые, по вашему мнению, не изменятся. Да, в этом примере useDebounce задержка вряд ли изменится. Но это все равно ошибка, но Hook не справляется с ней. Это проявляется и в других сценариях. (Например, хуки гораздо более совместимы с горячей перезагрузкой, потому что каждое значение обрабатывается как динамическое.)

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

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Тогда это явно не может измениться, если вы не поместите его в рендер. (Что не было бы идиоматическим использованием вашего хука.) Но утверждение, что <Slider min={50} /> никогда не может измениться, на самом деле неверно - кто-то может легко изменить его на <Slider min={state ? 50 : 100} /> . На самом деле, кто-то мог это сделать:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Если кто-то переключит isCelsius в состояние, компонент, предполагающий, что min никогда не изменится, не сможет обновиться. В этом случае неочевидно, что Slider будет тем же самым (но это будет потому, что у него такая же позиция в дереве). Так что это серьезное оружие с точки зрения внесения изменений в код. Важным моментом в React является то, что обновления отображаются так же, как и исходные состояния (обычно вы не можете сказать, какие из них какие). Независимо от того, визуализируете ли вы значение prop B или переходите от значения prop A к B - оно должно выглядеть и вести себя одинаково.

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

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

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

«Значение моей функции постоянно»

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

Если это действительно постоянно , то указать его в DEPS не пострадали. Например, в случае, когда функция setState внутри настраиваемого хука возвращается вашему компоненту, а затем вы вызываете ее из эффекта. Правило lint недостаточно умен, чтобы понять подобное косвенное обращение. Но с другой стороны, любой может обернуть этот обратный вызов позже, прежде чем вернуться, и, возможно, сослаться на другую опору или состояние внутри него. Тогда это не будет постоянным! И если вам не удастся обработать эти изменения, у вас будут неприятные устаревшие ошибки свойств / состояний. Так что указать это по умолчанию лучше.

Однако ошибочно считать, что значения функций обязательно постоянны. Они чаще всего постоянны в классах из-за привязки методов, хотя это создает свой собственный диапазон ошибок . Но в целом любая функция, которая закрывает значение в функциональном компоненте, не может считаться постоянной. Правило lint теперь лучше подсказывает вам, что делать. (Например, переместить его внутрь эффекта - простейшее исправление - или заключить в оболочку useCallback .)

Есть проблема в противоположном спектре этого, когда вы получаете бесконечные циклы (значение функции всегда изменяется). Мы поймаем это в правиле lint сейчас, когда это возможно (в том же компоненте), и предложим исправление. Но сложно пройти что-то на несколько уровней ниже.

Вы все равно можете обернуть его в useCallback чтобы устранить проблему. Помните, что технически изменение функции допустимо , и вы не можете игнорировать этот случай, не рискуя ошибиться. Например, onChange={shouldHandle ? handleChange : null} или рендеринг foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> в том же месте. Или даже fetchComments который закрывает состояние родительского компонента. Это может измениться. С классами его поведение изменится незаметно, но ссылка на функцию останется прежней. Таким образом, ваш ребенок пропустит это обновление - у вас действительно нет другого выхода, кроме передачи ребенку дополнительных данных. С функциональными компонентами и useCallback сам идентификатор функции изменяется - но только при необходимости. Так что это полезное свойство, а не просто препятствие, которого следует избегать.

Мы должны добавить лучшее решение для обнаружения бесконечных асинхронных циклов. Это должно смягчить самый запутанный аспект. В будущем мы могли бы добавить для этого обнаружение. Вы также можете написать что-то вроде этого самостоятельно:

useWarnAboutTooFrequentChanges([deps]);

Это не идеально, и нам нужно больше подумать о том, как с этим справиться. Я согласен случаи , как это довольно противно. Исправление без нарушения правила заключалось бы в том, чтобы сделать rules статическим, например, изменив API на createTextInput(rules) и обернув register и unregister в useCallback . Еще лучше, удалите register и unregister и замените их отдельным контекстом, в котором вы помещаете только dispatch . Тогда вы можете гарантировать, что у вас никогда не будет другого идентификатора функции после его чтения.

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

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

Реакция на сложные изменения стоимости

Мне странно, почему в этом примере используется эффект для чего-то, что по сути является обработчиком событий. Ведение того же «журнала» (я предполагаю, что это может быть отправка формы) в обработчике событий кажется более подходящим. Это особенно верно, если мы подумаем о том, что происходит, когда компонент отключается. Что, если он отключится сразу после того, как эффект был запланирован? Такие вещи, как отправка формы, в этом случае не должны просто «не происходить». Таким образом, похоже, что эффект может быть неправильным выбором.

Тем не менее, вы все равно можете делать то, что пробовали, - вместо этого сделав fullName равным setSubmittedData({firstName, lastName}) , а затем [submittedData] - это ваша зависимость, из которой вы можете прочитать firstName и lastName .

Интеграция с императивным / устаревшим кодом

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


Надеюсь, я никого не забыл! Сообщите мне, если я сделал или что-то неясно. Вскоре мы постараемся превратить уроки из этого в некоторые документы.

@gaearon , спасибо, что (# 14920 (комментарий) от @trevorgithub) в своем заключительном итоговом комментарии. (Я, конечно, ценю, что было много отзывов от многих людей; я думаю, что мой первоначальный комментарий затерялся в разделе скрытых элементов где-то в середине комментариев к проблеме).

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

В случае проблем с «интеграцией с императивным / устаревшим кодом» может показаться, что сделать не так много. Как в таких случаях игнорировать это предупреждение? Наверное:
// eslint-disable-line react-hooks/exhaustive-deps

Извините, я пропустил это.

Если вы получаете некоторые данные через реквизиты, но не хотите использовать эти реквизиты до каких-либо явных изменений, это звучит как правильный способ моделирования - иметь производное состояние.

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

Обычно производное состояние не рекомендуется, но здесь оно похоже на то, что вам нужно. Самый простой способ реализовать это было бы примерно так:

const [currentGetData, setCurrentGetData] = useState(getData);
const [prevRefreshRequest, setPrevRefreshRequest] = useState(refreshRequest);

if (prevRefreshRequest !== refreshRequest) {
  setPrevRefreshRequest(refreshRequest);
  setCurrentGetData(getData);
}

useEffect(() => {
  currentGetData(someId);
}, [currentGetData, someId]);

Вам также нужно будет поместить useCallback вокруг getData которое вы передаете.

Обратите внимание, что в целом схема передачи асинхронных функций для выборки кажется мне сомнительной. Думаю, в вашем случае вы используете Redux, так что это имеет смысл. Но если бы функция async была определена в родительском компоненте, это было бы подозрительно, потому что у вас, вероятно, были бы условия гонки. У ваших эффектов нет очистки, так как вы можете узнать, когда пользователь выбирает другой идентификатор? Существует риск того, что запросы будут поступать не по порядку и устанавливать неправильное состояние. Так что об этом нужно помнить. Если выборка данных находится в самом компоненте, вы можете использовать функцию очистки эффекта, которая устанавливает флаг «игнорировать», чтобы предотвратить setState из ответа. (Конечно, перемещение данных во внешний кеш часто является лучшим решением - именно так будет работать и Suspense.)

Ведение того же «журнала» (я предполагаю, что это может быть отправка формы) в обработчике событий кажется более подходящим.

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

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

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

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

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

У меня есть удобный крючок для обертывания функций дополнительными параметрами и их передачи. Это выглядит так:

function useBoundCallback(fn, ...bound) {
  return useCallback((...args) => fn(...bound, ...args), [fn, ...bound]);
}

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

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

Упрощенный пример этого потока показан здесь: https://codesandbox.io/s/vvv36834k5 (нажатие «Go!» Показывает журнал консоли, который включает путь к компонентам)


Проблема в том, что я получаю 2 ошибки линтера с этим правилом:

У React Hook (X) отсутствует зависимость: «привязана». Либо включите его, либо удалите массив зависимостей

React Hook (X) имеет элемент распространения в массиве зависимостей. Это означает, что мы не можем статически проверить, переданы ли вы правильные зависимости.

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


Мысли:

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

Привет @gaearon , я только что получил это предупреждение, которое нигде не обсуждалось:

Accessing 'myRef.current' during the effect cleanup will likely read a different ref value because by this time React has already updated the ref. If this ref is managed by React, store 'myRef.current' in a variable inside the effect itself and refer to that variable from the cleanup function.

Я нахожу это сообщение немного сбивающим с толку. Я предполагаю, что он пытается предупредить меня, что текущее значение ref при очистке может отличаться от значения в теле эффекта. Верно?
Если это так, и, зная об этом, можно ли игнорировать это предупреждение?

Мой случай, если интересно: CodeSandbox
Контекст: некий пользовательский хук для извлечения данных. Я использую счетчик в справке, чтобы предотвратить как условия гонки, так и обновления не подключенных компонентов.

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

@aweary

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

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

@ davidje13

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

Пожалуйста, отправьте новую проблему для предложений по изменению правила lint.

Есть ли способ отключить это правило специально для мест, где используется оператор распространения?

Вы всегда можете // eslint-disable-next-line react-hooks/exhaustive-deps если думаете, что знаете, что делаете.

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

Напишите новый выпуск, пожалуйста.

@CarlosGines

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

да.

Если это так, и, зная об этом, можно ли игнорировать это предупреждение?

Эммм .. нет, если это приведет к ошибке. 🙂

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

Да, может быть, этот вариант использования допустим. Подать новый вопрос для обсуждения, пожалуйста?

Я собираюсь заблокировать эту проблему, поскольку мы получили достаточно отзывов, и они были учтены.

Общие вопросы и ответы: https://github.com/facebook/react/issues/14920#issuecomment -471070149

Если вы хотите глубоко погрузиться в useEffect и зависимости, это здесь: https://overrected.io/a-complete-guide-to-useeffect/

Скоро мы также добавим новые материалы в документы.

Если вы хотите что-то изменить в правиле или не уверены, что ваше дело является законным, сообщите о новой проблеме.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги