← Назад к вопросам
Какие знаешь требования для написания пользовательского hook?
2.2 Middle🔥 211 комментариев
#React#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Требования для написания пользовательского Hook
Пользовательские хуки (Custom Hooks) - это функции, которые используют встроенные React хуки. Есть несколько важных правил и требований.
1. Правило именования
Пользовательский хук ДОЛЖЕН начинаться с префикса 'use':
// Правильно
function useCounter() { ... }
function useLocalStorage(key) { ... }
function useAsync(asyncFunction) { ... }
// Неправильно - это функция, не хук
function getCounter() { ... }
function localStorageManager() { ... }
// Почему: React использует правило именования для определения,
// что функция вызывает хуки. Без префикса 'use' ESLint выдаст ошибку.
2. Вызов встроенных хуков только на верхнем уровне
Хуки можно вызывать только на верхнем уровне функции, не в циклах, условиях или вложенных функциях:
// Правильно
function useCustomHook(dependency) {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
useEffect(() => { ... }, [dependency]);
return { count, data };
}
// Неправильно - условный вызов
function useBadHook(shouldUseEffect) {
if (shouldUseEffect) {
useEffect(() => { ... }); // Ошибка: нарушение правила хуков
}
}
// Неправильно - в цикле
function useBadLoop() {
for (let i = 0; i < 5; i++) {
useState(0); // Ошибка: порядок хуков нарушен
}
}
// Правильно - хук вызывает другие хуки
function useArray(initialArray) {
const [array, setArray] = useState(initialArray);
const [index, setIndex] = useState(0);
return {
array,
current: array[index],
push: (item) => setArray([...array, item])
};
}
3. Используйте встроенные хуки
Пользовательский хук должен использовать хотя бы один встроенный хук React:
// Правильно - использует useState
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
// Правильно - использует useEffect
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
// Неправильно - это просто функция, не хук
function getGreeting(name) {
return `Hello, ${name}`;
}
4. Правильно управляйте зависимостями
Все значения, используемые в useEffect, должны быть указаны в массиве зависимостей:
// Правильно - правильный список зависимостей
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => setData(data));
}, [url]); // url в зависимостях
return data;
}
// Неправильно - отсутствует зависимость
function useBadFetch(url, options) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url, options) // options используется, но не в зависимостях
.then(res => res.json())
.then(data => setData(data));
}, [url]); // Ошибка: пропущена 'options'
return data;
}
// Правильно - использование useCallback для стабилизации функции
function useFetchWithCallback(url, onSuccess) {
const [data, setData] = useState(null);
const stableOnSuccess = useCallback(onSuccess, []);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
stableOnSuccess(data);
});
}, [url, stableOnSuccess]);
return data;
}
5. Возвращайте переменные или объект, а не JSX
Пользовательский хук может возвращать значения, функции, объекты - но не JSX.
// Правильно - возвращает значения
function useVisible() {
const [visible, setVisible] = useState(false);
return { visible, setVisible };
}
// Правильно - используется в компоненте
function Modal() {
const { visible, setVisible } = useVisible();
return (
<div>
{visible && <p>Modal Content</p>}
<button onClick={() => setVisible(true)}>Open</button>
</div>
);
}
// Неправильно - хук возвращает JSX
function useModalContent() {
const [visible, setVisible] = useState(false);
return (
<div>
{visible && <p>Content</p>}
</div>
);
}
6. Очистка ресурсов в useEffect
Если хук создает подписки или интервалы, нужна функция очистки:
// Правильно - возвращает cleanup функцию
function useWindowResize(callback) {
useEffect(() => {
window.addEventListener('resize', callback);
// Cleanup функция - удалить слушатель
return () => {
window.removeEventListener('resize', callback);
};
}, [callback]);
}
// Правильно - очистка интервала
function useInterval(callback, delay) {
useEffect(() => {
const interval = setInterval(callback, delay);
return () => clearInterval(interval);
}, [callback, delay]);
}
7. Типизация в TypeScript
Пользовательские хуки должны иметь правильную типизацию:
interface UseCounterReturn {
count: number;
increment: () => void;
decrement: () => void;
}
function useCounter(initialValue: number = 0): UseCounterReturn {
const [count, setCount] = useState<number>(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
// Правильная типизация для асинхронного хука
function useAsync<T>(
asyncFunction: () => Promise<T>,
immediate: boolean = true
): { data: T | null; loading: boolean; error: Error | null } {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!immediate) return;
setLoading(true);
asyncFunction()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [asyncFunction, immediate]);
return { data, loading, error };
}
8. Пример полноценного пользовательского хука
function useLocalStorage(key, initialValue) {
// Инициализация со значением из localStorage или initialValue
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// Функция для обновления значения и localStorage
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
// Слушать изменения localStorage из других вкладок
useEffect(() => {
const handleStorageChange = (e) => {
if (e.key === key && e.newValue) {
setStoredValue(JSON.parse(e.newValue));
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [key]);
return [storedValue, setValue];
}
// Использование
function MyComponent() {
const [name, setName] = useLocalStorage('name', 'Guest');
return (
<div>
<p>Hello, {name}</p>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
}
Резюме требований
- Имя должно начинаться с 'use'
- Вызывайте встроенные хуки только на верхнем уровне
- Используйте встроенные хуки (useState, useEffect и др.)
- Правильно управляйте зависимостями в useEffect
- Очищайте ресурсы (event listeners, intervals, subscriptions)
- Типизируйте в TypeScript
- Не возвращайте JSX
- Делайте хуки переиспользуемыми и логически связанными