Хук useEffect в React — один из самых сложных и мощных хуков в функциональных компонентах, позволяющий выполнять побочные эффекты. Но что такое побочный эффект? В React под побочным эффектом понимаются любые операции, влияющие на что-то вне компонента, например вызов REST API, обновление DOM и т. д. Он будет выполняться после рендеринга компонента. Если вы знакомы с компонентами на основе классов, то ловушку useEffect можно легко понять как комбинацию методов жизненного цикла componentDidMount, componentDidUpdate и componentWillUnmount. В этой статье мы подробно рассмотрим использование хука useEffect с примерами.

Когда использовать хук эффекта

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

Некоторые из сценариев, когда вы хотите использовать хук эффекта

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

Синтаксис использованияЭффект

Синтаксис хука useEffect следующий:

useEffect(() => {
  // function body
}, [dependencies]);

Хук useEffect вызывается внутри функционального компонента и принимает два аргумента: функцию, представляющую тело эффекта, и необязательный массив зависимостей. Функция эффекта выполняется после рендеринга компонента. Когда массив зависимостей указан и значения аргументов в массиве зависимостей изменены, он сработает для повторного запуска эффекта.

Ниже приведен синтаксис хука useEffect с функцией очистки.

useEffect(() => {
  // effect function
  return () => {
    // cleanup function
  };
}, [dependencies]);

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

В одном и том же функциональном компоненте может быть несколько useEffect.

Как использовать эффект хук

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

import { useEffect } from "react";

function MyComponent() {
  useEffect(() => {
    // Your effect function here
  }, []);

  return <div>Hello World</div>;
}

Давайте посмотрим подробное использование useEffect с примерами,

Пример 1: (без передачи массива зависимостей)

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

import { useEffect } from "react";

function MyComponent() {
  useEffect(() => {
    console.log("This will be run every time the component renders");
  });

  return <div>Hello World</div>;
}

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

Пример 2: (Передача пустого массива зависимостей)

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

import { useEffect, useState } from "react";

function Posts() {
  const [posts, setposts] = useState([]);
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users/1/posts")
      .then((resp) => resp.json())
      .then((blogPosts) => setposts(blogPosts));
  }, []);

  return (
    <div className="App">
      {posts && posts.map((post) => <li>{post.title}</li>)}
    </div>
  );
}

export default Posts;

В приведенном выше примере мы извлекаем сообщения пользователя только один раз и отображаем их в DOM только один раз.

Некоторые другие сценарии, в которых вы будете передавать пустой массив зависимостей.

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

Пример 3: (передача аргументов в массив зависимостей)

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

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

import { useEffect, useState } from "react";

function Search() {
  const [posts, setposts] = useState([]);
  const [search, setsearch] = useState("");

  useEffect(() => {
    const filteredPosts = posts.filter((p) => p.title.includes(search));
    setposts(filteredPosts);
  }, [search]);

  return (
    <div className="App">
      {posts && (
        <input
          type="text"
          value={search}
          onChange={(e) => setsearch(e.target.value)}
        />
      )}
      {posts && posts.map((post) => <li>{post.title}</li>)}
    </div>
  );
}

export default Search;

Таким образом, всякий раз, когда пользователь вводит поисковый запрос, состояние search изменяется и вызывает повторный запуск эффекта.

Пример 4: (с функцией очистки)

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

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

import { useEffect, useRef, useState } from "react";

function Dropdown() {
  const ref = useRef(null);
  const [open, setOpen] = useState(false);
  const [options, setoptions] = useState([
    { key: 1, value: "Audi" },
    { key: 2, value: "BMW" },
    { key: 3, value: "Jaguar" },
    { key: 4, value: "Ferrari" }
  ]);
  const [option, setOption] = useState("");

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        setOpen(false);
      }
    };
    document.addEventListener("click", handleClickOutside);
    return () => document.removeEventListener("click", handleClickOutside);
  }, []);

  return (
    <div ref={ref}>
      <button onClick={() => setOpen(!open)}>Toggle Dropdown</button>
      {open && (
        <ul>
          {options.map((option) => (
            <li key={option.key} onClick={() => setOption(option.value)}>
              {option.value}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default Dropdown;

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

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

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

Повтор сеанса для разработчиков

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

Удачной отладки! Попробуйте использовать OpenReplay сегодня.

Как это не использовать (с примером)

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

Пример 1:

import { useEffect, useState } from "react";

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(count + 1);
  });

  return <div>{count}</div>;
}

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

Пример 2: (без передачи пустого массива зависимостей)

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

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

import { useEffect, useState } from "react";

function Posts() {
  const [posts, setposts] = useState([]);
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users/1/posts")
      .then((resp) => resp.json())
      .then((blogPosts) => setposts(blogPosts));
  });

  return (
    <div className="App">
      {posts && posts.map((post) => <li>{post.title}</li>)}
    </div>
  );
}

export default Posts;

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

Пример 3: (добавление ненужных зависимостей)

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

import { useEffect } from "react";

function TodoList({ todos, filter }) {
  useEffect(() => {
    console.log("filtering todos");
    // filter todos
  }, [todos, filter]);

  return <div>{/* todo list JSX */}</div>;
}

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

Пример 4: (не включая функции очистки)

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

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

import { useEffect, useRef, useState } from "react";

function Dropdown() {
  const ref = useRef(null);
  const [open, setOpen] = useState(false);
  const [options, setoptions] = useState([
    { key: 1, value: "Audi" },
    { key: 2, value: "BMW" },
    { key: 3, value: "Jaguar" },
    { key: 4, value: "Ferrari" }
  ]);
  const [option, setOption] = useState("");

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        setOpen(false);
      }
    };
    document.addEventListener("click", handleClickOutside);
    // No Cleanup function
  }, []);

  return (
    <div ref={ref}>
      <button onClick={() => setOpen(!open)}>Toggle Dropdown</button>
      {open && (
        <ul>
          {options.map((option) => (
            <li key={option.key} onClick={() => setOption(option.value)}>
              {option.value}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default Dropdown;

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

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

Заключение

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