vue-ssr的實現原理連載(四): vue-ssr中實現路由
上一篇文章基本已經實現了vue-ssr整理流程,接下來讓我們一起來實現vue-ssr的路由實現:
首先在上一次專案的src檔案下建立router檔案。經過前面文章的瞭解,路由應該也是一個函式,每次呼叫都返回一個新的路由例項。
src/router.js
import Vue from "vue"; import VueRouter from "vue-router"; import Foo from "./components/Foo"; Vue.use(VueRouter); export default () => { const router = new VueRouter({ mode: "history", routes: [ { path: "/", component: Foo }, { path: "/bar", component: () => import("./components/Bar.vue") } ] }); return router; };
然後我們在man.js入口檔案中引入這個路由函式,並在vue初始化函式中呼叫這個路由函式獲取一個新的路由例項掛載在vue上
src/main.js
import Vue from "vue"; import App from "./App"; import createRouter from "./router"; // const vm = new Vue({ // el: "#app", // render: h => h(App) // }); // main.js是專案的入口檔案,作用是提供vue的例項 // 將入口檔案改造為一個函式,每次呼叫都返回一個vue的例項,這樣做可以 1.根據客戶端或者服務端來新增或不新增el; 2.每次呼叫都產生一個新的例項,服務端的根本要求 export default () => { // 每次呼叫vue例項生成函式的時候呼叫一次路由生成函式,生成一個路由例項,然後再new Vue的時候進行掛載 const router = createRouter(); const app = new Vue({ router, render: h => h(App) }); return { app }; };
修改src/App.vue, 配置router-link 和 router-view
<template> <div id="app"> <router-link to="/">Foo頁面</router-link> <router-link to="/bar">Bar頁面</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'App', data() { return { }; } }; </script>
然後分別打包客戶端和服務端,開啟瀏覽器 localhost:3000 , 發現頁面報錯了:
原因是因為,我們服務中的路由 '/' 在被訪問後,會呼叫 vue-server-renderer 的渲染器去渲染模板為字串,但是渲染器並不知道該渲染哪個路由。
我們在serve.js中傳入要渲染的url:
router.get("/", async ctx => { // css樣式只能通過回撥,同步的話會有問題 ctx.body = await new Promise((resolve, reject) => { render.renderToString({ url: "/" }, (err, data) => { if (err) reject(err); resolve(data); }); });
這樣在server-entry.js中就能拿到傳入的引數,這個引數其實就是context上下文。並且我們需要在返回需要服務端渲染的app例項的時候先跳轉到指定的路由頁面,需要router例項,可以再main.js中將router也一併匯出:
src/amin.js
return { app, router };
src/server-entry.js
// content 上下文,是服務端傳入的 export default context => { // 服務端會執行這個方法 const { app, router } = createApp(); // 返回的例項應該跳轉到對應的路由 router.push(context.url); return app; };
重新打包後檢視頁面,顯示正常,並且之前的報錯也已經沒有了。
到這裡又會有一個問題,我們只是匹配了 / 路由,其他路由需要怎麼處理呢?當我們訪問bar後,頁面明顯是不對的。解決的方法就是在瀏覽器請求服務端的時候,服務端發現不是根路徑,就直接返回給server-entry.js當前的請求路徑。下面我們來寫這個中介軟體
// 路由匹配中介軟體 app.use(async ctx => { // 根據請求路徑去返回給server-entry url路徑 ctx.body = await new Promise((resolve, reject) => { render.renderToString({ url: ctx.path }, (err, data) => { if (err) reject(err); resolve(data); }); }); });
開啟瀏覽器發現bar頁面可以正常訪問了。
由於有非同步元件的問題,server-entry.js 例項返回函式最好返回一個promise, 同時vue-router 提供了一個 onReady的方法,這個方法接收兩個回撥,第一個是成功的回撥,第二個是失敗的回撥,即路由準備完成和路由準備失敗:
// 呼叫當前這個檔案產生一個vue的例項,並且需要匯出給node服務端使用 // content 上下文,是服務端傳入的 export default context => { // 服務端會執行這個方法 return new Promise((resolve, reject) => { const { app, router } = createApp(); // 返回的例項應該跳轉到對應的路由 router.push(context.url); router.onReady(() => { resolve(app); }, reject); }); };
重新整理瀏覽器一切正常,接下來還需要考慮一個問題,就是404頁面的問題: 當我們訪問一個不存在的路由的時候,頁面還是正常渲染了。
vue-router也提供了一個方法 router.getMatchedCompoents() 這個方法返回當前路徑對應匹配的路由陣列。我們可以根據這個函式的返回來判斷當前訪問路徑是否能匹配到路由,如果匹配不到的話就返回reject和一個狀態碼
router.onReady(() => { const matchs = router.getMatchedComponents(); if (matchs.length === 0) reject({ code: 404 }); resolve(app); }, reject);
然後再serve.js中可以通過錯誤處理來判斷是夠渲染正常的頁面還是404頁面
// 路由匹配中介軟體 app.use(async ctx => { try { // 根據請求路徑去返回給server-entry url路徑 ctx.body = await new Promise((resolve, reject) => { render.renderToString({ url: ctx.path }, (err, data) => { if (err) reject(err); resolve(data); }); }); } catch (err) { ctx.body = err.code; } });
開啟瀏覽器,試著訪問一個不存在的路由,頁面正確的顯示了404,再訪問一個存在的路由 /bar 頁面也正常的出現了。到此為止vue-ssr路由的簡單實現也完成了。
本節原始碼可以檢視我的github: https://github.com/Jasonwang911/vue-ssr/tree/master/step4
vue-ssr的實現原理連載(一): https://www.cnblogs.com/jasonwang2y60/p/11299503.html 原始碼: https://github.com/Jasonwang911/vue-ssr/tree/master/step1
vue-ssr的實現原理連載(二):https://www.cnblogs.com/jasonwang2y60/p/11300255.html 原始碼: https://github.com/Jasonwang911/vue-ssr/tree/master/step2
vue-ssr的實現原理連載(三):https://www.cnblogs.com/jasonwang2y60/p/11300255.html 原始碼: https://github.com/Jasonwang911/vue-ssr/tree/master/step3