Свои хуки
Переиспользуемая логика, правила хуков, useSyncExternalStore.
Обновлено
Что такое хук
Хук — особая функция React (имя начинается с use), которая даёт компоненту доступ к возможностям React: памяти (useState), эффектам (useEffect), контексту (useContext) и т. д.
Свой хук — функция, которую пишете вы и которая использует другие хуки: так логику с состоянием выносят и переиспользуют в нескольких компонентах.
function useToggle(initial = false) {
const [on, setOn] = useState(initial)
const toggle = useCallback(() => setOn(v => !v), [])
return [on, toggle] as const
}
const [menuOpen, toggleMenu] = useToggle()
const [darkMode, toggleDark] = useToggle(true) // полностью отдельное состояниеВажно: свой хук разделяет логику, а не состояние. У каждого компонента, который его вызывает, своя копия.
Правила хуков
- Вызывайте хуки только на верхнем уровне компонента или другого хука — не в
if, циклах или колбэках. - Только из React-компонентов или других хуков — не из обычных функций.
Почему: React различает хуки по порядку вызовов при каждом рендере. if, который пропускает хук, сбивает порядок, и состояние попадает не в тот хук.
Хуки React — по категориям
| Категория | Хуки | Для |
|---|---|---|
| состояние | useState, useReducer |
память компонента |
| контекст | useContext, use(Context) |
общие данные без props |
| эффекты | useEffect, useLayoutEffect |
синхронизация с внешним миром |
| ссылки | useRef |
DOM и значения без перерисовки |
| производительность | useMemo, useCallback |
мемоизация (React Compiler делает её автоматически) |
| внешние источники | useSyncExternalStore |
подписка на сторы, media queries, localStorage |
| действия (React 19) | useActionState, useOptimistic, useFormStatus, useTransition |
формы, мутации, оптимистичный UI |
| асинхронные данные | use(promise) |
чтение Promise с Suspense |
Примеры, которые пригодятся в любом проекте
function useDebouncedValue<T>(value: T, delay = 300) {
const [debounced, setDebounced] = useState(value)
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay)
return () => clearTimeout(id)
}, [value, delay])
return debounced
}
function useMediaQuery(query: string) {
return useSyncExternalStore(
cb => {
const m = matchMedia(query)
m.addEventListener('change', cb)
return () => m.removeEventListener('change', cb)
},
() => matchMedia(query).matches,
() => false, // значение на сервере
)
}useSyncExternalStore — правильный способ подписаться на внешний источник; именно так редактор кода в этом приложении определяет светлую/тёмную тему.
Когда выносить свой хук
| Признак | Пример |
|---|---|
одна и та же связка useState + useEffect в 2+ компонентах |
debounce, fetch, media query |
| в компоненте много логики и мало JSX | useCheckout() забирает логику, компонент остаётся просто UI |
| хотите тестировать логику отдельно | хук тестируется через renderHook |
Не выносите хуки «потому что можно» — хук на 3 строки, используемый один раз, только прячет код.
Где они лежат в FSD
Хук, связанный с сущностью → entities/<x>/model/use-x.ts. Общий хук (debounce, media query) → shared/lib/.
Коротко
- Свой хук = функция
use..., которая собирает другие хуки; разделяет логику, а не состояние. - Только на верхнем уровне, всегда в одном порядке.
useSyncExternalStore— для внешних источников; выносите хуки, когда логика повторяется.