React Пользовательские хуки

В React вы можете создавать свои собственные хуки!

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

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


Основные характеристики пользовательских хуков

  • Имя всегда начинается с use — например, useWindowSize, useLocalStorage, useForm. Это соглашение необходимо, чтобы React мог распознавать, где используется хук, и правильно применять правила хуков.
  • Могут возвращать любые значения — состояние, функции, объекты, массивы. Например, хук может возвращать значение и функцию для его обновления, как useState.
  • Используют другие хуки внутри себя — только так они могут управлять состоянием или эффектами.
  • Повторно используются между компонентами — основная цель: избежать дублирования логики.

Зачем нужны пользовательские хуки?

  1. Инкапсуляция логики: Вместо того чтобы писать один и тот же useEffect для отслеживания размера окна в нескольких компонентах, вы создаёте useWindowSize и используете его везде.
  2. Упрощение компонентов: Компоненты становятся чище и легче для понимания, так как сложная логика вынесена в отдельные хуки.
  3. Повторное использование без компонентной иерархии: В отличие от рендер-пропсов или HOC, хуки не требуют изменения структуры JSX и не создают лишних уровней в дереве.

Создание собственного хука

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

В следующем коде мы загружаем данные с URL и отображаем их.

Мы воспользуемся сервисом JSONPlaceholder для получения фиктивных данных.

Больше информации о получении данных можно найти в разделе "API Fetch JavaScript".

Пример

Используем сервис JSONPlaceholder для получения фейковых заголовков и отображаем их:


import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';

const Home = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos")
      .then(res => res.json())
      .then(data => setData(data));
  }, []);

  return (
    <>
      {data &&
        data.map(item => (
          <p key={item.id}>{item.title}</p>
        ))
      }
    </>
  );
};

createRoot(document.getElementById('root')).render(
  <Home />
);

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

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

Имя файла должно начинаться с use и заканчиваться .js, а располагаться он должен в той же директории, что и компонент.

Назовём файл useFetch.js.

Пример

Переместим компонент получения данных в новый файл:


// useFetch.js
import { useState, useEffect } from "react";

const useFetch = (url) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => setData(data));
  }, [url]);

  return [data];
};

export default useFetch;

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

Пример

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


// main.jsx
import { createRoot } from 'react-dom/client';
import useFetch from "./useFetch";

const Home = () => {
  const [data] = useFetch("https://jsonplaceholder.typicode.com/todos");

  return (
    <>
      {data &&
        data.map(item => (
          <p key={item.id}>{item.title}</p>
        ))
      }
    </>
  );
};

createRoot(document.getElementById('root')).render(
  <Home />
);

Объяснение примера:

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

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

Наконец, мы возвращаем наши данные из хука.

В файле main.jsx мы импортируем наш хук useFetch и используем его точно так же, как и любой другой хук. Именно здесь мы передаем URL для получения данных.

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


Еще примеры

Пример

useWindowSize


import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

Использование в компоненте:


function MyComponent() {
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>Ширина окна: {width}px</p>
      <p>Высота окна: {height}px</p>
    </div>
  );
}

Пример

useLocalStorage


import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

Использование в компоненте:


function ThemeSwitcher() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Текущая тема: {theme}
    </button>
  );
}


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

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

Преимущества

  • Уменьшение дублирования кода
  • Повышение читаемости и поддерживаемости
  • Легкость тестирования (можно тестировать логику отдельно от компонентов)
  • Возможность публикации и переиспользования в других проектах

На текущий момент пользовательские хуки остаются ключевой частью современной React-разработки, особенно в сочетании с TypeScript и архитектурой, ориентированной на переиспользуемую логику.