Nuxt 在最新的 2.12 版本中引入了一个新的 fetch
。Fetch 提供了一种将数据引入 Nuxt 应用程序的全新方式。
在这篇文章中,我们将探索 fetch 钩子的不同特性,并尝试了解它的工作原理。
Fetch 钩子和 Nuxt 生命周期
就 Nuxt 生命周期钩子而言,fetch
位于 Vue 生命周期中 created
钩子之后。我们已经知道,所有的 Vue 生命周期钩子都是通过它们的 this
上下文调用的。这同样适用于 fetch
钩子。
Fetch 钩子在服务器端创建组件实例后调用。这使得 this
上下文在 fetch
内部可用。
export default {
fetch() {
console.log(this)
}
}
让我们看看这对页面组件意味着什么。
页面组件
借助 this
上下文,fetch 能够直接修改组件的数据。这意味着我们可以设置组件的局部数据,而无需从页面组件中调度 Vuex store action 或提交 mutation。
因此,Vuex 变得可选,但并非不可能。如果需要,我们仍然可以使用 this.$store
来访问 Vuex store。
fetch 钩子的可用性
使用 fetch
,我们可以在任何 Vue 组件中异步预取数据。这意味着,除了在 /pages
目录中找到的页面组件之外,在 /layouts
和 /components
目录中找到的任何其他 .vue
组件也可以从 fetch 钩子中受益。
让我们看看这对布局和构建块组件意味着什么。
布局组件
使用新的 fetch
,我们现在可以直接从布局组件中进行 API 调用。这在 v2.12 版本发布之前是不可能的。
可能的用例
- 在 Nuxt 布局中从后端获取配置数据,以动态生成页脚和导航栏
- 在导航栏中获取用户相关数据(例如,用户个人资料、购物车商品数量)
- 在
layouts/error.vue
上获取网站相关数据
构建块(子/嵌套)组件
由于子组件中也提供了 fetch
钩子,我们可以将一些数据获取任务从页面级组件卸载,并将其委托给嵌套组件。这在 v2.12 版本发布之前也是不可能的。
这在很大程度上减少了路由级组件的责任。
可能的用例 - 我们仍然可以将 props 传递给子组件,但如果子组件需要有自己的数据获取逻辑,现在它们可以了!
多个 fetch 钩子的调用顺序
由于每个组件都可以有自己的数据获取逻辑,您可能会问,它们的调用顺序是什么?
Fetch 钩子在服务器端调用一次(在对 Nuxt 应用程序的第一次请求时),然后在客户端导航到其他路由时调用。但是由于我们可以为每个组件定义一个 fetch 钩子,因此 fetch 钩子按照其层次结构顺序调用。
禁用服务器端 fetch
此外,如果需要,我们甚至可以在服务器端禁用 fetch。
export default {
fetchOnServer: false
}
这样,fetch 钩子将仅在客户端调用。当 fetchOnServer
设置为 false 时,当组件在服务器端渲染时,$fetchState.pending
变为 true
。
错误处理
新的 fetch
在组件级别处理错误。让我们看看如何处理。
因为我们正在异步获取数据,所以新的 fetch() 提供了一个 $fetchState
对象,以检查请求是否已完成并成功进行。
以下是 $fetchState
对象的样子。
$fetchState = {
pending: true | false,
error: null | {},
timestamp: Integer
};
我们有三个键,
- Pending - 当在客户端调用 fetch 时,允许您显示占位符
- Error - 允许您显示错误消息
- Timestamp - 显示上次 fetch 的时间戳,这对于使用
keep-alive
进行缓存很有用
这些键随后直接在组件的模板区域中使用,以在从 API 获取数据的过程中显示相关的占位符。
<template>
<div>
<p v-if="$fetchState.pending">Fetching posts...</p>
<p v-else-if="$fetchState.error">Error while fetching posts</p>
<ul v-else>
…
</ul>
</div>
</template>
当在组件级别发生错误时,我们可以通过在 fetch 钩子中检查 process.server
并在其后添加 throw new Error()
语句来在服务器端设置 HTTP 状态代码。
async fetch() {
const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${this.$route.params.id}`)
.then((res) => res.json())
if (post.id === this.$route.params.id) {
this.post = post
} else {
// set status code on server and
if (process.server) {
this.$nuxt.context.res.statusCode = 404
}
// use throw new Error()
throw new Error('Post not found')
}
}
以这种方式设置 HTTP 状态代码对于正确的 SEO 很有用。
Fetch 作为方法
新的 fetch 钩子也充当一个方法,可以在用户交互时调用,或者从组件方法中以编程方式调用。
<!-- from template in template -->
<button @click="$fetch">Refresh Data</button>
// from component methods in script section
export default {
methods: {
refresh() {
this.$fetch()
}
}
}
使 Nuxt 页面更高效
我们可以使用 :keep-alive-props
prop 和 activated
钩子,通过新的 fetch 钩子使 Nuxt 页面组件更高效。
Nuxt 允许在内存中缓存一定数量的页面及其获取的数据。并且还允许添加一定秒数,然后我们才能重新获取数据。
对于上述任何方法,我们都必须在通用 <nuxt />
和 <nuxt-child
> 组件中使用 keep-alive
prop。
<template>
<div>
<nuxt keep-alive />
</div>
</template>
此外,我们可以将 :keep-alive-props
传递给 <nuxt />
组件,以缓存一定数量的页面及其获取的数据。
:keep-alive-props
prop 允许我们指示在我们在网站内其他地方导航时应该保留在内存中的最大页面数。
<nuxt keep-alive :keep-alive-props="{ max: 10 }" />
以上是一种提高页面性能的方法,这种方法更高级和通用,而下一种方法则深入研究,通过使用 $fetchState
的 timestamp
属性并将其与重新获取数据之前的秒数延迟进行比较来优化 fetch 请求调用。
Vue 的 activated
钩子在这里与 Nuxt 的 keep-alive
prop 一起使用以重新获取数据。
export default {
activated() {
// Call fetch again if last fetch more than a minute ago
if (this.$fetchState.timestamp <= Date.now() - 60000) {
this.$fetch()
}
}
}
asyncData 与 Fetch
就页面组件而言,新的 fetch
看起来与 asyncData()
太相似了,因为它们都处理本地数据。但是,以下是一些值得注意的关键差异。
在 Nuxt 2.12 中,asyncData
方法仍然是一个活跃的功能。让我们研究一下 asyncData
和新的 fetch
之间的一些关键差异。
asyncData
asyncData
仅限于页面级组件this
上下文不可用- 通过返回数据来添加 payload
export default {
async asyncData(context) {
const data = await context.$axios.$get(
`https://jsonplaceholder.typicode.com/todos`
)
// `todos` does not have to be declared in data()
return { todos: data.Item }
// `todos` is merged with local data
}
}
新的 Fetch
fetch
在所有 Vue 组件中都可用this
上下文可用- 简单地修改本地数据
export default {
data() {
return {
todos: []
}
},
async fetch() {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/todos`
)
// `todos` has to be declared in data()
this.todos = data
}
}
Nuxt 2.12 之前的 Fetch
如果您使用 Nuxt 一段时间了,那么您就会知道以前版本的 fetch
有很大不同。
这是一个重大更改吗?
不,它不是。实际上,旧的 fetch 仍然可以通过将
context
作为第一个参数传递来使用,以避免现有 Nuxt 应用程序中的任何重大更改。
以下是 fetch
钩子与 v2.12之前和之后相比的显著变化列表。
1. fetch
钩子的调用顺序
之前 - fetch
钩子在初始化组件之前调用,因此 this
在 fetch 钩子内部不可用。
之后 - fetch
在组件实例在服务器端创建时(在访问路由时)调用。
2. this
vs context
之前 - 我们可以访问页面级组件上的 Nuxt context
,前提是将 context
作为第一个参数传递。
export default {
fetch(context) {
// …
}
}
之后 - 我们可以像 Vue 客户端钩子一样访问 this
上下文,而无需传递任何参数。
export default {
fetch() {
console.log(this)
}
}
3. fetch
钩子的可用性
之前 - 只允许页面(路由级)组件在服务器端获取数据。
之后 - 现在,我们可以在任何 Vue 组件中异步预取数据。
4. fetch
钩子的调用顺序
之前 - fetch
可以在服务器端调用一次(在对 Nuxt 应用程序的第一次请求时),并在客户端导航到其他路由时调用。
之后 - 新的 fetch
与旧的 fetch 相同,但是……
……由于我们可以为每个组件定义一个 fetch
,因此 fetch
钩子按照其层次结构顺序调用。
5. 错误处理
之前 - 当 API 调用期间发生错误时,我们使用 context.error
函数显示自定义错误页面。
之后 - 新的 fetch
使用 $fetchState
对象来处理 API 调用期间模板区域中的错误。
错误处理在组件级别执行。
这是否意味着我们无法像在 Nuxt 2.12 之前那样向用户显示自定义错误页面?
是的,我们可以,但仅当涉及页面级组件数据时使用 asyncData()
。当使用 fetch
时,我们可以利用 this.$nuxt.error({ statusCode: 404, message: 'Data not found' })
来显示自定义错误页面。
结论
新的 fetch 钩子带来了许多改进,并在获取数据和以全新的方式组织路由级和构建块组件方面提供了更大的灵活性!
当您计划和设计需要同一路由内进行多次 API 调用的新 Nuxt 项目时,它肯定会让您有不同的思考方式。
我希望本文能帮助您熟悉新的 fetch
功能。我很乐意看到您用它构建的内容。