這篇文章主要為大家展示了“Vue中SSR如何實現(xiàn)”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Vue中SSR如何實現(xiàn)”這篇文章吧。
商州網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)!從網(wǎng)頁設計、網(wǎng)站建設、微信開發(fā)、APP開發(fā)、自適應網(wǎng)站建設等網(wǎng)站項目制作,到程序開發(fā),運營維護。成都創(chuàng)新互聯(lián)自2013年創(chuàng)立以來到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設就選成都創(chuàng)新互聯(lián)。Vue 提供了一個官方 Demo,該 Demo 優(yōu)點是功能大而全,缺點是對新手不友好,容易讓人看蒙。因此,今天我們來寫一個更加容易上手的 Demo??偣卜秩阶?,循序漸進。
寫一個簡單的前端渲染 Demo(不包含 Ajax 數(shù)據(jù));
將前端渲染改成后端渲染(仍然不包含 Ajax 數(shù)據(jù));
在后端渲染的基礎上,加上 Ajax 數(shù)據(jù)的處理;
這部分比較簡單,就是一個頁面中包含兩個組件:Foo 和 Bar。
<!-- index.html --> <body> <div id="app"> <app></app> </div> <script src="./dist/web.js"></script> <!--這是 app.js 打包出來的 JS 文件 --> </body> // app.js,也是 webpack 打包入口 import Vue from 'vue'; import App from './App.vue'; var app = new Vue({ el: '#app', components: { App } });
// App.vue <template> <div> <foo></foo> <bar></bar> </div> </template> <script> import Foo from './components/Foo.vue'; import Bar from './components/Bar.vue'; export default { components:{ Foo, Bar } } </script>
// Foo.vue <template> <div class='foo'> <h2>Foo</h2> <p>Component </p> </div> </template> <style> .foo{ background: yellow; } </style>
// Bar.vue <template> <div class='bar'> <h2>Bar</h2> <p>Component </p> </div> </template> <style> .bar{ background: blue; } </style>
最終渲染結(jié)果如下圖所示,源碼請參考這里。
第一步的 Demo 雖不包含任何 Ajax 數(shù)據(jù),但即便如此,要把它改造成后端渲染,亦非易事。該從哪幾個方面著手呢?
拆分 JS 入口;
拆分 Webpack 打包配置;
編寫服務端渲染主體邏輯。
1. 拆分 JS 入口
在前端渲染的時候,只需要一個入口 app.js。現(xiàn)在要做后端渲染,就得有兩個 JS 文件:entry-client.js 和 entry-server.js 分別作為瀏覽器和服務器的入口。
先看 entry-client.js,它跟第一步的 app.js 有什么區(qū)別嗎? → 沒有區(qū)別,只是換了個名字而已,內(nèi)容都一樣。
再看 entry-server.js,它只需返回 App.vue 的實例。
// entry-server.js export default function createApp() { const app = new Vue({ render: h => h(App) }); return app; };
entry-server.js 與 entry-client.js 這兩個入口主要區(qū)別如下:
entry-client.js 在瀏覽器端執(zhí)行,所以需要指定 el 并且顯式調(diào)用 $mount 方法,以啟動瀏覽器的渲染。
entry-server.js 在服務端被調(diào)用,因此需要導出為一個函數(shù)。
2. 拆分 Webpack 打包配置
在第一步中,由于只有 app.js 一個入口,只需要一份 Webpack 配置文件?,F(xiàn)在有兩個入口了,自然就需要兩份 Webpack 配置文件:webpack.server.conf.js 和 webpack.client.conf.js,它們的公共部分抽象成 webpack.base.conf.js。
關于 webpack.server.conf.js,有兩個注意點:
libraryTarget: 'commonjs2' → 因為服務器是 Node,所以必須按照 commonjs 規(guī)范打包才能被服務器調(diào)用。
target: 'node' → 指定 Node 環(huán)境,避免非 Node 環(huán)境特定 API 報錯,如 document 等。
3. 編寫服務端渲染主體邏輯
Vue SSR 依賴于包 vue-server-render,它的調(diào)用支持兩種入口格式:createRenderer 和 createBundleRenderer,前者以 Vue 組件為入口,后者以打包后的 JS 文件為入口,本文采取后者。
// server.js 服務端渲染主體邏輯 // dist/server.js 就是以 entry-server.js 為入口打包出來的 JS const bundle = fs.readFileSync(path.resolve(__dirname, 'dist/server.js'), 'utf-8'); const renderer = require('vue-server-renderer').createBundleRenderer(bundle, { template: fs.readFileSync(path.resolve(__dirname, 'dist/index.ssr.html'), 'utf-8') }); server.get('/index', (req, res) => { renderer.renderToString((err, html) => { if (err) { console.error(err); res.status(500).end('服務器內(nèi)部錯誤'); return; } res.end(html); }) }); server.listen(8002, () => { console.log('后端渲染服務器啟動,端口號為:8002'); });
這一步的最終渲染效果如下圖所示,從圖中我們可以看到,組件已經(jīng)被后端成功渲染了。源碼請參考這里。
這是關鍵的一步,也是最難的一步。
假如第二步的組件各自都需要請求 Ajax 數(shù)據(jù)的話,該怎么處理呢?官方文檔給我們指出了思路,我簡要概括如下:
在開始渲染之前,預先獲取所有需要的 Ajax 數(shù)據(jù)(然后存在 Vuex 的 Store 中);
后端渲染的時候,通過 Vuex 將獲取到的 Ajax 數(shù)據(jù)分別注入到各個組件中;
把全部 Ajax 數(shù)據(jù)埋在 window.INITIAL_STATE 中,通過 HTML 傳遞到瀏覽器端;
瀏覽器端通過 Vuex 將 window.INITIAL_STATE 里面的 Ajax 數(shù)據(jù)分別注入到各個組件中。
下面談幾個重點。
我們知道,在常規(guī)的 Vue 前端渲染中,組件請求 Ajax 一般是這么寫的:“在 mounted 中調(diào)用 this.fetchData,然后在回調(diào)里面把返回數(shù)據(jù)寫到實例的 data 中,這就 ok 了?!?/p>
在 SSR 中,這是不行的,因為服務器并不會執(zhí)行 mounted 周期。那么我們是否可以把 this.fetchData
提前到 created 或者 beforeCreate 這兩個生命周期中執(zhí)行?同樣不行。原因是:this.fetchData 是異步請求,請求發(fā)出去之后,沒等數(shù)據(jù)返回呢,后端就已經(jīng)渲染完了,無法把 Ajax 返回的數(shù)據(jù)也一并渲染出來。
所以,我們得提前知道都有哪些組件有 Ajax 請求,等把這些 Ajax 請求都返回了數(shù)據(jù)之后,才開始組件的渲染。
// store.js function fetchBar() { return new Promise(function (resolve, reject) { resolve('bar ajax 返回數(shù)據(jù)'); }); } export default function createStore() { return new Vuex.Store({ state: { bar: '', }, actions: { fetchBar({commit}) { return fetchBar().then(msg => { commit('setBar', {msg}) }) } }, mutations:{ setBar(state, {msg}) { Vue.set(state, 'bar', msg); } } }) }
// Bar.uve asyncData({store}) { return store.dispatch('fetchBar'); }, computed: { bar() { return this.$store.state.bar; } }
組件的 asyncData 方法已經(jīng)定義好了,但是怎么索引到這個 asyncData 方法呢?先看我的根組件 App.vue 是怎么寫的。
// App.vue <template> <div> <h2>App.vue</h2> <p>vue with vue </p> <hr> <foo1 ref="foo_ref"></foo1> <bar1 ref="bar_ref"></bar1> <bar2 ref="bar_ref2"></bar2> </div> </template> <script> import Foo from './components/Foo.vue'; import Bar from './components/Bar.vue'; export default { components: { foo1: Foo, bar1: Bar, bar2: Bar } } </script>
從根組件 App.vue 我們可以看到,只需要解析其 components 字段,便能依次找到各個組件的 asyncData 方法了。
// entry-server.js export default function (context) { // context 是 vue-server-render 注入的參數(shù) const store = createStore(); let app = new Vue({ store, render: h => h(App) }); // 找到所有 asyncData 方法 let components = App.components; let prefetchFns = []; for (let key in components) { if (!components.hasOwnProperty(key)) continue; let component = components[key]; if(component.asyncData) { prefetchFns.push(component.asyncData({ store })) } } return Promise.all(prefetchFns).then((res) => { // 在所有組件的 Ajax 都返回之后,才最終返回 app 進行渲染 context.state = store.state; // context.state 賦值成什么,window.__INITIAL_STATE__ 就是什么 return app; }); };
還有幾個問題比較有意思:
1、是否必須使用 vue-router?→ 不是。雖然官方給出的 Demo 里面用到了 vue-router,那只不過是因為官方 Demo 是包含多個頁面的 SPA 罷了。一般情況下,是需要用 vue-router 的,因為不同路由對應不同的組件,并非每次都把所有組件的 asyncData 都執(zhí)行的。但是有例外,比如我的這個老項目,就只有一個頁面(一個頁面中包含很多的組件),所以根本不需要用到 vue-router,也照樣能做 SSR。主要的區(qū)別就是如何找到那些該被執(zhí)行的 asyncData 方法:官方 Demo 通過 vue-router,而我通過直接解析 components 字段,僅此而已。
2、是否必須使用 Vuex? → 是,但也不是,請看尤大的回答。為什么必須要有類似 Vuex 的存在?我們來分析一下。
2.1. 當預先獲取到的 Ajax 數(shù)據(jù)返回之后,Vue 組件還沒開始渲染。所以,我們得把 Ajax 先存在某個地方。
2.2. 當 Vue 組件開始渲染的時候,還得把 Ajax 數(shù)據(jù)拿出來,正確地傳遞到各個組件中。
2.3. 在瀏覽器渲染的時候,需要正確解析 window.INITIAL_STATE ,并傳遞給各個組件。
因此,我們得有這么一個獨立于視圖以外的地方,用來存儲、管理和傳遞數(shù)據(jù),這就是 Vuex 存在的理由。
3、后端已經(jīng)把 Ajax 數(shù)據(jù)轉(zhuǎn)化為 HTML 了,為什么還需要把 Ajax 數(shù)據(jù)通過 window.INITIAL_STATE 傳遞到前端? → 因為前端渲染的時候仍然需要知道這些數(shù)據(jù)。舉個例子,你寫了一個組件,給它綁定了一個點擊事件,點擊的時候打印出 this.msg 字段值?,F(xiàn)在后端是把組件 HTML 渲染出來了,但是事件的綁定肯定得由瀏覽器來完成啊,如果瀏覽器拿不到跟服務器端同樣的數(shù)據(jù)的話,在觸發(fā)組件的點擊事件的時候,又上哪兒去找 msg 字段呢?
至此,我們已經(jīng)完成了帶 Ajax 數(shù)據(jù)的后端渲染了。這一步最為復雜,也最為關鍵,需要反復思考和嘗試。具體渲染效果圖如下所示,源碼請參考這里。
效果
大功告成了嗎?還沒。人們都說 SSR 能提升首屏渲染速度,下面我們對比一下看看到底是不是真的。(同樣在 Fast 3G 網(wǎng)絡條件下)。
官方思路的變形
行文至此,關于 Vue SSR Demo便已經(jīng)結(jié)束了。后面是我結(jié)合自身項目特點的一些變形,不感興趣的讀者可以不看。
第三步官方思路有什么缺點嗎?我認為是有的:對老項目來說,改造成本比較大。需要顯式的引入 vuex,就得走 action、mutations 那一套,無論是代碼改動量還是新人學習成本,都不低。
有什么辦法能減少對舊有前端渲染項目的改動量的嗎?我是這么做的。
// store.js // action,mutations 那些都不需要了,只定義一個空 state export default function createStore() { return new Vuex.Store({ state: {} }) } // Bar.vue // tagName 是組件實例的名字,比如 bar1、bar2、foo1 等,由 entry-server.js 注入 export default { prefetchData: function (tagName) { return new Promise((resolve, reject) => { resolve({ tagName, data: 'Bar ajax 數(shù)據(jù)' }); }) } }
// entry-server.js return Promise.all(prefetchFns).then((res) => { // 拿到 Ajax 數(shù)據(jù)之后,手動將數(shù)據(jù)寫入 state,不通過 action,mutation 那一套 // state 內(nèi)部區(qū)分的 key 值就是 tagName,比如 bar1、bar2、foo1 等 res.forEach((item, key) => { Vue.set(store.state, `${item.tagName}`, item.data); }); context.state = store.state; return app; });
// ssrmixin.js // 將每個組件都需要的 computed 抽象成一個 mixin,然后注入 export default { computed: { prefetchData () { let componentTag = this.$options._componentTag; // bar1、bar2、foo1 return this.$store.state[componentTag]; } } }
以上是“Vue中SSR如何實現(xiàn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關注創(chuàng)新互聯(lián)成都網(wǎng)站設計公司行業(yè)資訊頻道!
另外有需要云服務器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。
網(wǎng)頁標題:Vue中SSR如何實現(xiàn)-創(chuàng)新互聯(lián)
網(wǎng)站鏈接:http://jinyejixie.com/article32/decdpc.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供微信小程序、Google、網(wǎng)站排名、品牌網(wǎng)站制作、網(wǎng)站內(nèi)鏈、服務器托管
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)