发布·  

了解 Nuxt 2.12 中 fetch 的工作原理

探索 fetch 钩子的不同特性,并学习一种全新的方式将数据引入 Nuxt 应用程序。
Krutie Patel

Krutie Patel

@KrutiePatel

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

我们有三个键,

  1. Pending - 允许您在客户端调用 fetch 时显示占位符
  2. Error - 允许您显示错误消息
  3. 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 来在服务器端设置 HTTP 状态码,然后使用 throw new Error() 语句。

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。

layouts/default.vue
<template>
  <div>
    <nuxt keep-alive />
  </div>
</template>

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

:keep-alive-props prop 允许我们指示在网站内导航到其他地方时应保留在内存中的最大页面数。

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

上面是一种提高页面性能的方法,它更高级和通用,而下一个则通过使用 $fetchStatetimestamp 属性并将其与重新获取数据之前的延迟秒数进行比较来优化 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

  1. asyncData 仅限于页面级组件
  2. this 上下文不可用
  3. 通过 返回 数据添加负载
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

  1. fetch 在所有 Vue 组件中都可用
  2. this 上下文可用
  3. 简单地 修改 本地数据
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 的早期版本与现在有显著不同。

这是一个重大更改吗?

不,不是。实际上,通过将 context 作为第一个参数传递,仍然可以使用旧的 fetch,以避免您现有 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. 错误处理

之前 - 我们使用 context.error 函数,该函数在 API 调用期间发生错误时显示自定义错误页面。

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

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

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

是的,我们可以,但仅限于 asyncData() 处理页面级组件数据时。使用 fetch 时,我们可以利用 this.$nuxt.error({ statusCode: 404, message: 'Data not found' }) 来显示自定义错误页面。

结论

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

当您规划和设计需要同一路由内多次 API 调用的新 Nuxt 项目时,它肯定会让您思考有所不同。

我希望这篇文章能帮助您熟悉新的 fetch 功能。我很乐意看到您用它构建什么。