Продвинутые React Hooks: подробный разбор useEffect

С выходом React 16.8 в 2019 году React Hooks наконец-то стали доступны для использования в пригодных для эксплуатации приложениях. Хуки позволяют React-разработчикам делать функциональные компоненты с отслеживанием состояния и не использовать классовые компоненты.

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

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

В статье мы рассмотрим следующие вопросы и темы:

  • Что такое React Hooks?
  • Что представляет собой хук useEffect?
  • Использование массива зависимостей с хуком useEffect.
  • Запуск функции useEffect с изменением состояния или пропсов.
  • Что дальше?

Что такое React Hooks?

В React есть функциональные компоненты, которые не содержат внутреннего состояния. А ещё есть классовые компоненты, добавляющие в программу логику с отслеживанием состояния и позволяющие использовать методы жизненного цикла.

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

Композиция старых и новых приложений на React

И вот была предложена альтернатива в виде React Hooks.

React Hooks  —  это функции, которые позволяют подцепиться к состоянию и жизненному циклу React из функциональных компонентов. Это даёт возможность использовать React без классов, которые многим не нравятся из-за их зависимости от вызовов this JavaScript. А главное  —  хуки включаются по желанию и работают с имеющимся кодом.

Существует несколько встроенных хуков (таких как useEffect или useState), которые ссылаются на стандартные внутренние состояния. Есть также возможность создавать пользовательские хуки, ссылающиеся на выбранные состояния.

Вот самые популярные встроенные хуки:

  • useState: возвращает значение с отслеживанием состояния и функцию для его редактирования. Этот хук эквивалентен this.state и this.setState, которые есть в классовых компонентах.
  • useEffect: выполняет побочные эффекты из функциональных компонентов. Очередной побочный эффект выполняется после повторного отображения, делая возможным ограниченное итеративное поведение в React.
  • useContext: принимает объект контекста и возвращает текущее значение контекста. Запускает повторное отображение всякий раз при обновлении ближайшего MyContext.Provider.

Преимущества React Hooks:

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

Сравнение реализации компонентов с классами и хуками

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

Вот старый код на React без хуков:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: ''
    };
  }

  componentDidMount() {
    this.loadMessage();
  }

  loadMessage = async () => {
    try {
      const response = await axios.get('https://json.versant.digital/.netlify/functions/fake-api/message');
      this.setState({ message: response.data });
    } catch (e) {
      this.setState({ message: e.message });
    }
  };

  render() {
    return <h1>{this.state.message}</h1>
  }
}

В этом коде используются метод componentDidMount и this.setState для обращения к состоянию сообщения и манипулирования им. Заменим их на хуки useEffect и useState.

Для этого внесём в код следующие изменения:

  • Задействуем хук useState для управления состоянием сообщения.
  • Метод componentDidMount заменим на хук useEffect.
  • Установим состояние сообщения с помощью функции, предоставляемой хуком useState.

Вот как теперь выглядит то же самое приложение на React с хуками:

import React, { useEffect, useState } from 'react';
import axios from 'axios';

const INITIAL_MESSAGE = '';

const App = () => {
  const [message, setMessage] = useState(INITIAL_MESSAGE);

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

  const loadMessage = async () => {
    try {
      const response = await axios.get('https://json.versant.digital/.netlify/functions/fake-api/message');
      setMessage(response.data);
    } catch (e) {
      setMessage(e.message);
    }
  };

  return <h1>{message}</h1>;
};

export default App;

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

Что представляет собой хук useEffect?

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

Хук useEffect позволяет запускать дополнительный код после того, как React обновит DOM.

useEffect отчасти заменяет события жизненного цикла React. Он способен воспроизводить поведение методов componentDidMount, componentDidUpdate и componentWillUnmount.

То есть можно реагировать на изменения в любом компоненте, в котором содержится хук useEffect.

Синтаксис

Хук useEffect принимает два аргумента:

useEffect(() => {

// какой-то код

  }, [someProp, someState]);

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

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

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

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

