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

Какие знаешь требования для написания пользовательского 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>
  );
}

Резюме требований

  1. Имя должно начинаться с 'use'
  2. Вызывайте встроенные хуки только на верхнем уровне
  3. Используйте встроенные хуки (useState, useEffect и др.)
  4. Правильно управляйте зависимостями в useEffect
  5. Очищайте ресурсы (event listeners, intervals, subscriptions)
  6. Типизируйте в TypeScript
  7. Не возвращайте JSX
  8. Делайте хуки переиспользуемыми и логически связанными