---
title: "了解 Nuxt 2.12 中 fetch 的工作原理"
description: "探索 fetch 钩子的不同功能，并学习一种将数据导入 Nuxt 应用程序的全新方式。"
canonical_url: "https://nuxt.zhcndoc.com/blog/understanding-how-fetch-works-in-nuxt-2-12"
---
# 了解 Nuxt 2.12 中 fetch 的工作原理

> 探索 fetch 钩子的不同功能，并学习一种将数据导入 Nuxt 应用程序的全新方式。

Nuxt 在最新的 2.12 版本中引入了全新的 `fetch`。fetch 提供了一种全新的将数据导入 Nuxt 应用程序的方式。

在本文中，我们将探索 fetch 钩子的不同功能，并尝试理解它的工作原理。

## Fetch 钩子和 Nuxt 生命周期

在 Nuxt 生命周期钩子中，`fetch` 位于 Vue 生命周期的 `created` 钩子之后。就像我们已经知道的那样，所有 Vue 生命周期钩子都会以它们的 `this` 上下文被调用。fetch 钩子也是如此。

![Nuxt 生命周期中的新 fetch](https://nuxt.zhcndoc.com/assets/blog/new-fetch-lifecycle-hooks.png)

在组件实例在服务器端创建之后，会调用 fetch 钩子。这使得 `fetch` 内部可用 `this` 上下文。

```js
export default {
  fetch() {
    console.log(this)
  }
}
```

让我们看看这对页面组件意味着什么。

### 页面组件

通过使用 `this` 上下文，fetch 可以直接对组件的数据进行更改。这意味着我们可以在页面组件中设置组件的本地数据，而无需从页面组件分发 Vuex 存储操作或提交 mutations。

结果是，Vuex 变得可选，但不是不可能。如果需要，我们仍然可以像往常一样使用 `this.$store` 来访问 Vuex 存储。

## fetch 钩子的可用性

通过 `fetch`，我们可以异步地在**任何 Vue 组件**中预取数据。这意味着除了在 `/pages` 目录中找到的页面组件之外，每个在 `/layouts` 和 `/components` 目录中找到的 `.vue` 组件也可以从 fetch 钩子中受益。

让我们看看这对布局和构建组件意味着什么。

### 布局组件

使用新的 `fetch`，我们现在可以直接从布局组件中进行 API 调用。在 v2.12 发布之前，这是不可能的。

**可能的用例**

- 在 Nuxt 布局中从后端获取配置数据以动态生成页脚和导航栏
- 在导航栏中获取与用户相关的数据（例如用户简介、购物车商品数量）
- 在 `layouts/error.vue` 上获取网站相关数据

### 构建块（子/嵌套）组件

由于 `fetch` 钩子在子组件中也可用，我们可以将部分数据获取任务从页面级组件中卸载，并将其委托给嵌套组件。在 v2.12 发布之前，这也是不可能的。

这大大减少了路由级组件的责任。

**可能的用例 -** 我们仍然可以将 prop 传递给子组件，但是如果子组件需要有自己的数据获取逻辑，现在它们可以拥有！

## 多个 fetch 钩子的调用顺序

由于每个组件可以有自己的数据获取逻辑，你可能会问它们的调用顺序是什么？

在服务端只调用一次 fetch 钩子（在对 Nuxt 应用的首次请求时调用），然后在客户端导航到其他路由时再次调用。但是由于我们可以为每个组件定义一个 fetch 钩子，fetch 钩子的调用顺序就是它们所在层次的顺序。

### 在服务端禁用 fetch

此外，如果需要，我们甚至可以禁用服务端的 fetch。

```js
export default {
  fetchOnServer: false
}
```

这样，fetch 钩子将只在客户端调用。当 `fetchOnServer` 设置为 false 时，组件在服务器端渲染时，`$fetchState.pending` 变为 `true`。

## 错误处理

新的 `fetch` 在组件级别处理错误。让我们看看如何处理。

因为我们是异步获取数据，新的 fetch() 提供了一个 `$fetchState` 对象，用于检查请求是否已完成和进度是否成功。

下面是 `$fetchState` 对象的样子。

```text
$fetchState = {
  pending: true | false,
  error: null | {},
  timestamp: Integer
};
```

我们有三个键：

1. **Pending -** 让你在客户端调用 fetch 时显示一个占位符
2. **Error -** 让你显示一个错误消息
3. **Timestamp -** 显示上次 fetch 的时间戳，用于与 `keep-alive` 进行缓存

然后在组件的模板区域中直接使用这些键来显示在从 API 获取数据的过程中相关的占位符。

```html
<template>
  <div>
    <p v-if="$fetchState.pending">正在获取帖子...</p>
    <p v-else-if="$fetchState.error">获取帖子出错</p>
    <ul v-else>
      ...
    </ul>
  </div>
</template>
```

当发生错误时，我们可以在**组件级别**通过在 fetch 钩子中检查 `process.server`，设置服务端的 HTTP 状态码，然后跟随 `throw new Error()` 语句来在服务器端设置 HTTP 状态码。

```js
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 {
      // 在服务器端设置状态码，并且
      if (process.server) {
        this.$nuxt.context.res.statusCode = 404
      }
      // 使用 throw new Error()
      throw new Error('找不到帖子')
    }
}
```

以这种方式设置 HTTP 状态码对于正确的 SEO 是有用的。

## fetch 作为一个方法

新的 fetch 钩子也可以作为一个方法，可以在用户交互或从组件方法中以编程方式调用它。

```html
<!-- 在模板中 -->
<button @click="$fetch">刷新数据</button>
```

```js
// 在脚本区域的组件方法中
export default {
  methods: {
    refresh() {
      this.$fetch()
    }
  }
}
```

## 使 Nuxt 页面更具性能

使用新的 fetch 钩子、`:keep-alive-props` 属性和 `activated` 钩子，可以使 Nuxt 页面组件更具性能。

Nuxt 允许**在内存中缓存一定数量的页面**及其获取的数据。并且还可以**添加一定数量的秒数**，在这段时间内不能再次获取数据。

要使上述任何方法起作用，必须在通用的 `<nuxt />` 和 `<nuxt-child`> 组件中使用 `keep-alive` 属性。

```html [layouts/default.vue]
<template>
  <div>
    <nuxt keep-alive />
  </div>
</template>
```

此外，我们还可以将 `:keep-alive-props` 传递给 `<nuxt />` 组件，以缓存一定数量的页面及其获取的数据。

`:keep-alive-props` 属性允许我们指示在导航到站点的其他位置时，应在内存中保留的最大页面数量。

```html [layouts/default.vue]
<nuxt keep-alive :keep-alive-props="{ max: 10 }" />
```

以上是一种更高级和通用的提升页面性能的方法，下一个方法则通过使用 `$fetchState` 的 `timestamp` 属性，并将其与重新获取数据之前的秒数延迟进行比较，从而进一步优化了获取请求的调用。

在此，我们使用了 Vue 的 `activated` 钩子和 Nuxt 的 `keep-alive` 属性重新获取数据。

```js
export default {
  activated() {
    // 如果上次获取超过一分钟，则再次调用 fetch
    if (this.$fetchState.timestamp <= Date.now() - 60000) {
      this.$fetch()
    }
  }
}
```

## asyncData vs Fetch

就页面组件而言，新的 fetch 钩子似乎与 `asyncData()` 钩子非常相似，因为它们都处理本地数据。但是有一些值得注意的关键区别，如下所示。

截至 Nuxt 2.12，`asyncData` 方法仍然是一个活跃的功能。让我们来看看 `asyncData` 与新的 `fetch` 之间的一些关键区别。

### asyncData

1. `asyncData` 仅限于页面级组件
2. `this` 上下文不可用
3. 通过 **返回** 数据来添加载荷

```js
export default {
  async asyncData(context) {
    const data = await context.$axios.$get(
      `https://jsonplaceholder.typicode.com/todos`
    )
    // `todos` 不需要在 data() 中先声明
    return { todos: data.Item }
    // `todos` 与本地数据合并
  }
}
```

### 新的 Fetch

1. `fetch` 可以在所有 Vue 组件中使用
2. `this` 上下文可用
3. 简单地 **改变** 本地数据

```js
export default {
  data() {
    return {
      todos: []
    }
  },
  async fetch() {
    const { data } = await axios.get(
      `https://jsonplaceholder.typicode.com/todos`
    )
    // `todos` 必须在 data() 中先声明
    this.todos = data
  }
}
```

## Nuxt 2.12 之前的 fetch

如果你已经使用 Nuxt 工作了一段时间，那么你会知道以前的 `fetch` 版本和现在的有很大不同。

> **这是一个重大更改吗？**

> 不是的。实际上，旧的 fetch 仍然可以通过将 `context` 作为第一个参数传递来使用，以避免对现有 Nuxt 应用程序造成任何重大更改。

以下是与 v2.12 之前的 `fetch` 钩子相比，有显著更改的列表。

### 1. `fetch` 钩子的调用顺序

**之前 -** `fetch` 钩子在初始化组件之前被调用，因此在 fetch 钩子中无法使用 `this`。

**之后 -** 当访问路由时，在服务器端创建组件实例之后调用 `fetch`。

### 2. `this` vs `context`

**之前 -** 对于页面级组件，我们可以在页面级组件中访问 Nuxt 的 `context`，因为 `context` 被作为第一个参数传递。

```js
export default {
  fetch(context) {
    // ...
  }
}
```

**之后 -** 我们可以像 Vue 的客户端钩子一样访问 `this` 上下文，而无需传递任何参数。

```js
export default {
  fetch() {
    console.log(this)
  }
}
```

### 3. `fetch` 钩子的可用性

**之前 -** 只有页面（路由级）组件允许在服务器端获取数据。

**之后 -** 现在，我们可以在任何 Vue 组件中异步预获取数据。

### 4. `fetch` 钩子的调用顺序

**之前 -** `fetch` 钩子可以在服务器端调用一次（在第一次请求 Nuxt 应用程序时）并且在客户端导航到其他路由时调用。

**之后 -** 新的 `fetch` 与旧的 fetch 相同，但是...

...由于每个组件可以有一个 `fetch`，`fetch` 钩子按照它们的层次结构顺序进行调用。

### 5. 错误处理

**之前 -** 我们使用 `context.error` 函数，在进行 API 调用时发生错误时显示自定义错误页面。

**之后 -** 新的 `fetch` 使用 `$fetchState` 对象，在模板区域处理 API 调用期间的错误。

错误处理在组件级别执行。

> **这是否意味着我们不能像之前的 Nuxt 2.12 之前那样向用户显示自定义错误页面？**

是的，我们可以，但只能在涉及页面级组件数据时使用 `asyncData()`。当使用 `fetch` 时，我们可以利用 `this.$nuxt.error({ statusCode: 404, message: '数据未找到' })` 来显示自定义错误页面。

## 结论

新的 fetch 钩子带来了许多改进，并以全新的方式提供了获取数据和组织路由级和构建组件的更大灵活性！

当你计划和设计需要在同一路由中进行多个 API 调用的新 Nuxt 项目时，它肯定会使你产生一些不同的思考。

希望本文对您了解新的 fetch 功能有所帮助。期待看到您用它构建的项目。


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
