Skip to content

在 Nuxt.js 3 中获取数据的几个 API

Published:

前言

Nuxt.js@3 支持了多种渲染模式,可以同时在客户端和服务端调用 API 获取数据,但同时这也来带一些问题:如何避免重复的请求、如何高效的缓存数据等。

为了解决这些问题,Nuxt.js 3 提供了内置的数据请求库 $fetch​ 和两个 composable 函数:useFetch​ 和 useAsyncData​ 。

$Fetch

$fetch​ 这一内置插件是基于 ofetch​ ,相当于提供了一个包装(wrapper)。而 ofetch​ 提供了几个非常重要的特性:

const todos = await $fetch("/api/todos").catch(error => error.data);

useFetch

useFetch 是在 composable 函数里处理数据获取的最简单方式。

<script setup>
  const { data, error, pending, refresh } = await useFetch<Todo[]>('/api/todos')
</script>

<template>
  <span v-if="pending">Loading...</span>
  <span v-else-if="data">Todos: {{ data }}</span>
  <span v-else-if="error">Error: {{ error }}</span>
  <button @click="refresh">Refresh</button>
</template>

它会返回三个响应式变量和一个函数:

useFetch 负责在服务器上发出请求,并将数据转发到客户端。这样,当页面初始化时,返回一个在服务端将数据渲染完好的 html 页面。我们可以通过 useNuxtApp.payload()​ 检查这个 payload;Nuxt DevTools 会在 payload 选项卡中可视化显示这些数据。

useFetch 还通过使用 key 值来缓存 API 响应,这样可以减少 API 请求。这个 key 值是根据 URL 和 fetch 选项自动生成的。useFetch 是 Nuxt.js 中默认自动导入的,可以在设置函数、生命周期钩子、插件或路由中间件中使用。

我们还可以在 URL 字符串中使用 ref 的值,以确保组件在响应式变量更改时重新发出请求更新数据:

import { v4 as uuid } from "uuid";

const todoId = ref<string>(uuid());
const {
  data: tracks,
  pending,
  error,
} = useFetch(() => `/api/todos/${todoId.value}`);

如果 todoId​ 更新,url 也将发生改变,并再次发出请求获取数据。更多信息可以查看官网文档

选项

useFetch 函数第一个参数接收 url,第二个参数还接收 options​ 对象,用于控制发出的请求行为。

lazy

当将 lazy​ 设置为 true​ 时,Nuxt.js 会在异步请求函数解析完成前进行等待,当请求函数完成后,Nuxt.js 才会将数据渲染到异步组件或异步页面。

这项特性的底层是基于 Vue.js 的 <Suspense>​ 组件,以此确保 UI 中所有异步数据可用之前不会渲染。

<script setup>
  const { pending, data: todos } = useFetch("/api/todos", {
    lazy: true,
  });
</script>

<template>
  <div v-if="pending">加载中...</div>
  <div v-else>
    <div v-for="todo in todos">{{ todo.name }}</div>
  </div>
</template>

等同于:

<template>
  <Suspense>
    <!-- loading 等同于 pending,取决于数据是否全部获取到 -->
    <template #loading> 加载中... </template>
    <template #default>
      <div v-for="todo in todos">{{ todo.name }}</div>
    </template>
  </Suspense>
</template>

在这种情况下,需要通过解构获取 pending​ 值来处理加载状态。

const { pending, data: todos } = useLazyFetch<Todo[]>("/api/todos");

Client

默认情况下,useFetch 会在客户端和服务端同时发出请求,可以将 server​ 设置为 false​ :

const { pending, data: posts } = useFetch<Todo[]>("/api/todos", {
  lazy: true,
  server: false,
});

这不需要在初始渲染时获取的数据时特别有用,比如不涉及 SEO 或敏感数据的情况使用。

当在页面中使用 useFetch 并且刷新页面时会感觉到加载很慢,这是因为 Nuxt.js 在服务端请求完数据后才返回页面。如果没有在服务器上获取数据,例如使用 server: false​,数据将在 hydration 过程完成之前不会被获取。这意味着即使在客户端等待 useFetch​,数据在 <script setup>​ 中仍将继续为 null​。

Pick

pick​ 选项可以在服务端返回某个字段的数据,从而减少 HTML 页面的负载大小:

<script setup>
  const { data: todos } = await useFetch<Todo[]>('/api/todos', {
    pick: ['id', 'name'],
  })
</script>

<template>
  <div v-for="todo in todos">
    <span>{{ todo.name }}</span>
    <span>{{ todo.id }}</span>
  </div>
</template>

如果想修改更多字段或者某个属性值,可以使用 transform​ 函数来修改查询结果:

const { data: todos } = await useFetch<Todo[]>("/api/todos", {
  transform: todos => {
    return todos.map(todo => ({ name: todo.title, id: todo.description }));
  },
});

Refresh

为了手动获取或更新数据,可以使用 useFetch 提供的 execute​ 或 refresh​ 函数:

<script setup>
  const { data, error, execute, refresh } = await useFetch('/api/todos')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="refresh">刷新数据</button>
  </div>
</template>

这两个函数功能是相同的,但 execute​ 是 refresh​ 的别名,并且在使用 immediate: false​ 时更符合语义。

immediate​ 选项设置为 false​ 时(默认为 true​),它将不会立刻发出请求。

watch

利用 watch​ 选项,我们可以在项目中让 useFetch 依赖其他响应式值,当 watch 的响应式值发生更改时会重新调用异步请求函数:

const count = ref(1);

const { data, error, refresh } = await useFetch<Todo[]>("/api/todos", {
  watch: [count],
});

何时使用 refresh​ vs. watch​ 选项?

Query

使用 query​ 选项,可以在 useFetch 中包含查询参数:

const queryValue = ref("anyValue");

const { data, pending, error, refresh } = await useFetch<Todo[]>("/api/todos", {
  query: { status: true, date: new Date() },
});

这个选项是 ofetch 的一个扩展,利用 ufo​ 生成 URL。提供的对象会自动转换为字符串格式。

interseptor

我们可以在选项中定义异步拦截器,以注入 useFetch 的生命周期事件:

const { data, pending, error, refresh } = await useFetch("/api/todo", {
  onRequest({ request, options }) {},
  onRequestError({ request, options, error }) {},
  onResponse({ request, response, options }) {},
  onResponseError({ request, response, options }) {},
});

AsyncData

useFetch​ 专门用于从给定的 URL 获取数据,而 useAsyncData​ 允许更复杂的逻辑。基本上 useFetch(url)​ 等同于 useAsyncData(url, () => $fetch(url))​,为最常见的用例提供了更简洁的语法糖。

然而,在某些情况下,使用 useFetch​ 可能不太适用,例如当 CMS 或第三方服务提供其自己的查询层时。在这种情况下,你可以利用 useAsyncData​ 封装你的调用,享受语法糖带来的便利:

const { data, error } = await useAsyncData("getTodos", () => fetchTodos());

useAsyncData​ 中,第一个参数用作缓存从第二个参数(即查询函数)获得的响应的唯一 key 值。当然也可以省略参数并直接传递查询函数本身。在这种情况下,唯一 key 值将会被自动生成。