5 советов: избегайте распространенных проблем с React Hooks

React.js
5 советов: избегайте распространенных проблем с React Hooks

5 советов: избегайте распространенных проблем с React Hooks

оригинальный:Кент CD odds.com/blog/react-…

В этой статье мы рассмотрим распространенные проблемы с React Hooks и способы их избежать.

React HooksвПредставлено в октябре 2018 г., а в феврале 2019 г.выпускать. С момента выпуска React Hooks многие разработчики использовали хуки в своих проектах, потому что хуки действительно в значительной степени упрощают наше понимание компонентов.stateа также副作用управление.

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

Проблема 1: спешите использовать хуки, прежде чем вы поймете

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

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

Решение первой проблемы:Внимательно прочитайте официальную документацию и FAQ. 📚

Проблема 2: Неиспользование (или игнорирование) плагина ESLint для React Hooks

В то же время, когда был выпущен React Hooks,eslint-plugin-react-hooksэтоESLintПлагин также был выпущен. Этот плагин содержит два правила проверки:rules of hooksа такжеexhaustive deps. Рекомендуемая конфигурация по умолчанию:rules of hooksУстановить какerrorуровень, воляexhaustive depsУстановить какwarningуровень.

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

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

Предположим, у нас есть 2 страницы: одна страница со списком собак🐶List, показывающий список кличек собак; одна из них является страницей сведений о конкретной собакеDetail. На странице списка нажмите на имя собаки, вы откроете страницу сведений о собаке.

Хорошо, на странице сведений о собаке у нас есть компонент, отображающий сведения о собаке.DogInfo, который получает собакуdogId, и согласноdogIdЗапросите API, чтобы получить соответствующие данные:

function DogInfo({dogId}) {
  const [dog, setDog] = useState(null)
  // imagine you also have loading/error states. omitting to save space...
  useEffect(() => {
    getDog(dogId).then(d => setDog(d))
  }, []) // 😱
  return <div>{/* render the dog info here */}</div>
}

В приведенном выше коде мыuseEffectСписок зависимостей — это пустой массив, потому что нам нужен только компонентmountтолько для инициирования запроса. Пока что этот код в порядке. Теперь, давайте скажем, наша страница детализации собаки немного изменилась, добавив список «связанных собак». В это время в нашем коде есть ошибка. Нажмите на один из "списка связанных собак", нашDogInfoКомпоненты не обновляются до соответствующих сведений о собаке, хотяDogInfoКомпонент был сброшенrender.

Текущая ситуация такова, что щелчок по элементу в списке «Связанные собаки» вызывает повторный запуск страницы сведений.render, и поставит нажатую собачкуdogIdперейти кDogInfoНо поскольку мыDogInfoизuseEffectЗависимости, записать пустой массив, в результате чегоuseEffectповторно выполняться не будет.

Итак, вот измененный код:

function DogInfo({dogId}) {
  const [dog, setDog] = useState(null)
  // imagine you also have loading/error states. omitting to save space...
  useEffect(() => {
    getDog(dogId).then(d => setDog(d))
  }, [dogId]) // ✅
  return <div>{/* render the dog info here */}</div>
}

Из этого каштана мы можем сделать такой ключевой вывод: еслиuseEffectЗависимость действительно никогда не меняется, поэтому добавьте эту зависимость вuesEffectВ массиве зависимостей проблем нет. В то же время, если вы думаете, что зависимость не изменится, а это действительно так, это поможет вам найти ошибку в вашем коде.

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

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

Решение второй проблемы: **Установите, используйте и соблюдайте требования ESLint**.

Проблема 3: (неправильно) думать о хуках с точки зрения жизненного цикла компонента

До React Hooks мы могли использовать встроенные методы жизненного цикла компонентов в компонентах класса, чтобы сообщить React, когда и что он должен делать:

class LifecycleComponent extends React.Component {
  constructor() {
    // initialize component instance
  }
  componentDidMount() {
    // run this code when the component is first added to the page
  }
  componentDidUpdate(prevProps, prevState) {
    // run this code when the component is updated on the page
  }
  componentWillUnmount() {
    // run this code when the component is removed from the page
  }
  render() {
    // call me anytime you need some react elements...
  }
}

После выпуска Hooks не проблема писать компоненты класса, подобные приведенным выше (и это будет хорошо в обозримом будущем), и этот способ компонентов класса существует уже много лет. Крючки приносят ряд преимуществ, одним из моих любимых является (useEffect), хуки делают компоненты более совместимымидекларативныйграмматика. С помощью хуков мы можем более интуитивно сказать React: «Когда происходит изменение, я хочу выполнить соответствующее действие» вместо того, чтобы различать «в каком жизненном цикле компонента должно быть выполнено действие».