Вот типичные сценарии применения useEffect:

  • Добавление слушателя событий для кнопки.
  • Получение данных из API при монтировании компонента.
  • Выполнение действия при изменении состояния или пропсов.
  • Очистка слушателей событий при размонтировании компонента.

В каждом из этих случаев useEffect используется вместо метода жизненного цикла.

Использование массива зависимостей с хуком useEffect Hook

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

Приведённый ниже код выводит на страницу полученное сообщение, но не использует массив зависимостей.

import React, { useEffect, useState } from 'react';

const INITIAL_STATE = '';

const App = () => {
  const [message, setMessage] = useState(INITIAL_STATE);

  useEffect(() => {
    loadMessage();
  });

  const loadMessage = () => {
    console.log('>> Loading message <<');
    try {
      fetch('https://json.versant.digital/.netlify/functions/fake-api/message')
        .then(res => res.json())
        .then(message => {
          setMessage(message);
        });
    } catch (e) {}
  };

  console.log(`>> Current message is: ${message || 'EMPTY'} <<`);

  return <h1>{message}</h1>;
};

export default App;

Всё вроде хорошо, но при открытии консоли браузера обнаруживается, что сообщение >> Loading Message << несколько раз перезапускалось.

>> Current message is: EMPTY <<

>> Loading message <<

>> Current message is: Master React Hooks! <<

>> Loading message <<

>> Current message is: Master React Hooks! <<

Сообщение не изменилось, поэтому оптимизируем всё это: сообщения будут загружаться и получаться только раз.

Секрет в добавлении пустого массива зависимостей. Строки 8–10 просто заменяются на:

useEffect(() => {

  loadMessage();

}, []);

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

Запуск функции useEffect с изменением состояния или пропсов

Массивы зависимостей также полезны при создании адаптивных приложений. Но это должны быть заполненные массивы.

Возьмём приложение на React, позволяющее пользователям устанавливать псевдоним, вводя его в поле ввода. После установки псевдонима приложение получает персонализированное приветственное сообщение из внешнего API.

import React, { useEffect, useState } from 'react';

const App = () => {
  const [message, setMessage] = useState('');
  const [name, setName] = useState('');
  const [isTyping, setIsTyping] = useState(false);

  useEffect(() => {
    // Мы не хотим получать сообщение, когда пользователь набирает текст
    // Пропускаем эффект, когда isTyping имеет значение true
    if (isTyping) {
      return;
    }
    loadMessage(name);
  }, [name, isTyping]);

  const loadMessage = nickName => {
    try {
      fetch(
        `https://json.versant.digital/.netlify/functions/fake-api/message/name/${nickName}`
      )
        .then(res => res.json())
        .then(message => {
          setMessage(message);
        });
    } catch (e) {}
  };

  const handleNameFormSubmit = event => {
    event.preventDefault();
    setIsTyping(false);
  };

  return (
    <div className="App">
      <form onSubmit={handleNameFormSubmit}>
        <input
          value={name}
          onChange={event => {
            setIsTyping(true);
            setName(event.target.value);
          }}
        />
        <button>Set nickname</button>
      </form>
      <h1>{message}</h1>
    </div>
  );
};

export default App;

В строках 8–15 массив зависимостей содержит isTyping. useEffect запускается каждый раз, когда происходит изменение в любом из этих состояний. Но пользователю не нужна загрузка сообщения, пока он заполняет форму или нажимает на кнопку Set nickname («Установить псевдоним»).

Здесь на помощь приходит состояние isTyping. Если isTyping установлен, мы возвращаемся из функции useEffect и не запускаем её (строки 11-13).

Когда пользователь отправляет заполненную им форму, значение isTyping становится false. Хук обнаруживает изменение в состоянии isTyping и запускается снова. Теперь он минует оператор if и вызывает функцию для инициирования запроса на получение сообщения. На этот раз это функция loadMessage.

Вот и всё, мы только что создали метод componentDidUpdate с помощью хуков!

Что дальше?

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

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

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи The Educative Team: Advanced React Hooks: Deep Dive into useEffect Hook