← Назад к вопросам

Как создать кастомный hook?

2.0 Middle🔥 261 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Создание кастомных hooks в React

Что такое кастомный hook

Кастомный hook — это JavaScript функция, которая использует встроенные React hooks и возвращает state, логику или другие функции. Это способ переиспользовать логику между компонентами без высокоуровневых компонентов (HOC) и render props.

Правило: имена кастомных hooks должны начинаться с use.

Основная структура

function useFetchData(url) {
  const [data, setData] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)

  React.useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err)
        setLoading(false)
      })
  }, [url])

  return { data, loading, error }
}

Использование

function MyComponent() {
  const { data, loading, error } = useFetchData('/api/users')

  if (loading) return <div>Загрузка...</div>
  if (error) return <div>Ошибка: {error.message}</div>

  return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>
}

Практические примеры

1. useLocalStorage — сохранение в localStorage

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = React.useState(() => {
    try {
      const item = window.localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      console.error(error)
      return initialValue
    }
  })

  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)
    }
  }

  return [storedValue, setValue]
}

// Использование
function Counter() {
  const [count, setCount] = useLocalStorage('count', 0)
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

2. useWindowSize — размер окна

function useWindowSize() {
  const [windowSize, setWindowSize] = React.useState({
    width: undefined,
    height: undefined
  })

  React.useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      })
    }

    window.addEventListener('resize', handleResize)
    handleResize()

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

  return windowSize
}

// Использование
function ResponsiveComponent() {
  const { width } = useWindowSize()
  return <div>{width < 768 ? 'Mobile' : 'Desktop'}</div>
}

3. useToggle — переключение boolean

function useToggle(initialState = false) {
  const [state, setState] = React.useState(initialState)

  const toggle = React.useCallback(() => {
    setState(prev => !prev)
  }, [])

  return [state, toggle]
}

// Использование
function Modal() {
  const [isOpen, toggleModal] = useToggle(false)

  return (
    <>
      <button onClick={toggleModal}>Open Modal</button>
      {isOpen && <div>Modal Content</div>}
    </>
  )
}

4. usePrevious — получить предыдущее значение

function usePrevious(value) {
  const ref = React.useRef()

  React.useEffect(() => {
    ref.current = value
  }, [value])

  return ref.current
}

// Использование
function Counter() {
  const [count, setCount] = React.useState(0)
  const prevCount = usePrevious(count)

  return (
    <div>
      <p>Current: {count}, Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

5. useAsync — управление async операциями

function useAsync(asyncFunction, immediate = true) {
  const [status, setStatus] = React.useState('idle')
  const [data, setData] = React.useState(null)
  const [error, setError] = React.useState(null)

  const execute = React.useCallback(async () => {
    setStatus('pending')
    setData(null)
    setError(null)

    try {
      const response = await asyncFunction()
      setData(response)
      setStatus('success')
      return response
    } catch (err) {
      setError(err)
      setStatus('error')
    }
  }, [asyncFunction])

  React.useEffect(() => {
    if (immediate) {
      execute()
    }
  }, [execute, immediate])

  return { execute, status, data, error }
}

// Использование
function UserProfile({ userId }) {
  const { data: user, status, error } = useAsync(
    () => fetch(`/api/users/${userId}`).then(r => r.json()),
    true
  )

  if (status === 'pending') return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  return <div>{user?.name}</div>
}

6. useDebounce — debouncing значения

function useDebounce(value, delay = 500) {
  const [debouncedValue, setDebouncedValue] = React.useState(value)

  React.useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)

    return () => clearTimeout(handler)
  }, [value, delay])

  return debouncedValue
}

// Использование
function SearchUsers() {
  const [search, setSearch] = React.useState('')
  const debouncedSearch = useDebounce(search, 300)

  React.useEffect(() => {
    if (debouncedSearch) {
      // Выполняем поиск только после 300ms без изменений
      fetchUsers(debouncedSearch)
    }
  }, [debouncedSearch])

  return (
    <input
      type="text"
      value={search}
      onChange={(e) => setSearch(e.target.value)}
      placeholder="Search..."
    />
  )
}

Правила кастомных hooks

  1. Имя начинается с use — позволяет IDE определить, это hook или нет
  2. Вызывается только на верхнем уровне — не в условиях, циклах или вложенных функциях
  3. Вызывается только в React компонентах или других hooks — не в обычных функциях
  4. Используй встроенные hooks — useState, useEffect, useRef, useContext и т.д.
  5. Делай hooks переиспользуемыми — не привязывай к конкретному компоненту

Best Practices

  • Типизация — указывай типы параметров и возвращаемого значения
function useCounter(initial: number = 0): [number, () => void, () => void] {
  const [count, setCount] = React.useState(initial)
  return [
    count,
    () => setCount(c => c + 1),
    () => setCount(c => c - 1)
  ]
}
  • Документирование — добавляй JSDoc комментарии
/**
 * Hook для управления localStorage
 * @param {string} key - ключ в localStorage
 * @param {*} initialValue - начальное значение
 * @returns {[*, Function]} - текущее значение и функция установки
 */
function useLocalStorage(key, initialValue) {
  // ...
}

Когда использовать кастомные hooks

  • Переиспользование логики между компонентами
  • Управление сложным состоянием
  • Работа с эффектами и побочными эффектами
  • Инкапсуляция логики
  • Упрощение компонентов

Кастомные hooks — мощный инструмент для чистого и переиспользуемого кода.

Как создать кастомный hook? | PrepBro