Nuxt 3 — это мощный фреймворк на основе Vue, который упрощает разработку серверно-рендерируемых и статически генерируемых веб-приложений. Одна из его особенностей — возможность работы с асинхронными данными с помощью специальных хуков. Они обеспечивают эффективное взаимодействие с API, динамическую загрузку данных и подгрузку контента. Многие знают про эти хуки, но далеко не все используют их потенциал в полной мере. 

Меня зовут Леша Смолыгин, я разработчик в Lamoda Tech. Разберемся, как использовать популярные хуки запроса из коробки useFetch, useAsyncData, $fetch, useLazyAsyncData и реализовать кастомный fetch. Эти инструменты решают задачи, связанные с загрузкой данных из API, при этом каждый из них имеет свои особенности и ограничения. Разберём их и сравним, чтобы понять, какой инструмент лучше подходит для той или иной ситуации в вашем приложении.

Хуки из коробки

1. useFetch

useFetch — это хук, который используется для выполнения запросов и получения данных с серверов или API прямо на этапе рендеринга страницы. Он тесно интегрируется с серверным рендерингом, поддерживает автоматическое кеширование, а также эффективно обрабатывает ошибки и состояния загрузки. Благодаря своим свойствам useFetch отлично подходит для работы с асинхронными данными, которые нужны для серверного рендеринга.

Основные характеристики useFetch:

  • Рендеринг на сервере: useFetch работает как на стороне клиента, так и на стороне сервера, обеспечивая доступ к данным еще до того, как страница отобразится.

  • SSR и SSG поддержка: Nuxt 3 позволяет использовать useFetch как для серверного рендеринга (SSR), так и для статически сгенерированных страниц (SSG).

  • Поддержка автоматического кеширования: Запросы кешируются автоматически, что повышает производительность и уменьшает нагрузку на сервер.

Пример использования useFetch:

import { useFetch } from '#imports';

export default defineComponent({
  setup() {
    const { data, pending, error } = useFetch('https://api.example.com/data');

    return { data, pending, error };
  },
});

Когда использовать useFetch?

  • Если вам нужны данные, которые будут использованы на этапе SSR.

  • Если важно кеширование данных для снижения нагрузки на сервер.

  • Если нужно быстро обрабатывать и обновлять данные при изменении запроса.

2. useAsyncData

useAsyncData — это хук, созданный специально для работы с асинхронными данными. Он предоставляет схожую функциональность с useFetch, но позволяет вам точно контролировать время и условия запроса, а также работает на сервере.

Основные характеристики useAsyncData:

  • Полный контроль над запросом: useAsyncData не ограничивается запросами к API, поэтому можно использовать собственные функции, а не просто URL.

  • SSR и CSR поддержка: подходит как для серверного, так и для клиентского рендеринга.

  • Гибкость и универсальность: вы можете использовать различные функции, что дает гибкость в написании кода.

Пример использования useAsyncData:

import { useAsyncData } from '#imports';

export default defineComponent({
  setup() {
    const { data, pending, error } = useAsyncData('fetchData', async () => {
      const response = await fetch('https://api.example.com/data');
      return response.json();
    });

    return { data, pending, error };
  },
});

Когда использовать useAsyncData?

  • Если вам требуется более гибкий и универсальный способ получения данных.

  • Если вам нужно больше контроля над самим запросом, например, добавление заголовков или параметров.

  • Если вы хотите использовать асинхронные функции, а не просто URL-адреса.

3. $fetch

$fetch — это удобный метод для выполнения HTTP-запросов в Nuxt 3, который является оберткой над библиотекой ofetch. Его можно использовать для выполнения запросов, однако он не предоставляет встроенного состояния (таких как pending, error и так далее), что делает его менее гибким для сложных сценариев.

Основные характеристики $fetch:

  • Независимость SSR: $fetch не обрабатывает серверный рендеринг, его результаты всегда доступны только на клиенте.

  • Более низкий уровень абстракции: так как это обертка над fetch API, вам потребуется вручную обрабатывать состояния и ошибки.

  • Использование для простых запросов: идеален для тех случаев, когда вам не нужно отслеживать состояние запроса.

Пример использования $fetch:

import { $fetch } from '#imports';

export default defineComponent({
  setup() {
    const data = ref(null);

    $fetch('https://api.example.com/data')
      .then(response => {
        data.value = response;
      })
      .catch(error => {
        console.error('Error:', error);
      });

    return { data };
  },
});

Когда использовать $fetch?

  • Если вам нужно выполнять запросы без отслеживания состояний.

  • Если нужно просто получить данные с API.

  • Если нужно минимизировать код.

4. useLazyAsyncData

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

Основные характеристики useLazyAsyncData:

  • Ленивая загрузка данных: данные загружаются только тогда, когда они нужны.

  • Улучшение производительности: позволяет загружать данные асинхронно и избегать ненужных запросов.

  • Удобство для динамических компонентов: полезен для тех компонентов, которые загружаются только при необходимости.

Пример использования useLazyAsyncData:

import { useLazyAsyncData } from '#imports';

export default defineComponent({
  setup() {
    const { data, pending, error } = useLazyAsyncData('lazyFetchData', async () => {
      const response = await fetch('https://api.example.com/data');
      return response.json();
    });

    return { data, pending, error };
  },
});