Итак, теперь наш код выглядит так:

function HookComponent() {
  React.useEffect(() => {
    // This side effect code is here to synchronize the state of the world
    // with the state of this component.
    return function cleanup() {
      // And I need to cleanup the previous side-effect before running a new one
    }
    // So I need this side-effect and it's cleanup to be re-run...
  }, [when, any, ofThese, change])
  React.useEffect(() => {
    // this side effect will re-run on every single time this component is
    // re-rendered to make sure that what it does is never stale.
  })
  React.useEffect(() => {
    // this side effect can never get stale because
    // it legitimately has no dependencies
  }, [])
  return /* some beautiful react elements */
}

Ryan Florence Объясните изменение образа мышления с другой стороны.

Одна из основных причин, почему мне нравится эта функция (useEffect), заключается в том, что она помогает мне избежать множества ошибок. В процессе разработки, основанном на компонентах класса в прошлом, я обнаружил, что многие случаи внесения ошибок были вызваны тем, что я забылcomponentDidUpdateиметь дело сpropилиstateменяется; в другом случае я нахожусь вcomponentDidUpdateобработанныйpropилиstateизменения, но забыл отменить побочные эффекты, вызванные предыдущим изменением. Например, вы инициируете HTTP-запрос, но до того, как HTTP завершится, свойство или состояние компонента изменится, вам обычно следует отменить HTTP-запрос.

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

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

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

Проблема 4: слишком много беспокоитесь о производительности

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

function MyComponent() {
  function handleClick() {
    console.log('clicked some other component')
  }
  return <SomeOtherComponent onClick={handleClick} />
}

Обычно они беспокоятся по двум причинам:

  1. мы вMyComponentфункция определена внутриhandleClick, а это значит, что каждый разMyComponentПри рендеринге он переопределит другойhandleClick
  2. Каждый раз, когда мы визуализируем, мы будем ставить новыйhandleClickперешел кSomeOtherComponent, а это значит, что мы не можем пройтиReact.memo,React.PureComponentилиshouldComponentUpdateоптимизироватьSomeOtherComponentпроизводительность, которая вызываетSomeOtherComponentмного ненужных повторных рендеров

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

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

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

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

Также помните,Производственная версия React работает намного лучше, чем версия для разработки..

Четвертое решение проблемы:Помните, что React по своей природе быстр, не беспокойтесь и не оптимизируйте свою производительность преждевременно..

Проблема 5: Слишком много внимания уделяется тестированию хуков

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

Цитировать мою собственную статьюКак насчет тестирования кода с React Hooks?, если ваш тестовый код выглядит так:

test('setOpenIndex sets the open index state properly', () => {
  // using enzyme
  const wrapper = mount(<Accordion items={[]} />)
  expect(wrapper.state('openIndex')).toBe(0)
  wrapper.instance().setOpenIndex(1)
  expect(wrapper.state('openIndex')).toBe(1)
})

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

test('can open accordion items to see the contents', () => {
  const hats = {title: 'Favorite Hats', contents: 'Fedoras are classy'}
  const footware = {
    title: 'Favorite Footware',
    contents: 'Flipflops are the best',
  }
  const items = [hats, footware]
  // using React Testing Library
  const {getByText, queryByText} = render(<Accordion items={items} />)
  expect(getByText(hats.contents)).toBeInTheDocument()
  expect(queryByText(footware.contents)).toBeNull()
  fireEvent.click(getByText(footware.title))
  expect(getByText(footware.contents)).toBeInTheDocument()
  expect(queryByText(hats.contents)).toBeNull()
})

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

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

Вы можете узнать больше о тестировании из этих двух статей:Тестирование деталей реализацииа такжеAvoid the Test User.

Хорошо, решение этой проблемы:Избегайте тестирования деталей реализации компонентов.

Суммировать

Сказав все это, вот несколько советов, которые помогут вам избежать распространенных проблем с хуками:

  1. Внимательно прочитайте официальную документацию по хукам, а также раздел FAQ.
  2. Устанавливайте, используйте и наблюдайтеeslint-plugin-react-hooksПлагин ESLint
  3. Забудьте о том, как думать о жизненном цикле компонентов. Правильная осанка: как синхронизировать побочные эффекты и состояние компонента.
  4. React сам по себе выполняется очень быстро, поэтому перед преждевременной оптимизацией производительности вы должны хорошенько изучить соответствующие точки знаний.
  5. Избегайте тестирования деталей реализации компонента, вместо этого сосредоточьтесь на вводе и выводе компонента.

коммерческое время

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