Nuxt 3 Data Fetching 完全指南

首发于公众号 前端从进阶到入院,欢迎关注。

使用 Nuxt 3 的渲染模式,你可以在客户端和服务器端执行 API 调用和渲染页面,这带来了一些挑战。例如,我们要避免重复的网络调用,实现高效的缓存,并确保这些 API call 可以跨环境工作。为了解决这些挑战,Nuxt 提供了一个内置的数据获取库($fetch)和两个 composable API(useFetchuseAsyncData)。

本文将详细解释 Nuxt 3 中可用的不同数据获取方法,以及使用时机。

数据获取库

Nuxt 内置了一个数据获取库:ofetch

ofetch 建立在 fetch API 的基础上,提供了一些方便的特性:

  • 可在 node、浏览器和 workers 中使用
  • 智能解析响应中的 JSON 和原生值
  • response.okfalse 时自动抛出错误,并带有友好的错误信息和简洁的堆栈(隐藏内部详情)
  • 如果发生错误,会自动重试请求
  • 你可以提供异步拦截器,在 ofetch 生命周期中搞事情。

你可以在整个应用中使用 $fetch 别名来使用 ofetch:

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

useFetch

useFetch 是在组件 setup 函数中处理数据获取最直接的方式。

<script setup>



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



<template>

  <div v-if="pending">加载中...</div>
  <div v-else-if="data">todos: {{ data }}</div>
  <div v-else-if="error">错误: {{ error }}</div>
  <button @click="refresh">刷新</button>
</template>

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

  • data: 包含异步函数结果的响应式变量
  • error: 包含请求错误信息的响应式对象
  • pending: 表示请求是否在进行中的 Boolean 响应式变量
  • refresh/execute: 刷新由 handler 函数返回的数据。默认情况下,Nuxt 会等待一个 refresh 完成后才能再次执行。

如果 API 调用是在服务器端执行的,useFetch composable 函数会将数据转发给客户端。这样,页面水合(hydrate)时客户端不需要重新获取在服务器端已经获取过的数据。你可以通过 useNuxtApp.payload() 检查这个数据; Nuxt 开发者工具 可以在 payload 标签页中可视化这些数据。

另外,useFetch 会使用键(key)来缓存 API 响应,以减少 API 调用。这个键会根据 URL 和 fetch 选项自动生成。useFetch composable 函数可以在 setup 函数、生命周期钩子和插件或路由中间件中自动导入和使用。

你可以在 URL 字符串中使用 ref 的值来确保组件在响应式变量改变时更新:

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

如果 todoId 的值改变,URL 会相应更新并重新获取数据。

可以查看官方文档获取更多信息。

选项

useFetch 接受一组选项作为最后一个参数,可以用来控制 composable 函数的行为。

懒加载

当使用 Vue 的 Suspense 时,数据获取的 composable 函数会在导航到新页面前自动等待异步函数解析。Nuxt 在底层使用了 Vue 的 <Suspense> 组件来确保前往新页面前,所有数据都已经获取到。

然而,如果你想在客户端导航时绕过这个行为,可以使用 lazy 选项:

<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>

在这种情况下,你需要通过使用 pending 值手动处理加载状态。

另外,你可以选择使用 useLazyFetch,这是一个实现相同结果的方便方法:

const { pending, data: todos } = useLazyFetch("/api/todos");

仅客户端

默认情况下,数据获取 composable 函数会在客户端和服务器环境中执行它们的异步函数。如果你想要仅在客户端执行,可以把 server 选项设置为 false:

const { pending, data: posts } = useFetch("/api/comments", {
  lazy: true,
  server: false,
});


这在组合使用 lazy 选项时特别有用,用于初次渲染不需要的数据,比如对 SEO 不敏感的数据。

警告:如果你没有在服务器端获取数据,例如使用 server: false,在水合过程完成前数据不会在客户端获取。

这意味着即使你在客户端 await useFetch,在 <script setup>data 变量仍会继续为 null。

最小化有效载荷大小

pick 选项可以通过选择想要从 composable 函数中返回的字段来最小化 HTML 文档中的有效载荷大小:

<script setup>



  const { data: todos } = await useFetch('/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("/api/todos", {
  transform: (todos) => {
    return todos.map((todo) => ({ name: todo.title, id: todo.description }));
  },
});

重新获取

要手动获取或更新数据,可以使用 composable 函数提供的 executerefresh 函数:

<script setup>



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



<template>

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

这两个函数的目的相同,但 executerefresh 的别名,在使用 immediate: false 时更合适。当 immediate 选项设置为 false(默认为 true)时,它会阻止请求立即触发。

使用 watch 选项,可以在应用中的其他响应式值发生改变时重新运行获取函数:

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


何时使用 refresh 与 watch 选项?

当你知道服务器端的数据已经修改,需要相应更新客户端的数据时,使用 refresh()

当用户修改了需要发送到服务器的参数时,将这些参数设置为 watch 源。比如,如果你想要使用 search 参数过滤 API 结果,那就 watch 这个参数。这可以确保无论用户怎样改变 search 参数,都会从 API 重新加载最新和准确的数据。

查询搜索参数

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

const queryValue = ref("anyValue");
const { data, pending, error, refresh } = await useFetch("/api/todos", {
  query: { queryKey: queryValue, anotherQueryKey: "anotherQueryValue" },
});


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

拦截器

你可以定义异步拦截器,钩入 API 调用的生命周期事件:

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

这些选项由内置的 ofetch 库提供。

useAsyncData

useFetch 用于从给定 URL 获取数据,而 useAsyncData 允许更复杂的逻辑。基本上,useFetch(url) 相当于 useAsyncData(url, () => $fetch(url)),为最常见的用例提供了更流畅的开发体验。

然而,在某些情况下使用 useFetch composable 函数可能不太合适,比如 CMS 或第三方服务提供自己的查询层。在这种情况下,你可以利用 useAsyncData 封装你的调用,同时享受 composable 函数提供的好处:

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

useAsyncData 中,第一个参数充当第二个参数(查询函数)获得的响应的唯一缓存 key。不过也可以省略这个参数,直接传入查询函数本身。在这种情况下,唯一 key 会被自动生成。

提示: useAsyncDatauseFetch 提供相同的返回值对象类型,并接受共同的一组选项作为最后一个参数。这些选项允许你自定义 composable 函数的行为,包括导航阻塞、缓存和执行控制等特性。

你可以查看官方文档获取更多信息。

总结

让我们总结一下应该在哪些情况下使用哪种数据获取方法:

  • 根据用户交互发起网络请求时,$fetch 函数是一个合适的选择。当发送数据到事件处理程序、仅执行客户端逻辑或与 useAsyncData 组合使用时,建议使用 $fetch

  • 在组件的 setup 函数中处理数据获取,useFetch composable 函数是最简单的方法。

  • 如果需要对数据获取过程进行更精确的控制,可以选择结合使用 useAsyncData$fetch

参考:mokkapps.de/blog/a-comp…

首发于公众号 前端从进阶到入院,分享 Vue 源码 / React / TS / 浏览器 / 工程化等各个前端领域,我的文章帮助了很多小伙伴进入大厂,也阔以加我的好友,交个朋友。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYVdIuWD' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片