React — это популярная библиотека JavaScript для создания пользовательских интерфейсов, а ее API-интерфейс хуков позволяет разработчикам использовать состояние и другие функции React без написания компонента класса. Одним из наиболее полезных хуков является useEffect, который позволяет функциональному компоненту выполнять побочные эффекты, такие как выполнение вызовов API или изменение модели DOM.

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

Основное использование

Итак, что такое useEffect и как его использовать в компоненте React?

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

Вот пример использования useEffect для вывода сообщения на консоль после рендеринга компонента:

import { useEffect } from 'react';

function ExampleComponent() {
  useEffect(() => {
    console.log('The component has rendered!');
  });

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

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

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

import { useEffect, useState } from "react";

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

  useEffect(() => {
    console.log(`The count is ${count}`);
  }, [count]); // Only run the effect if count changes

  return (
    <div>
      <button onClick={() => setCount((count) => count + 1)}>
        Increment count
      </button>
      <p>The current count is {count}</p>
    </div>
  );
}

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

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

Расширенное использование

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

Выполнение побочных эффектов

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

Вот пример использования useEffect для получения данных из API и обновления состояния компонента с помощью ответа:

import { useEffect, useState } from 'react';

function ExampleComponent() {
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    async function fetchTodos() {
      const response = await fetch('/api/todos');
      const data = await response.json();
      setTodos(data);
    }

    fetchTodos();
  }, []);  // Run the effect only once

  return (
    <div>
      {todos.map(todo => (
        <p key={todo.id}>{todo.text}</p>
      ))}
    </div>
  );
}

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

Настройка подписок

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

Вот пример использования useEffect для настройки подписки на поток данных в реальном времени:

import { useEffect, useState } from "react";

function ExampleComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const subscription = someRealTimeDataFeed.subscribe(setData);

    return () => {
      subscription.unsubscribe(); // Clean up the subscription on unmount
    };
  }, []); // Run the effect only once

  return (
    <div>
      {data ? <p>The current data is: {data}</p> : <p>Loading data...</p>}
    </div>
  );
}

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

Уборка после эффектов

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

Вот пример использования useEffect для установки заголовка документа и очистки эффекта после его запуска:

import { useEffect } from "react";

function ExampleComponent({ title }) {
  useEffect(() => {
    document.title = title;

    return () => {
      document.title = "Default title"; // Reset the title on unmount
    };
  }, [title]); // Only run the effect if the title prop changes

  return (
    <div>
      <h1>{title}</h1>
    </div>
  );
}

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

Обработка ошибок

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

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

import { useEffect, useState } from "react";

function ExampleComponent() {
  const [todos, setTodos] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchTodos() {
      try {
        const response = await fetch("/api/todos");
        const data = await response.json();
        setTodos(data);
      } catch (err) {
        setError(err);
      }
    }

    fetchTodos();
  }, []); // Run the effect only once

  if (error) {
    return <p>An error occurred: {error.message}</p>;
  }

  return (
    <div>
      {todos.map((todo) => (
        <p key={todo.id}>{todo.text}</p>
      ))}
    </div>
  );
}

В этом примере хук useEffect используется для получения списка задач из API и обновления состояния компонента данными ответа. Если во время выборки возникает ошибка, она перехватывается, и состояние компонента error обновляется с учетом ошибки. Затем компонент отобразит сообщение об ошибке вместо списка задач.

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

Советы и рекомендации

Вот несколько советов и рекомендаций, которые следует учитывать при использовании useEffect в компонентах React:

Избегайте ненужного повторного рендеринга

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

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

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

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

import { useEffect, useState } from "react";

function Products() {
  const [products, setProducts] = useState([]);

  const [page, setPage] = useState(1);
  const [sizePerPage, setSizePerPage] = useState(10);
  const [sortField, setSortField] = useState("price");
  const [sortOrder, setSortOrder] = useState("asc");

  useEffect(() => {
    async function fetchProducts() {
      const data = {
        page,
        sizePerPage,
        sortField,
        sortOrder,
      };

      const options = {
        method: "POST",
        body: JSON.stringify(data),
        headers: {
          "Content-Type": "application/json",
        },
      };

      const response = await fetch("/api/products", options);
      const products = await response.json();
      setProducts(products);
    }

    fetchProducts();
  }, [page, sizePerPage, sortField, sortOrder]);

  return (
    <ProductsTable
      page={page}
      sizePerPage={sizePerPage}
      sortField={sortField}
      sortOrder={sortOrder}
      products={products}
    />
  );
}

Оптимизация производительности

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

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

Представьте, что на странице с таблицей продуктов, которую мы только что написали, также есть форма создания нового продукта, обернутая React.memo компонентом более высокого порядка. Каждый раз, когда создается успешный новый продукт, нам нужно обновлять таблицу продуктов. Вместо того, чтобы повторно определять функцию, которая вызывает продукты как в useEffect, так и в onSubmitSuccess реквизите, который мы передаем компоненту формы, мы можем определить его с помощью useCallback и использовать его как в useEffect, так и передать его дочернему компоненту в качестве реквизита. .

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

function Products() {
  const [products, setProducts] = useState([]);

  const [page, setPage] = useState(1);
  const [sizePerPage, setSizePerPage] = useState(10);
  const [sortField, setSortField] = useState("price");
  const [sortOrder, setSortOrder] = useState("asc");

  const fetchProducts = useCallback(async () => {
    const data = {
      page,
      sizePerPage,
      sortField,
      sortOrder,
    };

    const options = {
      method: "POST",
      body: JSON.stringify(data),
      headers: {
        "Content-Type": "application/json",
      },
    };

    const response = await fetch("/api/products", options);
    const products = await response.json();

    setProducts(products);
  }, [page, sizePerPage, sortField, sortOrder]);

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

  return (
    <>
      <ProductForm onSubmitSuccess={fetchProducts} />

      <ProductsTable
        page={page}
        sizePerPage={sizePerPage}
        sortField={sortField}
        sortOrder={sortOrder}
        products={products}
      />
    </>
  );
}

Структурируйте и организуйте свой код

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

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

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

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

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

function Products() {
  // State of messages
  const [messages, setMessages] = useState([]);
  
  const [products, setProducts] = useState([]);
  const [page, setPage] = useState(1);
  const [sizePerPage, setSizePerPage] = useState(10);
  const [sortField, setSortField] = useState("price");
  const [sortOrder, setSortOrder] = useState("asc");

  const fetchProducts = useCallback(async () => {
    const data = {
      page,
      sizePerPage,
      sortField,
      sortOrder,
    };

    const options = {
      method: "POST",
      body: JSON.stringify(data),
      headers: {
        "Content-Type": "application/json",
      },
    };

    const response = await fetch("/api/products", options);
    const products = await response.json();

    setProducts(products);
  }, [page, sizePerPage, sortField, sortOrder]);

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

  useEffect(() => {
    const subscription = someRealTimeDataFeed.subscribe(setMessages);

    return () => {
      subscription.unsubscribe(); // Clean up the subscription on unmount
    };
  }, []); // Run the effect only once

  return (
    <>
      <Messages messages={messages} />

      <ProductForm onSubmitSuccess={fetchProducts} />

      <ProductsTable
        page={page}
        sizePerPage={sizePerPage}
        sortField={sortField}
        sortOrder={sortOrder}
        products={products}
      />
    </>
  );
}

На этом мы завершаем обсуждение useEffect в React. В следующем разделе мы обобщим основные моменты, затронутые в этой статье.

Заключение

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

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

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