Когда использовать useLazyAsyncData?

  • Когда данные не нужны при первом рендере.

  • Когда нужно загружать данные только при взаимодействии с пользователем.

  • Когда нужно отложить запросы для оптимизации загрузки страницы.

Для удобства рассмотрим все описанные хуки в сравнительной таблице.

Хук

Основные особенности

Когда использовать

useFetch

SSR и SSG, автоматическое кеширование, встроенное состояние

Когда нужны данные на этапе SSR, требуется кеширование

useAsyncData

Универсальность, поддержка асинхронных функций, SSR и CSR

Когда нужен полный контроль над запросом

$fetch

Простая обертка над fetch API, отсутствие отслеживания состояний, подходит только для CSR

Для простых запросов, без необходимости отслеживания состояния

useLazyAsyncData

Ленивая загрузка, улучшение производительности, отложенные запросы

Когда нужно загружать данные только при необходимости или по требованию

Кастомный fetch

В Nuxt 3 вы можете настроить и использовать собственный fetch, чтобы адаптировать запросы к вашим требованиям и использовать их на всем проекте. 

Пример использования:

<script setup>
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
</script>

Этот пример достаточно избыточен. Можно написать кастомный composable, который будет удобнее в использовании:

import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>,
) {
  return useFetch(url, {
    ...options,
    $fetch: useNuxtApp().$api as typeof $fetch
  })
}

Более удачный пример использования будет выглядеть так:

<script setup>
const { data: modules } = await useAPI('/modules')
</script>

Когда использовать кастомный fetch?

Если вам требуется постоянно работать с аутентификацией, добавлять специфичные заголовки авторизации и обрабатывать ошибки API или стандартные параметры загрузки. При этом вы хотите описать всю логику в одном месте, чтобы она работала во всем проекте.

Создание кастомной обертки для useFetch

Вы можете создать собственную функцию, которая будет переопределять поведение стандартного useFetch. Например:

import { useFetch } from '#app'

export function useCustomFetch<T>(url: string, options: any = {}) {
  const token = useCookie('auth_token') // Получение токена из cookies
  
  options.headers = {
    ...options.headers,
    Authorization: `Bearer ${token.value}`
  }

  return useFetch<T>(url, options)
}

Эта обертка автоматически добавляет заголовок Authorization с токеном, если он есть.

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

Интерцепторы позволяют перехватывать запросы и ответы для их модификации. В useFetch доступны такие обработчики, как onRequest, onRequestError, onResponse, и onResponseError. Пример настройки:

const { data, error } = await useFetch('/api/resource', {
  onRequest({ options }) {
    options.headers = {
      ...options.headers,
      'X-Custom-Header': 'customValue'
    }
  },
  onResponse({ response }) {
    console.log('Response received:', response)
  },
  onResponseError({ error }) {
    console.error('Error in response:', error)
  }
})

Типизация и стандартные параметры

Вы можете задать стандартные значения для параметров, таких как базовый URL или тайм-аут запросов. Например:

export function useCustomFetchWithDefaults<T>(url: string, options: any = {}) {
  const defaults = {
    baseURL: 'https://api.example.com',
    timeout: 5000,
  }

  return useFetch<T>(url, { ...defaults, ...options })
}

Учет серверного и клиентского окружения

Кастомный fetch можно адаптировать под серверный и клиентский рендеринг. Например:

if (process.server) {
  options.headers = {
    ...options.headers,
    'X-Server-Only': 'true'
  }
}

Преимущества кастомного fetch

  • Уменьшение дублирования кода в запросах.

  • Унификация обработки ошибок.

  • Возможность интеграции с библиотеками для работы с токенами или кешем.

  • Централизованное управление логикой запросов.

Для более подробной информации и примеров использования кастомного fetch в Nuxt рекомендую изучить официальную документацию: Custom UseFetch Composable.

Заключение

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

Рассмотрим, какие плюсы и минусы можно выделить у хуков из коробки и кастомного решения при использовании на проекте:

Хуки из коробки

Кастомный хук

Плюсы

Не требуют ручного управления, все нужное уже собрано и готово к использованию

Можно настроить под себя, под логику сервиса

Минусы

Могут ограничивать или быть слишком избыточными (например, не всегда нужно встроенное кэширование)

Выше ответственность за реализацию

Нужно внедрять на этапе закладывания архитектуры или интеграции с новым сервисом

Итак, основное отличие в возможности управления и объеме вашей ответственности. Также, если у вас уже большой проект, написанный на хуках из коробки, то вам будет достаточно больно переезжать на свой кастом. При этом возможно использовать оба варианта. Да, это не самый лучший выход, но он тоже рабочий. Особенно если в будущем вы сможете постепенно избавляться от коробочных хуков в сторону своей реализации.

А какие хуки используете вы? Поделитесь в комментариях.

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


  1. Balek
    30.12.2024 08:01

    В наксте хуком называется совершенно другое. Это компосаблы.


    1. Beholder
      30.12.2024 08:01

      Они и в документации вроде хуками нигде не называются. Так что не надо нам тут реактовской терминологии :)