Собственные хуки
Собственный хук — это обычная функция, имя которой начинается с use и внутри которой вызываются другие хуки. В него выносят логику, повторяющуюся в нескольких компонентах: загрузку данных, подписку на событие, работу с localStorage. Компонент становится короче, а логику можно использовать повторно.
Хук для загрузки данных
В блоге и список статей, и страница статьи, и комментарии загружают данные одинаково: запрос, загрузка, ошибка. Соберём это в хук. Создайте папку и файл src/hooks/useFetch.js:
// src/hooks/useFetch.js
import { useEffect, useState } from 'react';
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
setLoading(true);
setError(null);
fetch(url)
.then((response) => {
if (!response.ok) throw new Error(`Ошибка ${response.status}`);
return response.json();
})
.then((json) => {
if (!ignore) setData(json);
})
.catch((err) => {
if (!ignore) setError(err.message);
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => {
ignore = true;
};
}, [url]);
return { data, loading, error };
}Хук возвращает объект с тремя значениями. При смене url запрос повторяется.
Использование
Компонент PostList из урока про загрузку данных сокращается до нескольких строк:
// src/components/PostList.jsx
import { useFetch } from '../hooks/useFetch.js';
export default function PostList() {
const { data: posts, loading, error } = useFetch(
'https://jsonplaceholder.typicode.com/posts',
);
if (loading) return <p>Загрузка…</p>;
if (error) return <p>Ошибка: {error}</p>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}На странице всё как раньше: «Загрузка…», затем список статей. Тот же хук подойдёт для одной статьи: useFetch('https://jsonplaceholder.typicode.com/posts/1').
Своё состояние у каждого вызова
Хук переиспользует логику, а не данные. Если два компонента вызовут useFetch, у каждого будут свои data, loading и error и свой запрос.
Простой хук без эффекта
Хук не обязан быть большим. Переключатель для меню или спойлера — src/hooks/useToggle.js:
// src/hooks/useToggle.js
import { useState } from 'react';
export function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue((current) => !current);
return [value, toggle];
}В компоненте: const [isOpen, toggle] = useToggle(); и кнопка onClick={toggle}.
Правила хуков
| Правило | Почему |
|---|---|
| Вызывайте хуки только на верхнем уровне компонента или другого хука | React различает хуки по порядку вызова |
| Не вызывайте хуки в условиях, циклах и обработчиках | Порядок будет разным при разных отрисовках |
Имя хука начинается с use | По нему линтер находит хуки и проверяет правила |
Частая ошибка
Хук внутри условия нарушает порядок вызовов: при одной отрисовке он есть, при другой — нет:
// Неправильно:
function PostPage({ id }) {
if (id) {
const { data } = useFetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
}
}Вызывайте хук всегда, а условие переносите внутрь хука или в разметку. Линтер Vite-проекта сообщит о такой ошибке правилом react-hooks/rules-of-hooks.