12 важнейших правил ESLint для React

Автор Джо Аттарди✏️

Введение

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

В этом посте мы рассмотрим эти правила и плагины ESLint, в том числе применительно к крючкам. Вот несколько быстрых ссылок, по которым вы сможете перейти:

  • Правила React Hooks (eslint-plugin-react-hooks)
    • react-hooks/rules-of-hooks
    • react-hooks/exhaustive-deps
  • Правила React (eslint-plugin-react)
    • react/button-has-type
    • react/prop-types
    • react/require-default-props
    • react/no-array-index-key
    • react/react-in-jsx-scope
    • react/jsx-uses-react
    • react/display-name
    • react/no-danger-with-children
    • react/jsx-no-bind

Правила React Hooks (eslint-plugin-react-hooks)

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

react-hooks/rules-of-hooks

Это правило принуждает компоненты следовать правилам Hooks при использовании Hooks. Правила подробно обсуждаются в документации по React, но есть два правила, которым необходимо следовать при использовании Hooks:

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

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

react-hooks/exhaustive-deps

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

Это правило хорошо помогает найти потенциальные ошибки, связанные с зависимостями, но есть некоторые ограничения:

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

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

Детали этого правила могут занять целую статью. Для более глубокого изучения этого правила и правильного его использования см. статью Понимание предупреждения о линтинге React exhaustive-deps здесь, в блоге LogRocket.

Правила React (eslint-plugin-react)

Этот плагин содержит гораздо больше правил (100 правил на момент написания статьи), которые относятся к ядру React. Большинство правил охватывают общую практику React, а другие — вопросы, связанные с синтаксисом JSX. Давайте рассмотрим некоторые из наиболее полезных.

react/button-has-type

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

Если атрибут type не указан, кнопка по умолчанию принимает тип submit. Это может вызвать проблемы для кнопок, которые исходят из элемента form. Нажатие на такую кнопку внутри формы приведет к потенциально нежелательной отправке формы.

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

Это правило принуждает все кнопки явно иметь атрибут type — даже те, которые предназначены для отправки формы. Явное указание позволяет избежать непреднамеренных отправок и четко определить смысл кода.

react/prop-types

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

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

Эти два подхода подробно рассматриваются в статье Сравнение TypeScript и PropTypes в приложениях React, написанной Диллионом Мегидой.

react/require-default-props

В зависимости от компонента, некоторые реквизиты могут быть обязательными, а другие — необязательными. Если необязательный реквизит не передан компоненту, он будет undefined. Это может быть ожидаемо, но может привести к ошибкам, если значение не проверяется.

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

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

defaultProps

Эта стратегия ожидает, что функциональный компонент имеет объект defaultProps с параметрами по умолчанию.

const MyComponent = ({ action }) => { ... }

MyComponent.propTypes = {
  Action: PropTypes.string;
};

MyComponent.defaultProps = {
  action: 'init'
};
Вход в полноэкранный режим Выйти из полноэкранного режима

defaultArguments

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

const MyComponent = ({ action = 'init' }) => { ... }

MyComponent.propTypes = {
  Action: PropTypes.string;
};
Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы используете стратегию defaultArguments, не должно быть объекта defaultProps. Если он есть, это правило не сработает.

react/no-array-index-key

При рендеринге списка элементов в React мы обычно вызываем map на массиве, и функция отображения возвращает компонент. Чтобы отслеживать каждый элемент в списке, React требует, чтобы эти компоненты имели свойство key.

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

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

react/react-in-jsx-scope

Рассмотрим этот простой компонент React:

const Greeter = ({ name }) => <div>Hello {name}!</div>;
Вход в полноэкранный режим Выход из полноэкранного режима

На объект React вообще не ссылаются. Однако React все равно должен быть импортирован, иначе вы столкнетесь с ошибкой. Это связано с процессом транспиляции JSX. Браузеры не понимают JSX, поэтому в процессе сборки (обычно с помощью такого инструмента, как Babel или TypeScript) элементы JSX преобразуются в правильный JavaScript.

Этот сгенерированный код JavaScript вызывает React.createElement вместо элементов JSX. Приведенный выше компонент может быть преобразован в нечто подобное:

const Greeter = ({ name }) => React.createElement("div", null, "Hello ", name, "!");
Вход в полноэкранный режим Выход из полноэкранного режима

Ссылки на React здесь являются причиной того, что React все равно должен быть импортирован. Это правило гарантирует, что все файлы с JSX-разметкой (не обязательно даже компоненты React) имеют React в области видимости (обычно через вызов import или require).

