Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Собственные хуки

Собственный хук — это обычная функция, имя которой начинается с 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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.