В React вы можете создавать свои собственные хуки!
Когда у вас есть компоненты, которые могут использоваться несколькими другими компонентами, их можно превратить в пользовательский хук.
Пользовательские хуки в React — это функции, созданные разработчиком для выноса повторяющейся логики из компонентов с использованием встроенных хуков React, таких как useState, useEffect, useRef, useCallback и других.
Основные характеристики пользовательских хуков
- Имя всегда начинается с
use— например,useWindowSize,useLocalStorage,useForm. Это соглашение необходимо, чтобы React мог распознавать, где используется хук, и правильно применять правила хуков. - Могут возвращать любые значения — состояние, функции, объекты, массивы. Например, хук может возвращать значение и функцию для его обновления, как
useState. - Используют другие хуки внутри себя — только так они могут управлять состоянием или эффектами.
- Повторно используются между компонентами — основная цель: избежать дублирования логики.
Зачем нужны пользовательские хуки?
- Инкапсуляция логики: Вместо того чтобы писать один и тот же
useEffectдля отслеживания размера окна в нескольких компонентах, вы создаётеuseWindowSizeи используете его везде. - Упрощение компонентов: Компоненты становятся чище и легче для понимания, так как сложная логика вынесена в отдельные хуки.
- Повторное использование без компонентной иерархии: В отличие от рендер-пропсов или 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 и архитектурой, ориентированной на переиспользуемую логику.