我主要从个人角度介绍一下我对服务器端渲染的理解。阅读本文后,您将了解到:
什么是服务器端渲染,它和客户端渲染有什么区别?为什么需要服务器端渲染?服务器端渲染的优缺点是什么?如何同构VUE项目?服务器端渲染的定义在讲服务级渲染之前,我们先来回顾一下页面的渲染过程:
浏览器请求html文本呈现过程来解析HTML文本,并构建DOM树来解析HTML。同时,如果遇到内联样式或样式脚本,会下载并构建stytle规则,如果遇到Javascript脚本,会下载并执行脚本。在构建DOM树和样式规则之后,呈现过程将它们合并到一个呈现树中。渲染过程开始布局渲染树,生成布局树,绘制布局树,并生成绘图记录。渲染过程将布局树分层,光栅化每一层,并获得合成帧。渲染进程将复合帧信息发送到GPU进程以显示在页面上
可以看到,页面的渲染其实就是浏览器将HTML文本转换成页面框架的过程。现在我们大部分的WEB应用都是使用JavaScript框架(Vue,React,Angular)来渲染页面。也就是说,JavaScript脚本执行的时候,HTML页面已经解析好了,DOM树已经构建好了。JavaScript脚本只是动态地改变DOM树的结构,使页面变成它想要的样子。这种渲染方式叫动态渲染,也可以叫客户端仁德。
那么什么是服务器端渲染呢?顾名思义,服务器端渲染就是当浏览器请求页面的URL时,服务器把我们需要的HTML文本组装起来,返回给浏览器。浏览器解析HTML文本后,不需要执行JavaScript脚本,就可以直接构造想要的DOM树并显示在页面上。这种在服务器端组装HTML的过程称为服务器端呈现。
服务器端渲染的起源WEB1.0在没有AJAX的时候,也就是Web1.0时代,几乎所有的应用都是服务器端渲染(此时的服务器渲染并不是现在的服务器渲染)。当时的页面渲染大概是这样的。浏览器请求页面URL,然后服务器收到请求后,去数据库查询数据,把数据扔进后端组件模板(php,asp,jsp等。).并将其渲染成HTML片段,然后服务器将这些HTML片段组装起来形成一个完整的HTML,最后返回给浏览器。此时浏览器已经得到了一个完整的由服务器动态组装的HTML文本,然后将HTML渲染到页面中,这个过程中没有任何JavaScript代码参与。
客户端渲染在WEB1.0时代,服务器端渲染似乎是当时最好的渲染方式,但是随着业务的日益复杂和后续AJAX的出现,WEB1.0服务器端渲染的缺点也逐渐暴露出来。
每次更新页面的一个小模块,都需要再次请求页面,再次检查数据库,重新组装HTML。前端JavaScript代码和后端(jsp,php,jsp)代码混杂在一起,使得日益复杂的WEB应用难以维护。那时候根本没有前端工程师的职位,前端的js工作一般都是后端的同学jQuery来承担。但是随着前端页面逐渐复杂,后端开始觉得js麻烦。虽然很简单,但是漏洞太多,公司就招了一些专门写js的人,也就是前端。这时候前端就鄙视前端了,因为后端觉得js太简单了,无非就是写页面的特效(JS)和剪贴图(css)。这根本不是一个真正的程序员。
随着nodejs的出现,前端看到了翻身的机会。为了摆脱后端的指指点点,前端开始了一场前端与后端分离的运动,希望从后端独立发展。前端分离,表面上看起来是代码分离,实际上是为了前端人员的分离,也就是前端和后端团队的分离。
前端和后端分离后,网页开始被当作一个独立的应用(SPA,单页应用)。前端团队接管所有页面渲染,后端团队只负责提供所有数据查询和处理的API。大致流程如下:首先浏览器请求URL,前端服务器直接返回一个空的静态html文件(没有任何数据库查询和模板组装)。这个HTML文件加载了呈现页面所需的许多JavaScript脚本和CSS样式表。浏览器获得HTML文件后,开始加载脚本和样式表,并执行脚本。此时,脚本请求后端服务提供的API来获取数据。采集完成后,数据会通过JavaScript脚本动态渲染到页面,完成页面展示。
这种将正面和背面分开的渲染模式也称为客户端渲染(CSR)。
随着单页应用(SPA)的发展,程序员逐渐发现SEO(搜索引擎优化)有问题,而且随着应用的复杂,JavaScript脚本不断臃肿,导致首屏渲染比Web1.0中的服务器渲染慢很多。
选择自己的路,跪着走下去。于是前端团队选择用nodejs在服务器端渲染页面,然后服务器端渲染又出现了。通常,该过程类似于客户端渲染。首先,浏览器请求URL。前端服务器收到URL请求后,根据不同的URL向后端服务器请求数据。请求完成后,前端服务器会用特定的数据组装一个HTML文本,返回给浏览器。浏览器获取HTML后,开始呈现页面。同时,浏览器加载并执行JavaScript脚本,将事件绑定到页面上的元素,并使页面具有交互性。当用户与浏览器页面交互时,比如跳转到下一个页面,浏览器会执行JavaScript脚本,向后端服务器请求数据,获取数据后再次执行JavaScript代码动态渲染页面。
服务器端渲染的优缺点与客户端渲染相比,服务器端渲染的优点是什么?
对SEO好就是对SEO好。事实上,爬虫抓取你的页面是有好处的。那么,当别人用搜索引擎搜索相关内容时,你的网页排名可以更高,这样你的流量就会更高。那为什么服务器端渲染更有利于爬虫抓取你的页面呢?其实爬行动物可以分为低级爬行动物和高级爬行动物。
低级爬虫:只请求URL,爬行URL返回的任何HTML内容。高级爬虫:请求URL,加载并执行JavaScript脚本来呈现页面,爬行JavaScript呈现的内容。也就是说,底层爬虫对客户端渲染的页面无能为力,因为返回的HTML是一个空壳,需要执行一个JavaScript脚本才能渲染出真正的页面。目前像百度、谷歌、微软等公司。一些老爬虫属于低级爬虫,通过使用服务器端渲染,对这些低级爬虫更加友好。
与客户端渲染相比,服务器端渲染在浏览器请求URL后,已经得到了一个带有数据的HTML文本。浏览器只需要解析HTML并直接构建DOM树。对于客户端呈现,首先需要获得一个空的HTML页面。此时页面已经进入白屏,然后需要加载并执行JavaScript,请求后端服务器获取数据,JavaScript渲染页面,才能看到最终页面。尤其是在复杂的应用中,由于需要加载JavaScript脚本,应用越复杂,需要加载的JavaScript脚本越多越大,会导致应用首屏加载时间非常长,从而降低体验。
服务器端渲染的缺点不是所有的WEB应用都必须使用SSR,这个需要开发者自己权衡,因为服务器端渲染会带来以下问题:
代码复杂性增加。为了实现服务器端渲染,应用程序代码需要同时兼容服务器端和客户端,而一些依赖的外部扩展库只能在客户端运行,需要特殊处理才能在服务器端渲染应用中运行。需要更多的服务器负载平衡。由于服务器增加了渲染HTML的需求,原本只需要输出静态资源文件的nodejs服务增加了数据采集的IO和渲染HTML的CPU。如果流量突然激增,可能会导致服务器关闭,因此需要使用响应式缓存策略,并准备相应的服务器负载。与构建设置和部署相关的更多要求。与可以部署在任何静态文件服务器上的完全静态的单页应用程序(SPA)不同,服务器需要在Node.js服务器的运行环境中才能呈现应用程序。所以在使用服务器渲染SSR之前,开发者需要考虑投入产出比。比如大部分应用系统不需要SEO,首屏时间也不是很慢。如果用SSR,那就小题大做了。
知道了服务器渲染同构的优缺点,如果我们需要在项目中使用服务器渲染,我们需要做什么?那就是同构我们的项目。
同构是在服务器端渲染中定义的。页面呈现有两种方式:
前端服务器请求后端服务器获取数据,组装html返回给浏览器,浏览器直接解析html然后渲染页面。在交互过程中,浏览器请求新数据并动态更新呈现的页面。这两种渲染方式的一个区别是,一种是在服务器端组装HTML,一种是在客户端组装HTML,运行环境不同。所谓同构,就是一段代码在服务端和客户端都可以执行,执行效果是一样的,就是完成这个html的组装,正确显示页面。也就是说,一段代码可以由客户端渲染,也可以由服务器渲染。
同构的条件要达到同构需要满足哪些条件?首先,我们来思考一下一个应用中页面的构成。如果我们使用Vue.js,当我们打开一个页面时,首先打开的是这个页面的URL。此URL可用于通过应用程序的路由匹配来查找特定页面。不同的页面有不同的视图。那么,观点是什么?从应用的角度来说,视图=模板数据,那么在Vue.js中,模板可以理解为组件,数据可以理解为数据模型,也就是响应式数据。因此,对于同构应用,我们必须实现客户端和服务器之间的路由、模型组件和数据模型的共享。
了解了服务器端渲染和同构的原理后,我们从零开始,一步一步完成同构,通过实践了解SSR。
基本NODEJS服务器呈现。首先,模拟最简单的服务器渲染,只需将我们需要的html文件返回到页面。
const express=require(' express ');\ n const app=express();app.get('/'),function(req,RES){ \ n RES . send(`\ n html \ n head \ n titles Sr/title \ n/head \ n body \ n phello world/p \ n/body \ n/html \ n `);\ n });app.listen(3001,function(){ \ n console . log(' listen:3001 ');\ n });启动后打开localhost:3001可以看到页面显示hello world。并打开网页源代码:
也就是说,浏览器在获取服务器返回的HTML源代码时,可以直接显示hello world,而不需要加载任何JavaScript脚本。
基本VUE客户端渲染的实现我们使用vue-cli创建一个新的vue项目,并修改一个App.vue组件:
template \ n \ tdiv \ n \ t \ tphello world/p \ n \ t \ t button @ click=' say hello ' say hello/button \ n \ t/div \ n/template \ n \ n script \ n export default { \ n方法:{ \ n say hello(){ \ n \ t alert(' hello SSR '); } }}/script然后运行npm run serve启动项目并打开浏览器。你也可以看到页面显示hello world,但是打开我们网页的源代码:
除了简单的兼容性处理noscript标签外,只有一个简单的id为app的div标签,没有关于hello world的字。可以说这是一个空页面(白屏)。当加载了下面Script标签的JavaScript脚本后,页面开始运行这些脚本,执行后,hello world正常显示。也就是说,真正渲染hello world的是JavaScript脚本。
同构Vue项目中配置模板组件的共享实际上是使用同一套组件代码。为了实现VUE组件可以在服务器上运行,我们首先需要解决代码编译的问题。通常,vue项目使用webpack来构建代码。同样,服务器端的代码可以通过使用webpack和借用一个官方的来构建。
步骤1:构建服务器端代码。从上图可以看出,服务器端代码构建完成后,需要在nodejs服务器上运行构建结果。但是,对于服务器端代码构建,有以下几点需要注意:
不需要编译CSS,只有在浏览器(客户端)运行时才需要样式表。构建目标的运行环境是commonjs,nodejs的模块化模式是commonjs,不需要代码裁剪。nodejs将所有代码一次性加载到内存中效率更高。因此,我们得到了服务器的webpack构建配置文件vue.server.config.js。
const node externals=require(' web pack-node-externals ');\ N const vuessserverplugin=require(' vue-server-renderer/server-plugin ')\ N \ N module . exports={ \ N CSS:{ \ N extract:false//不要提取CSS },\ N Configure Web Pack:()=({ \ N Entry:`。/src/server-entry . js `,//服务器条目文件\ n DevTool:' source-map '\ n Target:' node '//构建目标是nodejs环境\ n输出:{ \ library Target:' common js 2 '//构建目标加载模式commonjs }, //跳过node_mdoules,它将在运行时自动加载,而不编译\ n Externals:node Externals({ \ n allow list:[/\ \。css$/] //为了css模块的方便而允许CSS文件\ n}),\ n优化:{ \ nSplit Chunks:false//关闭代码切割\ n},\ n \ t插件:[\ nNewVuessServer Lugin () \ n] \ n})使用vue-server-renderer提供的服务器插件。这个插件主要和下面提到的客户端插件一起使用。主要用于实现开发过程中nodejs的热加载、源码图和html文件生成。
步骤2:构建客户端代码。当构建客户机代码时,使用客户机的执行入口文件。构建完成后,构建结果可以在浏览器中运行。但是,在服务器渲染中,HTML是由服务器渲染的。也就是说,加载那些JavaScript脚本是由服务器决定的,因为HTML中的脚本标签是由服务器拼接的。所以在构建客户端代码的时候,我们需要使用插件生成一个构建结果列表,用来告诉服务器当前页面需要加载哪些JS脚本和CSS样式表。
因此,我们得到了客户端的构建配置vue.client.config.js
const VueSSRClientPlugin=require(' vue-server-renderer/client-plugin ')\ n \ n模块。exports={ \ n configure web pack:()=({ \ n entry:` ./src/client-entry.js ' devtool: 'source-map ' target: 'web '\ n plugins:[\ n new VueSSRClientPlugin()\ n]\ n }), chainWebpack: config={ //去除所有关于客户端生成的超文本标记语言配置,因为已经交给后端生成\ n配置。插件。删除(' html ');\ n配置。插件。删除(' preload '). config.plugins.delete('预取');\ n } \ n }使用服务器渲染器提供的客户端-服务器,主要作用是生成构建加过清单vue-ssr-client-manifest.json,服务端在渲染页面时,根据这个清单来渲染超文本标记语言中的脚本标签(JavaScript)和环标签(CSS)。
接下来,我们需要将vue。客户端。配置文件和vue.server.config.js都交给vue-cli内置的构建配置文件vue.config.js,根据环境变量使用不同的配置
//vue。配置。js \ n const TARGET _ NODE=process。环境。web pack _ TARGET===' NODEconst serverConfig=require('/vue。服务器。config’);const clientConfig=require('/vue。客户。config’);\ n \ n if(目标节点){ \ n模块。exports=服务器配置;其他模块。exports=客户端配置;}使用跨环境区分环境
'脚本''服务器''巴别节点src/服务器。' js '\ n ' serve '' vue-CLI-service serve '\ n ' build '' vue-CLI-service build '\ n ' build:server '' cross-env web pack _ TARGET=node vue-CLI-service build-mode server ' \ n } \ n }模板组件共享第一步:创建VUE实例为了实现模板组件共享,我们需要将获取某视频剪辑软件渲染实例写成通用代码,如下createApp:
从“Vue”导入Vue\ n从'导入应用程序./App '\ n \ n导出默认函数create App(context){ \ n const App=new Vue({ \ n render:h=h(App)\ n });\ n \ t返回{ \ n \ tapp \ n }\ n };第二步:客户端实例化VUE新建客户端项目的入口文件,客户端条目。射流研究…
从“Vue”导入Vue \ n导入从以下位置创建应用程序./创建应用程序'\ n \ n const { app }=create app();\ n \应用程序.$ mount(' # app ');客户端条目。射流研究…是浏览器渲染的入口文件,在浏览器加载了客户端编译后的代码后,组件会被渲染到身份证明(识别)为应用的元素节点上。
第三步:服务端实例化VUE新建服务端代码的入口文件,服务器条目。射流研究…
从'导入创建应用程序./创建应用程序' \ n \ n导出默认上下文={ \ n const { app }=创建应用程序(上下文);返回应用程序;\ n }服务器条目。射流研究…是提供给服务器渲染某视频剪辑软件组件的入口文件,在浏览器通过统一资源定位器访问到服务器后,服务器需要使用服务器入口。射流研究…提供的函数,将组件渲染成html。
第四步:HTTP服务所有东西的准备好之后,我们需要修改nodejs的超文本传送协议服务器的启动文件。首先,加载服务端代码服务器入口。射流研究…的网络包构建结果
const path=require(' path ');\ n常量服务器捆绑包=路径。解决(流程。CWD(),' serverDist '' vue-SSR-server-bundle。JSON’);\ n const { createBundleRenderer }=require(' vue-server-renderer ');\ n常量服务器捆绑包=路径。解决(流程。CWD(),' serverDist '' vue-SSR-server-bundle。JSON’);加载客户端代码客户端条目。射流研究…的网络包构建结果
const客户端清单路径=路径。解决(流程。CWD(),' dist '' vue-SSR-client-manifest。JSON’);\ n const客户端清单=require(客户端清单路径);使用服务器渲染器的createBundleRenderer创建一个超文本标记语言渲染器:
const模板=fs。读取文件同步(路径。resolve(_ _ dirname,“index.html”),“utf-8”);\ n const renderer=createBundleRenderer(服务器捆绑,{模板,//使用超文本标记语言模板 clientManifest //将客户端的构建结果清单传入\ n });创建超文本标记语言模板,index.html
html \ n head \ n titles SSR/title \ n/head \ n body \ n!- vue-ssr-outlet – /body/html在超文本标记语言模板中,通过传入的客户端渲染结果客户端清单,将自动注入所有环样式表标签,而占位符将会被替换成模板组件被渲染后的具体的超文本标记语言片段和脚本脚本标签。
HTML准备好之后,我们暂停服务器中的所有路由请求。
const express=require(' express ');\ n const app=express();/* code todo实例化renderer renderer */\ n \ napp . get(' * 'function (req,RES){ \ nrenderer . rendertostring({ },(err,html)={\ n if (err) {\ n res.send,\ n return\ n } \ n RES . send(html);\ n })\ n });接下来,我们构建客户端和服务器项目,然后执行node server.js并打开页面源代码。
看似符合预期,却发现控制台出现错误,客户端build css和js无法加载。原因很清楚。我们没有将客户端构建结果文件挂载到服务器的静态资源目录中。在装载路由之前,添加以下代码:
app . use(express . static(path . resolve(process . CWD(),' dist '));看起来你完了。点击say hello也会弹出一条信息。细心的同学会发现,根节点有一个数据服务器渲染的属性。这个属性有什么作用?
既然服务器已经渲染了HTML,我们显然不需要丢弃它,重新创建所有的DOM元素。相反,我们需要“激活”这些静态HTML,然后使它们成为动态的(能够响应随后的数据变化)。
如果您检查服务器呈现的输出结果,一个特殊的属性会添加到应用程序的根元素中:
div id=' app ' data-server-rendered=' true ' data-server-rendered是一个特殊的属性,用来让客户端Vue知道HTML的这一部分是由Vue在服务器端渲染的,应该在激活模式下挂载。
路由的共享与同步模板组件共享完成后,下面就是路由的共享。我们面前的服务器使用的路由是*,接受任何URL,这样就可以让所有的URL请求都交给Vue进行路由处理,从而完成客户端路由和服务器路由的复用。
步骤1:创建路由器实例。为了实现重用,像createApp一样,我们创建了一个createRouter.js
从“Vue”导入Vue;\ n从“vue-router”导入路由器;\ n从'导入主目录。/views/Home '\ n导入关于来自。/views/About '\ n UE . use(Router)\ n const routes=[{ \ n path:'/' name: 'Home ' component: Home},{ path: '/about ' name: 'About '\ n component:About \ n }];\ n导出默认函数createrouter () {\ n返回newrouter ({\ n mode:' history '\ n routes \ n}) \ n}在createApp.js中创建路由器
从“Vue”导入Vue;\ n从'导入应用程序。/App '\ n导入从'创建路由器。/create router '\ n \ n导出默认函数create app(context){ \ n const router=create router();//创建路由器实例 const app=new Vue({ router,//将路由器注入根Vue实例\ n render:h=h(App)\ n });返回{路由器,应用};\ n };第二步:路由器做好路由匹配后,修改server-entry.js,将请求的URL传递给路由器,这样在创建app时就可以根据URL匹配对应的路由,然后就可以知道需要渲染哪些组件了。
从'导入createApp。/create app '\ n \ n导出默认上下文={ //因为它可能是异步路由挂钩函数或组件,所以我们将返回一个承诺, //以便服务器可以等待所有内容准备就绪后再进行呈现。 return new Promise((resolve,reject)={ const { app,router }=create app(); //在服务器端设置路由器的位置\ n router . push(context . URL)\ n//on ready等待,直到路由器解析了可能的异步组件和挂钩函数\ n router . on ready()={ \ n const matched components=router . get matched components(); //如果路由无法匹配,则执行reject函数并返回404 if(!matchedComponents.length) {退货拒绝({代码:404 \ n }); } //Promise应解析应用程序实例,以便它可以呈现\ n resolve (app) \ n},reject \ n}) \ n}修改server.js的路由,并将url传递给呈现器。
app.get('* 'function(req,RES){ \ n const context={ \ n URL:req . URL \ n };\ n renderer . rendertostring(context,(err,html)={ \ n if(err){ \ n console . log(err); res.send('500服务器错误');返回;\ n } \ n RES . send(html);\ n })\ n });为了测试,我们将App.vue修改为路由器视图。
template \ n div id=' app ' \ n router-link to='/' Home/router-link \ n router-link to='/about ' about/router-link \ n router-view/\ n/div \ n/template Home . vue
template \ n div home Page/div \ n/template about . vue
Template \ n div关于页面/div \ n/template编译、运行和查看源代码。
点击路由没有刷新页面,但是客户端路由跳转了,一切都符合预期。
数据共享和状态同步之前我们只是简单的实现了服务器端的渲染,但是现实中我们在访问页面的时候,还需要获取要渲染的数据并渲染成HTML。也就是说,在渲染HTML之前,我们需要准备好所有的数据,然后传递给渲染器。
通常,在Vue中,我们将状态数据交给Vuex进行管理。当然,状态也可以存储在组件中,但是我们需要在组件实例化时自己同步数据。
步骤1:创建一个商店实例。与createApp类似,第一步是创建一个createstore.js来实例化商店,并将其提供给客户机和服务器。
从“Vue”导入Vue;\ nim从“Vuex”导入Vuex;\ n从'导入{fetchItem}。/API '\ n \ nvue . use(Vuex);\ n \ n导出默认函数createStore() {返回新的Vuex。Store({ state: { item: {} },\ n actions:{ \ n fetchItem({ commit },id) {\ n return fetchItem(id)。然后(item={ commit('setItem 'item); }) } },突变:{ setItem(state,item) { Vue.set(state.item,item);\ n } \ n } \ n })\ n }操作封装了请求数据的功能,而变化用于设置状态。
将createstore添加到createApp中,将store注入到vue实例中,这样所有的Vue组件都可以得到store实例。
导出默认函数create app(context){ \ n const router=create router();\ n const store=createStore();\ n constapp=newVue ({\ n router,\ n store,//将存储注入到根Vue实例\ n render:h=h(App)\ n }); return {路由器、商店、应用};\ n };为了测试方便,我们模拟了一个远程服务函数。
fetchItem,用于查询对应item
export function fetchItem(id) { const items = [ { name: ‘item1’, id: 1 }, { name: ‘item2’, id: 2 }, { name: ‘item3’, id: 3 } ]; const item = items.find(i =>> i.id == id); return Promise.resolve(item);}第二步:STORE连接组件
一般情况下,我们需要通过访问路由,来决定获取哪部分数据,这也决定了哪些组件需要渲染。事实上,给定路由所需的数据,也是在该路由上渲染组件时所需的数据。所以,我们需要在路由的组件中放置数据预取逻辑函数。
在Home组件中自定义一个静态函数asyncData,需要注意的是,由于此函数会在组件实例化之前调用,所以它无法访问 this。需要将 store 和路由信息作为参数传递进去
<template>><div>> <div>>id: {{item.id}}</div>> <div>>name: {{item.name}}</div>></div>></template>><script>>export default { asyncData({ store, route }) { // 触发 action 后,会返回 Promise return store.dispatch(‘fetchItems’, route.params.id) }, computed: { // 从 store 的 state 对象中的获取 item。 item() { return this.$store.state.item; } }}</script>>第三步:服务端获取数据
在服务器的入口文件server-entry.js中,我们通过URL路由匹配 router.getMatchedComponents()得到了需要渲染的组件,这个时候我们可以调用组件内部的asyncData方法,将所需要的所有数据都获取完后,传递给渲染器renderer上下文。
修改createApp,在路由组件匹配到了之后,调用asyncData方法,获取数据后传递给renderer
import createApp from ‘./createApp’;export default context =>> { // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise, // 以便服务器能够等待所有的内容在渲染前就已经准备就绪。 return new Promise((resolve, reject) =>> { const { app, router, store } = createApp(); // 设置服务器端 router 的位置 router.push(context.url) // onReady 等到 router 将可能的异步组件和钩子函数解析完 router.onReady(() =>> { const matchedComponents = router.getMatchedComponents(); // 匹配不到的路由,执行 reject 函数,并返回 404 if (!matchedComponents.length) { return reject({ code: 404 }) } // 对所有匹配的路由组件调用 `asyncData()` Promise.all(matchedComponents.map(Component =>> { if (Component.asyncData) { return Component.asyncData({ store, route: router.currentRoute }); } })).then(() =>> { // 状态传递给renderer的上下文,方便后面客户端激活数据 context.state = store.state resolve(app) }).catch(reject); }, reject); })}
将state存入context后,在服务端渲染HTML时候,也就是渲染template的时候,context.state会被序列化到window.__INITIAL_STATE__中,方便客户端激活数据。
第四步:客户端激活状态数据
服务端预请求数据之后,通过将数据注入到组件中,渲染组件并转化成HTML,然后吐给客户端,那么客户端为了激活后端返回的HTML被解析后的DOM节点,需要将后端渲染组件时用的store的state也同步到浏览器的store中,保证在页面渲染的时候保持与服务器渲染时的数据是一致的,才能完成DOM的激活,也就是我们前面说到的data-server-rendered标记。
在服务端的渲染中,state已经被序列化到了window.__INITIAL_STATE__,比如我们访问http://localhost:3001?id=1,查看页面源代码

可以看到,状态已经被序列化到window.__INITIAL_STATE__中,我们需要做的就是将这个window.__INITIAL_STATE__在客户端渲染之前,同步到客户端的store中,下面修改client-entry.js
const { app, router, store } = createApp();if (window.__INITIAL_STATE__) { // 激活状态数据 store.replaceState(window.__INITIAL_STATE__);}router.onReady(() =>> { app.$mount(‘#app’, true);});
通过使用store的replaceState函数,将window.__INITIAL_STATE__同步到store内部,完成数据模型的状态同步。
总结
当浏览器访问服务端渲染项目时,服务端将URL传给到预选构建好的VUE应用渲染器,渲染器匹配到对应的路由的组件之后,执行我们预先在组件内定义的asyncData方法获取数据,并将获取完的数据传递给渲染器的上下文,利用template组装成HTML,并将HTML和状态state一并吐给前端浏览器,浏览器加载了构建好的客户端VUE应用后,将state数据同步到前端的store中,并根据数据激活后端返回的被浏览器解析为DOM元素的HTML文本,完成了数据状态、路由、组件的同步,同时使得页面得到直出,较少了白屏时间,有了更好的加载体验,同时更有利于SEO。
个人觉得了解服务端渲染,有助于提升前端工程师的综合能力,因为它的内容除了前端框架,还有前端构建和后端内容,是一个性价比还挺高的知识,不学白不学,加油!
图像渲染原理是什么
渲染是CG(计算机图形学)的最后一个过程,也是最终使图像符合3D场景的阶段。英语被翻译。角
CG,利用计算机技术进行视觉设计和制作的领域。
包括:游戏、动画、漫画、建筑设计等。
家装效果图是通过家装和装修施工前的施工图纸,以真实直观的视图展示施工后的实际效果。
制作流程:模型设计、材质设计、灯光设计、效果图。
为了渲染《阿凡达》,Vita Digital使用了一个占地10000平方英尺的服务器场。渲染一帧的平均时间是2小时160分钟。整体渲染时间288万小时,相当于一台服务器328年!
U200b《阿丽塔》平均每帧100小时渲染,全片125分钟,用3万台电脑渲染。平均需要14400小时,一天24小时,两年的制作周期,总计4.32亿小时。
Alita眼睛的CG模型,有多达900万个多边形
-Alita的机电躯干,包含7000多个精雕细琢的零件;
-阿丽塔脸上的细毛,50多万根;
-其他最耗时费力的头发,包括132,000根头发、2,000根眉毛和480根睫毛
帧和帧速率(fps)
帧数,由计算机显卡输出的静态画面。
帧率,电脑显卡一秒钟输出一定数量的静态图片。
帧速率)=帧数)/时间,单位为f/s(每秒帧数,fps) \ u200b
也就是说,如果动画的帧速率恒定在每秒60帧(fps),那么它在一秒钟内的帧数是60帧,在两秒钟内的帧数是120帧。
由于人类视觉的残留,肉眼会将每秒24帧以上的静态画面视为连续的动态视频。
(你可以拍摄每秒60帧的视频,然后通过软件把每秒帧数调整到每秒24帧左右,那么你一秒钟拍摄的图像就可以慢速回放到两秒钟,连续,不会卡顿)
简单来说,一帧是静止图像,帧的快速连续显示形成运动的错觉,就是视频。
渲染是将3D模型转换成2D图像并最终呈现在屏幕上的过程。
实时渲染从字面上很好理解,就是我们想看到3D物体或场景实时渲染后的效果。
实时,用数字怎么理解?至少24FPS:只有连续播放24张图像的速度达到或超过1秒,人眼观看时才不会有卡顿感。
比如最常见的3D游戏《王者》 《吃鸡》,或者一些互动的3D应用,比如智慧城市、智慧园区的可视化项目。
在实时渲染的场景下,这些应用会独立运行在我们的电脑和手机上,通过本地的硬件能力来完成实时渲染的过程。
所以玩大型游戏的话,本地硬件性能一定要好。
离线渲染(Offline Rendering)对应的是实时渲染。简单来说,我们不需要实时看到渲染的场景。
常见的有我们的家装效果图,好莱坞大片,3D动画等影视场景。
都需要达到非常逼真的渲染效果,甚至是完全真实的场景再现,但对实时性要求不高。
那为什么不能实时渲染一个效果图,而要用离线渲染的机制来代替呢?
因为渲染效果和保真要求不一样!
3.如何进行离线渲染?
云渲染字面意思是在云中完成渲染。通常对云渲染的理解是在云端实时渲染的场景。
云渲染的出现就是为了解决这个问题:硬件性能差的终端也能实时渲染出效果好的3D内容。
云渲染的基本原理是将所有的3D渲染工作交给云。渲染完成后,视频被编码并实时传输到我们的客户端,客户端就变成了视频播放器,对视频流进行解码和播放。在这个过程中,交互功能可以通过鼠标和键盘操作来完成。
各自的优缺点和使用场景
实时渲染\ u200b
追求渲染速度需要强大的交互体验。因此,即使客户端的硬件性能很高,也需要优化大量的数学算法,在不特别降低渲染效果的情况下,减少渲染时间,达到良好的实时交互。例如:游戏、智能公园
离线呈现(离线呈现)
追求渲染质量,不要求实时性和交互性。追求的是极致的渲染效果,达到真实的体验。因此采用最极致、最优秀、最接近真实物理原理的渲染算法,以极高的保真度进行渲染过程。通过渲染服务器场的云计算能力,最大限度地缩短渲染时间。比如:家装图片、电影等。
云渲染)
在追求相对较高的渲染质量的同时,还要满足实时性的要求。所以云计算能力的部署和调度能力会更高,让客户端配置不高的用户也能通过云渲染体验到很好的3D应用。比如:云桌面
房地产/建筑
3D艺术家只需要几张空间的照片,他们可以使用任何必要设施的逼真图像来舞台。
虚拟现实中的云渲染可以用于培训医务人员,练习操作技术而不使病人处于危险之中,并创建新的教育内容。
通过云渲染软件,设计师可以完成不同的风格、颜色、纹理和光照,并将结果展示给客户。
娱乐/游戏
让电影、动画和游戏更具沉浸感。