JavaRush /Курсы /Модуль 3: React /Создание хуков для повторного использования логики запрос...

Создание хуков для повторного использования логики запросов

Модуль 3: React
11 уровень , 8 лекция
Открыта

Зачем нужны пользовательские хуки?

Устали копировать и вставлять логику API-запросов в каждый компонент? Вы не одиноки! В этой лекции мы рассмотрим, как создавать пользовательские хуки (custom hooks) в React, которые позволят нам переиспользовать логику запросов. Это не только избавит ваши компоненты от лишнего кода, но и сделает проект более лаконичным и удобным для поддержки.

Представьте себе: у вас три компонента, каждый из которых взаимодействует с API. В каждом компоненте вы прописываете запрос, управляете состоянием загрузки и ошибки. Всё это дублируется. Профессиональный программист посмотрел бы на вас с подозрительным прищуром. Вот тут на помощь приходят хуки.

Пользовательский хук — это просто переиспользуемая функция, которая может взаимодействовать с состояниями React, эффектами и логикой. Вместо того чтобы повторять код в каждом компоненте, вы помещаете эту логику в хук и вызываете его там, где это необходимо. Помните, что хуки — это не магия, они просто функции, которые начинают с use.

Шаг 1: Основы создания пользовательского хука

Давайте начнем с простого примера. Мы создадим хук useFetch для выполнения GET-запросов и управления состоянием загрузки, данных и ошибок.

Пример базового пользовательского хука

Создадим файл useFetch.ts:

import { useState, useEffect } from "react";

interface UseFetchResult<T> {
  data: T | null; // Тип данных, которые вернёт запрос
  loading: boolean; // Состояние загрузки
  error: string | null; // Ошибки при запросе
}

const useFetch = <T>(url: string): UseFetchResult<T> => {
  const [data, setData] = useState<T | null>(null); // Данные
  const [loading, setLoading] = useState<boolean>(true); // Флаг загрузки
  const [error, setError] = useState<string | null>(null); // Ошибка

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true); // Запускаем загрузку
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`Ошибка HTTP: ${response.status}`);
        }
        const result = await response.json();
        setData(result); // Устанавливаем данные
      } catch (err) {
        setError((err as Error).message); // Устанавливаем ошибку
      } finally {
        setLoading(false); // Завершаем загрузку
      }
    };

    fetchData();
  }, [url]); // Хук перезапустится при изменении URL

  return { data, loading, error }; // Возвращаем объект результата
};

export default useFetch;

Теперь в этом хуке вся логика запроса, а также состояния loading, data и error.

Шаг 2: Типизация данных, возвращаемых API

Чтобы использовать хук эффективно, нужно точно знать, какие данные возвращает ваш API. Например, представим, что вы работаете с API списка пользователей:

interface User {
  id: number;
  name: string;
  email: string;
}

Когда вы вызываете useFetch<User[]>("https://api.example.com/users"), TypeScript будет "знать", что результатом загрузки будет массив объектов типа User.

Шаг 3: Использование пользовательского хука в компоненте

Теперь мы используем наш хук в компоненте:

import React from "react";
import useFetch from "./useFetch";

interface User {
  id: number;
  name: string;
  email: string;
}

const UserList: React.FC = () => {
  const { data: users, loading, error } = useFetch<User[]>("https://api.example.com/users");

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

  return (
    <ul>
      {users?.map((user) => (
        <li key={user.id}>
          {user.name} ({user.email})
        </li>
      ))}
    </ul>
  );
};

export default UserList;

Посмотрите, как чисто выглядит компонент! Теперь его единственная задача — рендерить данные. Всё остальное обрабатывает useFetch.

Шаг 4: Добавление POST-запросов

Давайте модернизируем наш хук, чтобы он поддерживал не только получение данных, но и отправку. Добавим метод postData.

const useFetch = <T>(url: string): UseFetchResult<T> & { postData: (body: any) => Promise<void> } => {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  const postData = async (body: any) => {
    setLoading(true);
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      if (!response.ok) {
        throw new Error(`Ошибка HTTP: ${response.status}`);
      }
      const result = await response.json();
      setData(result); // Обновляем данные после POST
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setLoading(false);
    }
  };

  return { data, loading, error, postData };
};

Теперь наш хук поддерживает и получение данных, и отправку.

Шаг 5: Пример компонента с POST-запросом

Используем новый метод postData:

const CreateUser: React.FC = () => {
  const { data, loading, error, postData } = useFetch<User>("https://api.example.com/users");

  const handleCreateUser = () => {
    postData({ name: "Новое имя", email: "newemail@example.com" });
  };

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

  return (
    <div>
      <button onClick={handleCreateUser}>Создать пользователя</button>
      {data && <p>Создан пользователь: {data.name}</p>}
    </div>
  );
};

Хук для всех запросов: универсализация

Наблюдая за расширением функциональности нашего хука, вы можете спросить: "А как насчет PUT, DELETE и других типов запросов?" На практике их можно тоже включить. Однако важно помнить, что излишняя сложность может привести к меньшей читаемости. Если нужды нет, не пытайтесь "обобщить всё".

Особенности и подводные камни

По мере работы с хуками есть несколько моментов, которые важно учитывать. Во-первых, запросы могут прерваться. Если компонент размонтируется до завершения асинхронного действия, нужно аккуратно следить за состояниями, чтобы избежать утечек памяти. Например, вызов abortController.abort() можно использовать для прерывания запроса, если компонент размонтирован.

Также важно понимать, что не все API одинаково предсказуемы. Всегда выделяйте время на тестирование вашего кода, обрабатывая как положительные сценарии, так и неожиданные ошибки.

С помощью пользовательских хуков вы упростите работу с любыми API, сократите дублирование кода и сделаете проект более структурированным. Используйте их с умом!

1
Задача
Модуль 3: React, 11 уровень, 8 лекция
Недоступна
Базовый хук для GET-запроса
Базовый хук для GET-запроса
1
Задача
Модуль 3: React, 11 уровень, 8 лекция
Недоступна
Хук для универсальных HTTP-запросов
Хук для универсальных HTTP-запросов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