react/jsx-uses-react

Постоянный импорт React необходим для правильной транспозиции, но когда ESLint просматривает файл, это все еще JSX, поэтому он не увидит нигде ссылки на React. Если в проекте используется правило no-unused-vars, это приведет к ошибке, поскольку React импортирован, но нигде не используется.

Это правило отлавливает такую ситуацию и предотвращает ошибку no-unused-vars при импорте React.

react/display-name

Для правильного отладочного вывода все компоненты React должны иметь отображаемое имя. Во многих случаях это не требует дополнительного кода. Если компонент является именованной функцией, отображаемое имя будет именем функции. В приведенных ниже примерах отображаемое имя компонента будет MyComponent.

  • const MyComponent = () => { ... }.
  • const MyComponent = function() { return ...; }.
  • export default function MyComponent() { return ...; }.

В некоторых случаях автоматическое отображение имени теряется. Обычно это происходит, когда объявление компонента обернуто другой функцией или компонентом более высокого порядка, как в двух приведенных ниже примерах:

  • const MyComponent = React.memo(() => { ... });
  • const MyComponent = React.forwardRef((props, ref) => { ... });.

Имя MyComponent привязывается к новому «внешнему» компоненту, возвращаемому memo и forwardRef. Сам компонент теперь не имеет отображаемого имени, что приведет к неудаче этого правила.

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

const MyComponent = React.memo(() => { ... });
MyComponent.displayName = 'MyComponent';
Войти в полноэкранный режим Выход из полноэкранного режима

react/no-children-prop

Компоненты React принимают специальный параметр children. Значением этого свойства будет любое содержимое внутри открывающего и закрывающего тегов элемента. Рассмотрим этот простой компонент MyList:

const MyList = ({ children }) => {
  return <ul>{children}</ul>;
};
Вход в полноэкранный режим Выйти из полноэкранного режима

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

<MyList>
  <li>item1</li>
  <li>item2</li>
</MyList>
Войти в полноэкранный режим Выход из полноэкранного режима

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

<MyList children={<li>item1</li><li>item2</li>} />
Войти в полноэкранный режим Выйти из полноэкранного режима

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

<MyList children={<><li>item1</li><li>item2</li></>} />
Войти в полноэкранный режим Выход из полноэкранного режима

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

Это в основном стилистический выбор/шаблон, но он предотвращает случайную передачу как явного реквизита children, так и дочерних элементов:

<MyList children={<><li>item1</li><li>item2</li></>}>
  <li>item3</li>
  <li>item4</li>
</MyList>
Вход в полноэкранный режим Выход из полноэкранного режима

В этом случае дочерние элементы (item3 и item4) будут отображены, но item1 и item2 не будут. Это правило гарантирует, что дочерние элементы передаются только идиоматическим способом, как дочерние JSX-элементы.

react/no-danger-with-children

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

Реквизит ожидает объект со свойством __html, значением которого является необработанная строка HTML. Эта строка будет установлена в качестве innerHTML.

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

Это правило приводит в исполнение аналогичное правило. Если dangerouslySetInnerHTML используется с дочерними элементами, правило lint не сработает. Гораздо лучше отлавливать эти ошибки при линтинге или во время сборки, а не по сообщениям пользователей после развертывания приложения!

react/jsx-no-bind

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

Когда какая-либо функция определена внутри компонента, она будет новым объектом функции при каждом рендеринге. Это означает, что при каждом повторном рендеринге компонента функция будет считаться измененной. Даже при использовании React.memo, компонент будет перерендериваться.

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

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

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

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

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

Подведение итогов

Рассмотренные здесь правила — это лишь некоторые из тех, которые предоставляет плагин eslint-plugin-react. Некоторые правила могут быть излишне строгими, но большинство из них также имеют опции настройки, позволяющие сделать их менее строгими.

Есть еще один очень полезный плагин ESLint, посвященный JSX и практикам доступности: eslint-plugin-jsx-a11y. Правила этого плагина проверяют вашу разметку JSX, чтобы убедиться, что соблюдается хорошая практика доступности HTML.

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


Полная видимость производственных приложений React

Отладка приложений React может быть сложной задачей, особенно когда пользователи сталкиваются с проблемами, которые трудно воспроизвести. Если вы заинтересованы в мониторинге и отслеживании состояния Redux, автоматическом выявлении ошибок JavaScript, отслеживании медленных сетевых запросов и времени загрузки компонентов, попробуйте LogRocket.

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

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

Оцените статью
devanswers.ru
Добавить комментарий