跳到主要内容

Nuxt 生命周期

理解 Nuxt 应用的生命周期,可以帮助你更深入地了解框架是如何运作的,尤其是在服务端渲染和客户端渲染两方面。

本章的目标是,从高层视角概述框架的各个组成部分、它们的执行顺序,以及它们如何协同工作。

服务端生命周期

在服务端,针对应用的每个初始请求会执行以下步骤:

服务端插件 once

Nuxt 由 Nitro 驱动,这是一个现代的服务端引擎。

当 Nitro 启动时,它会初始化并执行 /server/plugins 目录下的插件。这些插件可以:

  • 捕获并处理应用级别的运行时错误。
  • 注册在 Nitro 关闭时执行的钩子。
  • 为请求生命周期事件(例如修改响应)注册钩子。
Nitro 插件只在服务器启动时执行一次。在无服务器环境中,服务器会在每次传入请求时启动,Nitro 插件也是如此。不过,它们并不会被等待(awaited)。

服务端中间件

初始化 Nitro 服务器之后,会针对每个请求执行 server/middleware/ 下的中间件。中间件可用于认证、日志记录或请求转换等任务。

从中间件返回一个值会终止请求,并将该返回值作为响应发送。一般来说应该避免这种行为,以确保请求被正确处理!

应用插件(App plugins)

首先创建 Vue 和 Nuxt 实例。之后,Nuxt 执行它的应用插件。这包括:

  • 内置插件,例如 Vue Router 和 unhead。
  • 位于 app/plugins/ 目录中的自定义插件,包括没有后缀的(例如 myPlugin.ts)以及带 .server 后缀的(例如 myServerPlugin.server.ts)。

插件以特定的顺序执行,并且可能彼此存在依赖。更多关于执行顺序和并行性的细节,请参阅 插件文档。

在此步骤之后,Nuxt 调用 app:created 钩子,可用于执行额外的逻辑。

路由校验(Route validation)

在初始化插件之后、执行中间件之前,如果 definePageMeta 函数中定义了 validate 方法,Nuxt 会调用它。validate 方法可以是同步的也可以是异步的,常用于校验动态路由参数。

  • 如果参数有效,validate 函数应返回 true。
  • 如果校验失败,它应返回 false,或一个包含 status 和/或 statusText 的对象来终止请求。

更多信息,请参阅 路由校验文档。

应用中间件(App middleware)

中间件允许你在导航到某个特定路由之前运行代码。它常用于认证、重定向或日志记录等任务。

在 Nuxt 中,有三种类型的中间件:

  • 全局路由中间件
  • 具名路由中间件
  • 匿名(或内联)路由中间件

Nuxt 在初始页面加载时(服务端和客户端都会)执行所有全局中间件,然后在任何客户端导航之前再次执行。具名和匿名中间件只在对应页面组件中定义的页面(路由)meta 的 middleware 属性所指定的路由上执行。

关于每种类型及示例的细节,请参阅 中间件文档。

在服务端发生的任何重定向,都会向浏览器发送一个 Location: 头;随后浏览器会向这个新位置发起一个全新的请求。发生这种情况时,除非持久化在 cookie 中,否则所有应用状态都会被重置。

页面与组件

Nuxt 在此步骤渲染页面及其组件,并使用 useFetch 和 useAsyncData 获取所需的任何数据。由于服务端没有动态更新、也不会发生 DOM 操作,像 onBeforeMount、onMounted 这样的 Vue 生命周期钩子以及后续的钩子在 SSR 期间不会执行。

默认情况下,Vue 在 SSR 期间会暂停依赖追踪以获得更好的性能。

服务端没有响应式,因为 Vue SSR 是将应用自上而下渲染为静态 HTML,无法回头修改已经渲染过的内容。

你应该避免在 <script setup> 的根作用域中编写会产生需要清理的副作用的代码。此类副作用的一个例子是使用 setInterval 设置定时器。在仅客户端的代码中,我们可能会设置一个定时器,然后在 onBeforeUnmount 或 onUnmounted 中将其拆除。然而,由于卸载钩子在 SSR 期间永远不会被调用,定时器将永远存在。为避免此问题,请将你的副作用代码移入 onMounted 中。

观看 Daniel Roe 解释服务端渲染与全局状态的视频。

HTML 输出

在获取了所有所需数据并渲染了组件之后,Nuxt 会将渲染好的组件与来自 unhead 的设置结合,生成一个完整的 HTML 文档。这个 HTML 连同相关数据一起被发回客户端,以完成 SSR 过程。

在将 Vue 应用渲染为 HTML 之后,Nuxt 会调用 app:rendered 钩子。
在最终确定并发送 HTML 之前,Nitro 会调用 render:html 钩子。这个钩子允许你操作生成的 HTML,例如注入额外的脚本或修改 meta 标签。

客户端生命周期

无论你选择了哪种 Nuxt 模式,生命周期的这一部分都完全在浏览器中执行。

应用插件

这一步骤与服务端执行类似,包含内置插件和自定义插件。

app/plugins/ 目录中的自定义插件,例如没有后缀的(例如 myPlugin.ts)和带 .client 后缀的(例如 myClientPlugin.client.ts),会在客户端执行。

在此步骤之后,Nuxt 调用 app:created 钩子,可用于执行额外的逻辑。

路由校验

此步骤与服务端执行相同,包含 definePageMeta 函数中定义的 validate 方法(如果定义)。

应用中间件

Nuxt 中间件运行在服务端和客户端。如果你希望某些代码只在特定环境运行,考虑通过使用 import.meta.client 用于客户端、import.meta.server 用于服务端来拆分它。

挂载 Vue 应用并水合

调用 app.mount('#__nuxt') 将 Vue 应用挂载到 DOM 上。如果应用使用 SSR 或 SSG 模式,Vue 会执行水合步骤,使客户端应用变得可交互。在水合期间,Vue 会重新创建应用(服务端组件 除外),将每个组件与其对应的 DOM 节点匹配,并附加 DOM 事件监听器。

为确保正确的水合,保持服务端和客户端数据的一致性很重要。对于 API 请求,建议使用 useAsyncData、useFetch 或其他 SSR 友好的组合式函数。这些方法确保服务端获取的数据在水合期间被复用,避免重复请求。任何新的请求都应该只在水合之后触发,以防止水合错误。

在挂载 Vue 应用之前,Nuxt 会调用 app:beforeMount 钩子。
在挂载 Vue 应用之后,Nuxt 会调用 app:mounted 钩子。

Vue 生命周期

与服务端不同,浏览器会执行完整的 Vue 生命周期。