摘要:在實(shí)際應(yīng)用中,我們可以將回調(diào)函數(shù)拿到的拼接到模板中,下面看下的實(shí)現(xiàn)把轉(zhuǎn)換模板字符串方法不斷拼接模板字符串,用做存儲,然后調(diào)用當(dāng)通過完畢,執(zhí)行傳入方法支持傳入實(shí)例和渲染完成后的回調(diào)函數(shù)。
服務(wù)端渲染:
簡單說:比如說一個(gè)模板,數(shù)據(jù)是從后臺獲取的,如果用客戶端渲染那么瀏覽器會先渲染html和css,然后再通過js的ajax去向后臺請求數(shù)據(jù)再更改渲染。就是在前端再用Node建個(gè)后臺,把首屏數(shù)據(jù)加載成一個(gè)完整的頁面在node建的后臺渲染好,瀏覽器拿到的就是一個(gè)完整的dom樹。根據(jù)項(xiàng)目打開地址,路由指到哪個(gè)頁面就跳到哪。
服務(wù)端比起客戶端渲染頁面的優(yōu)點(diǎn):
首屏渲染速度更快
客戶端渲染的一個(gè)缺點(diǎn)是,用戶第一次訪問頁面,此時(shí)瀏覽器沒有緩存,需要先從服務(wù)端下載js,然后再通過js操作動態(tài)添加dom并渲染頁面,時(shí)間較長。而服務(wù)端渲染的規(guī)則是,用戶第一次訪問瀏覽器可以直接解析html文檔并渲染頁面,并屏渲染速度比客戶端渲染更快。
SEO
服務(wù)端渲染可以讓搜索引擎更容易讀取頁面的meta信息,以及其它SEO相關(guān)信息,大大增加了網(wǎng)站在搜索引擎中的速度。
減少HTTP請求
服務(wù)端渲染可以把一些動態(tài)數(shù)據(jù)在首次渲染時(shí)同步輸出到頁面,而客戶端渲染需要通過AJAX等手段異步獲取這些數(shù)據(jù),這樣就相當(dāng)于多了一次HTTP請求。
普通服務(wù)端渲染vue提供了renderToString接口,可以在服務(wù)端把vue組件渲染成模板字符串,我們先看下用法:
benchmarks/ssr/renderToString.js const Vue = require("../../dist/vue.runtime.common.js") const createRenderer = require("../../packages/vue-server-renderer").createRenderer const renderToString = createRenderer().renderToString const gridComponent = require("./common.js") // vue支行時(shí)的代碼,不包括編譯部分 console.log("--- renderToString --- ") const self = (global || root) self.s = self.performance.now() renderToString(new Vue(gridComponent), (err, res) => { if (err) throw err // console.log(res) console.log("Complete time: " + (self.performance.now() - self.s).toFixed(2) + "ms") console.log() })
這段代碼是支行在node.js環(huán)境中的,主要依賴vue.common.js,vue-server-render.其中vue.common.js是vue運(yùn)行時(shí)代碼,不包括編譯部分:vue-server-render對外提供createRenderer方法,renderToString是createRenderer方法返回值的一個(gè)屬性,它支持傳入vue實(shí)例和渲染完成后的回調(diào)函數(shù),這里要注意,由于引用的是只包括運(yùn)行時(shí)的vue代碼,不包括編譯部分,所以其中err表示是否出錯(cuò),result表示dom字符串。在實(shí)際應(yīng)用中,我們可以將回調(diào)函數(shù)拿到的result拼接到模板中,下面看下renderToString的實(shí)現(xiàn):
src/server/create-renderer.js const render = createRenderFunction(modules, directives, isUnaryTag, cache) return { renderToString ( component: Component, context: any, cb: any ): ?Promise{ if (typeof context === "function") { cb = context context = {} } if (context) { templateRenderer.bindRenderFns(context) } // no callback, return Promise let promise if (!cb) { ({ promise, cb } = createPromiseCallback()) } let result = "" const write = createWriteFunction(text => { result += text return false }, cb) try { // render:把component轉(zhuǎn)換模板字符串str ,write方法不斷拼接模板字符串,用result做存儲,然后調(diào)用next,當(dāng)component通過render完畢,執(zhí)行done傳入resut, render(component, write, context, () => { if (template) { result = templateRenderer.renderSync(result, context) } cb(null, result) }) } catch (e) { cb(e) } return promise } }
renderToString方法支持傳入vue實(shí)例component和渲染完成后的回調(diào)函數(shù)done。它定義了result變量,同時(shí)定義了write方法,最后執(zhí)行render方法。整個(gè)過程比較核心的就是render方法:
src/server/render.js return function render ( component: Component, write: (text: string, next: Function) => void, userContext: ?Object, done: Function ) { warned = Object.create(null) const context = new RenderContext({ activeInstance: component, userContext, write, done, renderNode, isUnaryTag, modules, directives, cache }) installSSRHelpers(component) normalizeRender(component) renderNode(component._render(), true, context) }
/** * // render實(shí)際上是執(zhí)行了renderNode方法,并把component._render()方法生成的vnode對象作為參數(shù)傳入。 * @param node 先判斷node類型,如果是component Vnode,則根據(jù)這個(gè)Node創(chuàng)建一個(gè)組件的實(shí)例并調(diào)用_render方法作為當(dāng)前node的childVnode,然后遞歸調(diào)用renderNode * @param isRoot 如果是一個(gè)普通dom Vnode對象,則調(diào)用renderElement渲染元素,否則就是一個(gè)文本節(jié)點(diǎn),直接用write方法。 * @param context */ function renderNode (node, isRoot, context) { if (node.isString) { renderStringNode(node, context) } else if (isDef(node.componentOptions)) { renderComponent(node, isRoot, context) } else if (isDef(node.tag)) { renderElement(node, isRoot, context) } else if (isTrue(node.isComment)) { if (isDef(node.asyncFactory)) { // async component renderAsyncComponent(node, isRoot, context) } else { context.write(``, context.next) } } else { context.write( node.raw ? node.text : escape(String(node.text)), context.next ) } }
/**主要功能是把VNode對象渲染成dom元素。 * 先判斷是不是根元素,然后渲染開始開始標(biāo)簽,如果是自閉合標(biāo)簽直接寫入write,再執(zhí)行next方法 * 如果沒有子元素,又不是閉合標(biāo)簽,通過write寫入開始-閉合標(biāo)簽。再執(zhí)行next.dom渲染完畢 * 否則就通過write寫入開始標(biāo)簽,接著渲染所有的子節(jié)點(diǎn),再通過write寫入閉合標(biāo)簽,最后執(zhí)行next * @param context */ function renderElement (el, isRoot, context) { const { write, next } = context if (isTrue(isRoot)) { if (!el.data) el.data = {} if (!el.data.attrs) el.data.attrs = {} el.data.attrs[SSR_ATTR] = "true" } if (el.functionalOptions) { registerComponentForCache(el.functionalOptions, write) } const startTag = renderStartingTag(el, context) const endTag = `${el.tag}>` if (context.isUnaryTag(el.tag)) { write(startTag, next) } else if (isUndef(el.children) || el.children.length === 0) { write(startTag + endTag, next) } else { const children: Array流式服務(wù)端渲染= el.children context.renderStates.push({ type: "Element", rendered: 0, total: children.length, endTag, children }) write(startTag, next) } }
普通服務(wù)器有一個(gè)痛點(diǎn)——由于渲染是同步過程,所以如果這個(gè)app很復(fù)雜的話,可能會阻塞服務(wù)器的event loop,同步服務(wù)器在優(yōu)化不當(dāng)時(shí)甚至?xí)o客戶端獲得內(nèi)容的速度帶來負(fù)面影響。vue提供了renderToStream接口,在渲染組件時(shí)返回一個(gè)可讀的stream,可以直接pipe到HTTP Response中,流式渲染能確保在服務(wù)端響應(yīng)度,也能讓用戶更快地獲得渲染內(nèi)容。renderToStream源碼:
benchmarks/ssr/renderToStream.js const Vue = require("../../dist/vue.runtime.common.js") const createRenderer = require("../../packages/vue-server-renderer").createRenderer const renderToStream = createRenderer().renderToStream const gridComponent = require("./common.js") console.log("--- renderToStream --- ") const self = (global || root) const s = self.performance.now() const stream = renderToStream(new Vue(gridComponent)) let str = "" let first let complete stream.once("data", () => { first = self.performance.now() - s }) stream.on("data", chunk => { str += chunk }) stream.on("end", () => { complete = self.performance.now() - s console.log(`first chunk: ${first.toFixed(2)}ms`) console.log(`complete: ${complete.toFixed(2)}ms`) console.log() })
這段代碼也是同樣運(yùn)行在node環(huán)境中的,與rendetToString不同,它會把vue實(shí)例渲染成一個(gè)可讀的stream。源碼演示的是監(jiān)聽數(shù)據(jù)的讀取,并記錄讀取數(shù)據(jù)的時(shí)間
,而在實(shí)際應(yīng)用中,我們也可以這樣寫:
const Vue = require("../../dist/vue.runtime.common.js") const createRenderer = require("../../packages/vue-server-renderer").createRenderer const renderToStream = createRenderer().renderToStream const gridComponent = require("./common.js") const stream = renderToStream(new Vue(gridComponent)) app.use((req,res)=>{ stream.pipe(res) })
如果代碼運(yùn)行在Express框架中,則可以通過app.use方法創(chuàng)建middleware,然后直接把stream pipe到res中,這樣客戶端就能很快地獲得渲染內(nèi)容了,下面看下renderToStream的實(shí)現(xiàn):
src/server/create-renderer.js const render = createRenderFunction(modules, directives, isUnaryTag, cache) return { ... renderToStream (component: Component,context?: Object): stream$Readable { if (context) { templateRenderer.bindRenderFns(context) } const renderStream = new RenderStream((write, done) => { render(component, write, context, done) }) if (!template) { return renderStream } else { const templateStream = templateRenderer.createStream(context) renderStream.on("error", err => { templateStream.emit("error", err) }) renderStream.pipe(templateStream) return templateStream } }
renderToStream傳入一個(gè)Vue對象實(shí)例,返回的是一個(gè)RenderStream對象的實(shí)例,我們來看下RenderStream對象的實(shí)現(xiàn):
src/server/create-stream.js // 繼承了node的可讀流stream.Readable;必須提供一個(gè)_read方法從底層資源抓取數(shù)據(jù)。通過Push(chunk)調(diào)用_read。向隊(duì)列插入數(shù)據(jù),push(null)結(jié)束 export default class RenderStream extends stream.Readable { buffer: string; // 緩沖區(qū)字符串 render: (write: Function, done: Function) => void; // 保存?zhèn)魅氲膔ender方法,最后分別定義了write和end方法 expectedSize: number; // 讀取隊(duì)列中插入內(nèi)容的大小 write: Function; next: Function; end: Function; done: boolean; constructor (render: Function) { super() // super調(diào)用父類的構(gòu)造函數(shù) this.buffer = "" this.render = render this.expectedSize = 0 // 首先把text拼接到buffer緩沖區(qū),然后判斷buffer.length,如果大于expecteSize,用this.text保存 //text,同時(shí)調(diào)用this.pushBySize把緩沖區(qū)內(nèi)容推入讀取隊(duì)列中。 this.write = createWriteFunction((text, next) => { const n = this.expectedSize this.buffer += text if (this.buffer.length >= n) { this.next = next this.pushBySize(n) return true // we will decide when to call next } return false }, err => { this.emit("error", err) }) // 渲染完成后;我們應(yīng)該把最后一個(gè)緩沖區(qū)推掉. this.end = () => { this.done = true // 標(biāo)志組件的渲染已經(jīng)完畢,然后調(diào)用push將緩沖區(qū)剩余內(nèi)容推入讀取隊(duì)列中 this.push(this.buffer) //把緩沖區(qū)剩余內(nèi)容推入讀取隊(duì)列中 } } //截取buffer緩沖區(qū)前n個(gè)長度的數(shù)據(jù),推入到讀取隊(duì)列中,同時(shí)更新buffer緩沖區(qū),刪除前n條數(shù)據(jù) pushBySize (n: number) { const bufferToPush = this.buffer.substring(0, n) this.buffer = this.buffer.substring(n) this.push(bufferToPush) } tryRender () { try { this.render(this.write, this.end) // 開始渲染組件,在初始化RenderStream方法時(shí)傳入。 } catch (e) { this.emit("error", e) } } tryNext () { try { this.next() // 繼續(xù)渲染組件 } catch (e) { this.emit("error", e) } } _read (n: number) { this.expectedSize = n // 可能最后一個(gè)塊增加了緩沖區(qū)到大于2 n,這意味著我們需要通過多次讀取調(diào)用來消耗它 // down to < n. if (isTrue(this.done)) { // 如果為true,則表示渲染完畢; this.push(null) //觸發(fā)結(jié)束信號 return } if (this.buffer.length >= n) { // 緩沖區(qū)字符串長度足夠,把緩沖區(qū)內(nèi)容推入讀取隊(duì)列。 this.pushBySize(n) return } if (isUndef(this.next)) { this.tryRender() //false,開始渲染組件 } else { this.tryNext() //繼續(xù)渲染組件 } } }
回顧一下,首先調(diào)用renderToStream(new Vue(option))創(chuàng)建好stream對象后,通過stream.pipe()方法把數(shù)據(jù)發(fā)送到一個(gè)WritableStream中,會觸發(fā)RenderToStream內(nèi)部_read方法的調(diào)用,不斷把渲染的組件推入讀取隊(duì)列中,這個(gè)WritableStream就可以不斷地讀取到組件的數(shù)據(jù),然后輸出,這樣就實(shí)現(xiàn)了流式服務(wù)端渲染技術(shù)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/93079.html
摘要:公司的招聘要求都提到了至少熟悉其中一種前端框架,有前端工程化與模塊化開發(fā)實(shí)踐經(jīng)驗(yàn)相關(guān)字眼。我們主要從端公眾號移動端小程序三大平臺進(jìn)行前端的技術(shù)選型,并來說說選其技術(shù)的幾大優(yōu)勢。技術(shù)的優(yōu)勢互聯(lián)網(wǎng)前端大潮后,前端出現(xiàn)了大框架,分別是與。 1、技術(shù)選型的背景前端技術(shù)發(fā)展日新月異,互聯(lián)網(wǎng)上出現(xiàn)的新型框架也比較多,如何讓新招聘的人員...
摘要:所以,這次就來聊聊組件的服務(wù)器端渲染。這種模式下,后端只提供接口,傳統(tǒng)的服務(wù)器端路由模板渲染則都有層接管。這樣,前端開發(fā)人員可以自由的決定哪些組件需要在服務(wù)器端渲染,哪些組件可以放在客戶端渲染,前后端完全解耦,但又保留了服務(wù)器端渲染的功能。 細(xì)說 Vue 組件的服務(wù)器端渲染 聲明:需要讀者對 NodeJs、Vue 服務(wù)器端渲染有一定的了解 現(xiàn)在,前后端分離與客戶端渲染已經(jīng)成為前端開發(fā)的...
摘要:有目錄結(jié)構(gòu)書寫方式組件集成項(xiàng)目構(gòu)建等的約束,整個(gè)應(yīng)用中是沒有文件的,所有的響應(yīng)都是動態(tài)渲染的,包括里面的元信息路徑等。更多參考細(xì)說后端模板渲染客戶端渲染中間層服務(wù)器端渲染開發(fā)工具開發(fā)時(shí)主要會用到的工具。 vue 前端項(xiàng)目技術(shù)選型、開發(fā)工具、周邊生態(tài) 聲明:這不是一篇介紹 Vue 基礎(chǔ)知識的文章,需要熟悉 Vue 相關(guān)知識 主架構(gòu):vue, vue-router, vuex UI 框...
摘要:有目錄結(jié)構(gòu)書寫方式組件集成項(xiàng)目構(gòu)建等的約束,整個(gè)應(yīng)用中是沒有文件的,所有的響應(yīng)都是動態(tài)渲染的,包括里面的元信息路徑等。更多參考細(xì)說后端模板渲染客戶端渲染中間層服務(wù)器端渲染開發(fā)工具開發(fā)時(shí)主要會用到的工具。 vue 前端項(xiàng)目技術(shù)選型、開發(fā)工具、周邊生態(tài) 聲明:這不是一篇介紹 Vue 基礎(chǔ)知識的文章,需要熟悉 Vue 相關(guān)知識 主架構(gòu):vue, vue-router, vuex UI 框...
摘要:平臺主要功能如下支持客戶端渲染和服務(wù)端渲染微信登錄鑒權(quán)頁面組件增刪改查,復(fù)制移動等圖片上傳微信文章一鍵復(fù)制等等動態(tài)組件的配置原理之后專門用一篇文章詳細(xì)寫吧持續(xù)集成這個(gè)其實(shí)也不算是項(xiàng)目,算是前端的工具。 2017年算是踏入真正的前端的一年,從實(shí)習(xí)到去年,說是前端的崗位,但卻因?yàn)閷?shí)習(xí)生的身份、公司技術(shù)不夠等原因,一直停留在傳統(tǒng)的html+css+jq,那時(shí)候感覺前端的世界在翻天覆地地變化,...
閱讀 1240·2021-11-22 13:54
閱讀 1425·2021-11-22 09:34
閱讀 2698·2021-11-22 09:34
閱讀 4009·2021-10-13 09:39
閱讀 3342·2019-08-26 11:52
閱讀 3361·2019-08-26 11:50
閱讀 1529·2019-08-26 10:56
閱讀 1913·2019-08-26 10:44