nuxt 后端渲染的核心 并不一定是异步请求就是前端渲染 后端渲染在dom加载前绑定数据 核心原理代码

本文详细探讨了Nuxt.js中的服务端渲染(SSR)机制,特别是asyncData功能,介绍了其如何在组件加载前异步获取数据,以及在服务端渲染流程中的作用。通过了解Nuxt.js的构建过程和渲染器工作原理,揭示了asyncData在SSR上下文中的执行细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Nuxt.js是vue官方推荐的一款优秀的服务端渲染(ssr)项目,集成了Vue,Vue-Router,Vuex,Vue-Meta等组件/框架,内置了webpack用于自动化构建,使我们可以更快速地搭建一个具有服务端渲染能力的应用。

今天主要来了解下Nuxt.js中一个非常重要的拓展功能:

asyncData(异步数据)的实现

1、asyncData有什么用?

在日常需求中可能想要在服务器端获取并渲染数据。那么使用asyncData方法可以使得你能够在渲染组件之前异步获取数据。asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。

在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用asyncData方法来获取数据并返回给当前组件。

2、为了更好的理解,首先先了解下ssr原理

如上图所示:webpack将 Source 打包出两个bundle文件:

其中 Server Bundle用于服务端渲染,服务端通过渲染器 bundleRenderer 将 bundle 生成首屏html片段;

而 Client Bundle 用于客户端渲染,首屏外的交互和数据处理还是需要浏览器执行 Client Bundle 来完成

我们的主角asyncData()就是在上图中Node Server中处理

3、创建渲染器

我们直接从打包之后说起,Nuxt renderer使用vue-server-renderer插件创建渲染器并解析webpack打好的bundle文件

 

const { createBundleRenderer } = require('vue-server-renderer’)

async ready() {

...

if (!this.options.dev) {

await this.loadResources()

}

...

}

//加载resource资源

async loadResources(_fs = fs) {

let distPath = path.resolve(this.options.buildDir, 'dist')

let updated = []

// 根据resourceMap配置去相应位置找到webpack打包好的文件

//(打包配置可在源码builder下webpack目录下server.js与client.js查看)

resourceMap.forEach(({ key, fileName, transform }) => {

...

this.resources[key] = data

updated.push(key)

})

...

if (updated.length > 0) {

this.createRenderer()

}

}

createRenderer() {

...

// 创建ssr渲染器

this.bundleRenderer = createBundleRenderer(

// 此文件是webpack 使用 VueSSRServerPlugin插件生成的server-bundle.json(服务器构建清单),

// 一个json文件,后边在调用渲染器方法时会根据这个清单解析不同文件

this.resources.serverBundle,

… //一些渲染器配置

)

}

4 、渲染器调用自身方法返回拼装好的组件html

 

let APP = await this.bundleRenderer.renderToString(context)

调用renderToString()会在传入包含请求信息的上下文,方法内读取server-bundle.json构建清单(其实它是由我们自定义的一个server.js生成,这里面写着如何去提前取数据),并将上下文环境context传入,该文件返回一个新的vue实例,renderToString()方法会根据返回的vue实例生成一段拼装好数据的html片段

4.1、server.js做了哪些事情

server.js做了如下图红框中这些事情

每个用户通过浏览器访问Vue页面时,都是一个全新的上下文,但在服务端,应用启动后就一直运行着,处理每个用户请求的都是在同一个应用上下文中。为了不串数据,需要为每次SSR请求,创建全新的app, store, router。

const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)

const _app = new Vue(app)

我们主要关注最后一部分asyncData部分

首先会根据上下文环境中的url调用getMatchedcomponents()将匹配的component返回

 

const Components = getMatchedComponents(router.match(ssrContext.url))

// 首先会根据上下文环境中的url调用getMatchedComponents()将匹配的component返回

export function getMatchedComponents(route, matches = false) {

return [].concat.apply([], route.matched.map(function (m, index) {

return Object.keys(m.components).map(function (key) {

matches && matches.push(index)

return m.components[key]

})

}))

}

接着遍历每个component,根据component的asyncData配置,执行 promisify()来promise化asyncData方法并将上下文对象赋给asyncData方法

promisify()方法接受两个参数:第一个组件中配置的asyncData()方法;第二个是挂载到新vue实例上的上下文对象

<-- server.js文件-->

let asyncDatas = await Promise.all(Components.map(Component => {

let promises = []

// Call asyncData(context)

if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {

let promise = promisify(Component.options.asyncData, app.context)

promise.then(asyncDataResult => {

ssrContext.asyncData[Component.cid] = asyncDataResult

applyAsyncData(Component)

return asyncDataResult

})

promises.push(promise)

} else {

promises.push(null)

}

// Call fetch(context)

if (Component.options.fetch) {

promises.push(Component.options.fetch(app.context))

}

else {

promises.push(null)

}

return Promise.all(promises)

}))

执行后通过applyAsyncData()方法将得到的数据同步一份给页面中定义的data,asyncData只是在首屏的时候调用一次,后续交互还是交给client处理

 

export function applyAsyncData(Component, asyncData) {

const ComponentData = Component.options.data || noopData

// Prevent calling this method for each request on SSR context

if (!asyncData && Component.options.hasAsyncData) {

return

}

Component.options.hasAsyncData = true

Component.options.data = function () {

const data = ComponentData.call(this)

if (this.$ssrContext) {

asyncData = this.$ssrContext.asyncData[Component.cid]

}

// 将服务端获取的数据绑定到data上

return { ...data, ...asyncData }

}

if (Component._Ctor && Component._Ctor.options) {

Component._Ctor.options.data = Component.options.data

}

}

5、拼装完整html并return

server.js会将新创建的Vue实例返回,renderToString()会根据实例内容创建好一段已经拼装好的代码片段

最后就是调用ssrTemplate将一些layout的模板拼装好返回整个页面的html

 

async renderRoute(url, context = {}) {

...

// 非服务端渲染,返回空标签,等待挂载vue实例

if (this.noSSR || spa) {

...

return { html, getPreloadFiles }

}

// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)

// 服务端渲染,APP即为拼装好的html片段

let APP = await this.bundleRenderer.renderToString(context)

...

APP += `< type="text/java">${serializedSession}`

APP += context.renders()

APP += m..text({ body: true })

APP += m.no.text({ body: true })

HEAD += context.renderStyles()

let html = this.resources.ssrTemplate({

HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),

BODY_ATTRS: m.bodyAttrs.text(),

HEAD,

APP,

ENV

})

return {

html,

cspSrcHashes,

getPreloadFiles: context.getPreloadFiles,

error: context.nuxt.error,

redirected: context.redirected

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值