Vue.js?狀態(tài)管理及?SSR解析
前端狀態(tài)管理出現(xiàn)的意義及解決的問題
隨著前端應(yīng)用的逐步復(fù)雜,我們的組件中需要使用越來越多的狀態(tài)。有的時(shí)候我們需要使用子組件將狀態(tài)傳遞給父組件就會(huì)比較復(fù)雜,數(shù)據(jù)的向上傳遞過程我們可能會(huì)使用回調(diào)函數(shù)或是數(shù)據(jù)綁定的形式去處理,就會(huì)讓代碼晦澀難懂。
我們需要一種方式,能夠讓數(shù)據(jù)在所有組件中共享,同時(shí)能以簡單的方式進(jìn)行傳遞,這種組織數(shù)據(jù)的方式就是狀態(tài)管理。我們很自然的就想到,把數(shù)據(jù)放到所有需要使用的組件的公共祖先上,在使用時(shí)自上而下傳遞即可。
在 vue.js
中,我們主要說的狀態(tài)管理庫就是 vuex
,當(dāng)然,只要你能實(shí)現(xiàn)有條理的組織數(shù)據(jù),那么它都可以認(rèn)為是一種狀態(tài)管理庫。
事實(shí)上,我們可以簡單的這樣理解【狀態(tài)管理】這個(gè)詞,vuex
實(shí)際上做的事情就是:
- 在頂層實(shí)現(xiàn)一個(gè)數(shù)據(jù)管理的倉庫
store
,將所有組件間需要共享的數(shù)據(jù)放置于此; - 同時(shí)組件也可以對(duì)這個(gè)
store
內(nèi)的數(shù)據(jù)進(jìn)行更新,同時(shí)更新完之后響應(yīng)式更新所有使用此數(shù)據(jù)組件的視圖;
Vuex 源碼解讀
Vuex 公共方法
- 路徑:
src\util.js
export function find(list, f) { return list.filter(f)[0]; } export function deepCopy(obj, cache = []) { if (obj === null || typeof obj !== 'object') { return obj; } const hit = find(cache, c => c.original === obj); if (hit) { return hit.copy; } const copy = Array.isArray(obj) ? [] : {}; cache.push({ original: obj, copy, }); Object.keys(obj).forEach(key => { copy[key] = deepCopy(obj[key], cache); }); return copy; } export function forEachValue(obj, fn) { Object.keys(obj).forEach(key => fn(obj[key], key)); } export function isObject(obj) { return obj !== null && typeof obj === 'object'; } export function isPromise(val) { return val && typeof val.then === 'function'; } export function assert(condition, msg) { if (!condition) throw new Error(`[vuex] ${msg}`); } export function partial(fn, arg) { return function () { return fn(arg); }; }
Vuex 介紹及深入使用
在說 vuex
之前,我們必須說一說 flux
架構(gòu),flux
架構(gòu)可謂是狀態(tài)管理的鼻祖。
flux
架構(gòu)最早由 facebook
推出,主要是為了處理當(dāng)時(shí)他們的 react
框架下狀態(tài)管理的問題,但在當(dāng)時(shí)來講,整個(gè)設(shè)計(jì)比較復(fù)雜,后來人們簡化了其中的一些理念,但是保留了核心思想,繼而依據(jù)框架實(shí)現(xiàn)了很多不同的狀態(tài)管理庫,例如 redux
,vuex
等等。其中 redux
大多數(shù)被用在了 react
項(xiàng)目中,而 vuex
就是在 vue
框架中實(shí)現(xiàn)的這么一個(gè) flux
架構(gòu)的狀態(tài)管理庫。
**flux
架構(gòu)約定,存放數(shù)據(jù)的地方稱為 store
,store
內(nèi)部的 state
是數(shù)據(jù)本身,我們必須通過 action
才能修改 store
里的 state
。**這里的 action
指的意思是 行為,在大部分實(shí)現(xiàn)里面是一個(gè)函數(shù),通過調(diào)用函數(shù)來更改 store
內(nèi)部的 state
。
vuex
中,我們可以通過 mutation
來【同步】的改變 state
,這時(shí)候就可以在組件中通過 commit
進(jìn)行調(diào)用更改 state
。
同樣的,我們也可以通過 action
來【異步】更改 state
,不過在 action
中,我們還是需要調(diào)用 mutation
。
Vuex 使用(官網(wǎng))
網(wǎng)址鏈接:vuex.vuejs.org/zh/guide/st…
1、基本框架
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules: {}, });
2、基本使用
./store/index.js
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { count: 0, }, mutations: { increment(state, payload = 1) { state.count += payload; }, }, actions: {}, modules: {}, });
./Home.vue
<template> <div class="home"> <div>count: {{ count }}</div> <button @click="increment">增加</button> <button @class="decrement">減少</button> </div> </template> <script> export default { name: 'Home', computed: { count() { return this.$store.state.count; }, }, methods: { increment() { // 使用 commit 派發(fā) mutation 事件 // this.$store.commit("increment"); // 可以傳遞參數(shù) this.$store.commit('increment', 2); }, decrement() {}, }, }; </script>
3、State
可以使用計(jì)算屬性獲取 state
中的數(shù)據(jù):
computed: { count () { return this.$store.state.count } }
3.1 mapState 輔助函數(shù)
當(dāng)一個(gè)組件需要獲取多個(gè)狀態(tài)的時(shí)候,將這些狀態(tài)都聲明為計(jì)算屬性會(huì)有些重復(fù)和冗余。為了解決這個(gè)問題,我們可以使用 mapState
輔助函數(shù)幫助我們生成計(jì)算屬性。
import { mapState } from "vuex"; computed: { ...mapState(["num"]) }
4、Getter
Vuex
允許我們在 store
中定義 getter
(可以認(rèn)為是 store
的計(jì)算屬性)。就像計(jì)算屬性一樣,getter
的返回值會(huì)根據(jù)它的依賴被緩存起來,且只有當(dāng)它的依賴值發(fā)生了改變才會(huì)被重新計(jì)算。
Getter
接受 state
作為其第一個(gè)參數(shù):
const store = new Vuex.Store({ state: { todos: [ { id: 1, text: 'foo', done: true }, { id: 2, text: 'bar', done: false }, ], }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done); }, }, });
4.1 通過屬性訪問
Getter
會(huì)暴露為 store.getters
對(duì)象,你可以以屬性的形式訪問這些值:
store.getters.doneTodos; // -> [{ id: 1, text: '...', done: true }]
Getter
也可以接受其他 getter
作為第二個(gè)參數(shù):
getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length; }; }
4.2 通過方法訪問
你也可以通過讓 getter
返回一個(gè)函數(shù),來實(shí)現(xiàn)給 getter
傳參。
getters: { // ... getTodoById: state => id => { return state.todos.find(todo => todo.id === id); }; }
store.getters.getTodoById(2); // -> { id: 2, text: '...', done: false }
【注意】:getter
在通過方法訪問時(shí),每次都會(huì)去進(jìn)行調(diào)用,而不會(huì)緩存結(jié)果。
4.3 mapGetters 輔助函數(shù)
mapGetters
輔助函數(shù)僅僅是將 store
中的 getter
映射到局部計(jì)算屬性:
import { mapGetters } from 'vuex'; export default { computed: { // 使用對(duì)象展開運(yùn)算符將 getter 混入 computed 對(duì)象中 ...mapGetters(['doneTodosCount', 'anotherGetter']), }, };
如果你想將一個(gè) getter
屬性另取一個(gè)名字,使用對(duì)象形式:
...mapGetters({ // 把 this.doneCount 映射為 this.$store.getters.doneTodosCount doneCount: 'doneTodosCount' })
5、Mutation
更改 Vuex
的 store
中的狀態(tài)的唯一方法是提交 mutation
。Vuex 中的 mutation
非常類似于事件:每個(gè) mutation
都有一個(gè)字符串的 事件類型 (type
) 和 一個(gè) 回調(diào)函數(shù) (handler
)。這個(gè)回調(diào)函數(shù)就是我們實(shí)際進(jìn)行狀態(tài)更改的地方,并且它會(huì)接受 state
作為第一個(gè)參數(shù):
const store = new Vuex.Store({ state: { count: 1, }, mutations: { increment(state) { // 變更狀態(tài) state.count++; }, }, });
你不能直接調(diào)用一個(gè) mutation handler
。這個(gè)選項(xiàng)更像是事件注冊:“當(dāng)觸發(fā)一個(gè)類型為 increment
的 mutation
時(shí),調(diào)用此函數(shù)。”要喚醒一個(gè) mutation handler
,你需要以相應(yīng)的 type
調(diào)用 store.commit
方法:
store.commit('increment');
5.1 提交載荷(Payload)
你可以向 store.commit
傳入額外的參數(shù),即 mutation
的 載荷(payload
):
mutations: { increment (state, n) { state.count += n } } // 使用方法 store.commit('increment', 10)
在大多數(shù)情況下,載荷應(yīng)該是一個(gè)對(duì)象,這樣可以包含多個(gè)字段并且記錄的 mutation
會(huì)更易讀:
mutations: { increment (state, payload) { state.count += payload.amount } } // 使用方法 store.commit('increment', { amount: 10 })
對(duì)象風(fēng)格的提交方式:
提交 mutation
的另一種方式是直接使用包含 type
屬性的對(duì)象:
store.commit({ type: 'increment', amount: 10, });
當(dāng)使用對(duì)象風(fēng)格的提交方式,整個(gè)對(duì)象都作為載荷傳給 mutation
函數(shù),因此 handler
保持不變:
mutations: { increment (state, payload) { state.count += payload.amount } }
5.2 使用常量替代 Mutation 事件類型
使用常量替代 mutation
事件類型在各種 Flux
實(shí)現(xiàn)中是很常見的模式。這樣可以使 linter
之類的工具發(fā)揮作用,同時(shí)把這些常量放在單獨(dú)的文件中可以讓你的代碼合作者對(duì)整個(gè) app
包含的 mutation
一目了然:
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION';
// store.js import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // 我們可以使用 ES2015 風(fēng)格的計(jì)算屬性命名功能來使用一個(gè)常量作為函數(shù)名 [SOME_MUTATION] (state) { // mutate state } } })
5.3 Mutation 必須是同步函數(shù)
一條重要的原則就是要記住 mutation
必須是同步函數(shù)。為什么?請(qǐng)參考下面的例子:
mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } }
現(xiàn)在想象,我們正在 debug
一個(gè) app
并且觀察 devtool
中的 mutation
日志。每一條 mutation
被記錄,devtools
都需要捕捉到前一狀態(tài)和后一狀態(tài)的快照。然而,在上面的例子中 mutation
中的異步函數(shù)中的回調(diào)讓這不可能完成:因?yàn)楫?dāng) mutation
觸發(fā)的時(shí)候,回調(diào)函數(shù)還沒有被調(diào)用,devtools
不知道什么時(shí)候回調(diào)函數(shù)實(shí)際上被調(diào)用 —— 實(shí)質(zhì)上任何在回調(diào)函數(shù)中進(jìn)行的狀態(tài)的改變都是不可追蹤的。
5.4 在組件中提交 Mutation
你可以在組件中使用 this.$store.commit('xxx')
提交 mutation
,或者使用 mapMutations
輔助函數(shù)將組件中的 methods
映射為 store.commit
調(diào)用(需要在根節(jié)點(diǎn)注入 store
)。
import { mapMutations } from 'vuex'; export default { // ... methods: { ...mapMutations([ 'increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')` // `mapMutations` 也支持載荷: 'incrementBy', // 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment', // 將 `this.add()` 映射為 `this.$store.commit('increment')` }), }, };
6、Action
Action
類似于 mutation
,不同在于:
Action
提交的是mutation
,而不是直接變更狀態(tài)。Action
可以包含任意異步操作。
讓我們來注冊一個(gè)簡單的 action
:
const store = new Vuex.Store({ state: { count: 0, }, mutations: { increment(state) { state.count++; }, }, actions: { increment(context) { context.commit('increment'); }, }, });
Action
函數(shù)接受一個(gè)與 store
實(shí)例具有相同方法和屬性的 context
對(duì)象,因此你可以調(diào)用 context.commit
提交一個(gè) mutation
,或者通過 context.state
和 context.getters
來獲取 state
和 getters
。當(dāng)我們在之后介紹到 Modules 時(shí),你就知道 context
對(duì)象為什么不是 store
實(shí)例本身了。
實(shí)踐中,我們會(huì)經(jīng)常用到 ES2015
的參數(shù)解構(gòu)來簡化代碼(特別是我們需要調(diào)用 commit
很多次的時(shí)候):
actions: { increment ({ commit, state, getters }) { commit('increment') } }
7、分發(fā) Action
Action
通過 store.dispatch
方法觸發(fā):
store.dispatch('increment');
乍一眼看上去感覺多此一舉,我們直接分發(fā) mutation
豈不更方便?實(shí)際上并非如此,還記得 mutation
必須同步執(zhí)行這個(gè)限制么?Action
就不受約束!我們可以在 action
內(nèi)部執(zhí)行異步操作:
actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
Actions
支持同樣的載荷方式和對(duì)象方式進(jìn)行分發(fā):
// 以載荷形式分發(fā) store.dispatch('incrementAsync', { amount: 10, }); // 以對(duì)象形式分發(fā) store.dispatch({ type: 'incrementAsync', amount: 10, });
7.1 在組件中分發(fā) Action
你在組件中使用 this.$store.dispatch('xxx')
分發(fā) action
,或者使用 mapActions
輔助函數(shù)將組件的 methods
映射為 store.dispatch
調(diào)用(需要先在根節(jié)點(diǎn)注入 store
):
import { mapActions } from 'vuex'; export default { // ... methods: { ...mapActions([ 'increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')` // `mapActions` 也支持載荷: 'incrementBy', // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)` ]), ...mapActions({ add: 'increment', // 將 `this.add()` 映射為 `this.$store.dispatch('increment')` }), }, };
7.2 組合 Action
Action
通常是 異步 的,那么如何知道 action
什么時(shí)候結(jié)束呢?更重要的是,我們?nèi)绾尾拍芙M合多個(gè) action
,以處理更加復(fù)雜的異步流程?
首先,你需要明白 store.dispatch
可以處理被觸發(fā)的 action
的處理函數(shù)返回的 Promise
,并且 store.dispatch
仍舊返回 Promise
:
actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) } }
現(xiàn)在你可以:
store.dispatch('actionA').then(() => { // ... });
在另外一個(gè) action
中也可以:
actions: { // ... actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) } }
最后,如果我們利用 async / await
,我們可以如下組合 action
:
// 假設(shè) getData() 和 getOtherData() 返回的是 Promise actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } }
一個(gè)
store.dispatch
在不同模塊中可以觸發(fā)多個(gè)action
函數(shù)。在這種情況下,只有當(dāng)所有觸發(fā)函數(shù)完成后,返回的Promise
才會(huì)執(zhí)行。
8、嚴(yán)格模式
開啟嚴(yán)格模式,僅需在創(chuàng)建 store
的時(shí)候傳入 strict: true
:
const store = new Vuex.Store({ // ... strict: true, });
在嚴(yán)格模式下,無論何時(shí)發(fā)生了狀態(tài)變更且不是由 mutation
函數(shù)引起的,將會(huì)拋出錯(cuò)誤(但是數(shù)據(jù)還是會(huì)改變)。這能保證所有的狀態(tài)變更都能被調(diào)試工具跟蹤到。
8.1 開發(fā)環(huán)境與發(fā)布環(huán)境
不要在發(fā)布環(huán)境下啟用嚴(yán)格模式! 嚴(yán)格模式會(huì)深度監(jiān)測狀態(tài)樹來檢測不合規(guī)的狀態(tài)變更 —— 請(qǐng)確保在發(fā)布環(huán)境下關(guān)閉嚴(yán)格模式,以避免性能損失。
類似于插件,我們可以讓構(gòu)建工具來處理這種情況:
const store = new Vuex.Store({ // ... strict: process.env.NODE_ENV !== 'production', });
vue.js 服務(wù)端渲染介紹
大多數(shù)我們使用的 UI
框架如 vue
和 react
,都是在客戶端進(jìn)行渲染,也就是說每個(gè)用戶在加載進(jìn)來我們所有的 html
文件和 js
文件之后,才開始渲染頁面的內(nèi)容。
但是這樣做會(huì)有兩個(gè)問題,一個(gè)是如果用戶網(wǎng)絡(luò)速度比較慢,如果我們渲染的內(nèi)容比較多的話,就會(huì)產(chǎn)生一個(gè)延遲,造成不好的用戶體驗(yàn)。另一個(gè)是某些爬蟲,例如百度的搜索收錄的爬蟲,在爬取你的頁面時(shí),獲取不到你的頁面的真實(shí)內(nèi)容,導(dǎo)致站點(diǎn) SEO
權(quán)重變低。
所以很多需要 SEO
的頁面,都需要在服務(wù)端提前渲染好 html
的內(nèi)容,在用戶訪問時(shí)先返回給用戶內(nèi)容,這楊對(duì)用戶和爬蟲都非常友好。
我們可以通過直接在頁面上右擊查看網(wǎng)頁源代碼,來查看一個(gè)頁面是否有服務(wù)端渲染。
1、客戶端渲染和服務(wù)端渲染
客戶端渲染
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>客戶端渲染</title> </head> <body> <script> document.body.innerHTML = '<div>你好</div>'; </script> </body> </html>
在 Network
的中 Preview
中無數(shù)據(jù),在 Response
中的沒有 DOM 標(biāo)簽:
查看網(wǎng)頁源代碼:
2、服務(wù)端渲染
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>服務(wù)端渲染</title> </head> <body> <div>你好</div> </body> </html>
在 Network
的中 Preview
中有數(shù)據(jù),在 Response
中的有 DOM 標(biāo)簽:
查看網(wǎng)頁源代碼:
客戶端路由
在控制臺(tái)中可以看到,切換路由的時(shí)候并沒有發(fā)起 ajax
請(qǐng)求。
3、服務(wù)端渲染實(shí)例
vue.js
的服務(wù)端渲染非常簡單,我們只需要在 node.js
中通過 vue-server-renderer
模塊,調(diào)用對(duì)應(yīng)服務(wù)端渲染的渲染器對(duì)組件渲染即可,他就會(huì)生成組件對(duì)應(yīng)的 html
內(nèi)容。渲染成功的 html
標(biāo)簽,我們可以直接返回到客戶端作為初始請(qǐng)求 html
的返回值。
- 安裝依賴
yarn add express vue vue-server-renderer
./index.js
const Vue = require('vue'); const createRenderer = require('vue-server-renderer').createRenderer; const vm = new Vue({ data() { return { count: 100, }; }, template: `<div>{{ count }}</div>`, }); const renderer = createRenderer(); renderer.renderToString(vm, (err, html) => { console.log('html ==========', html); // <div data-server-rendered="true">100</div> });
./index.js
const Vue = require('vue'); const createRenderer = require('vue-server-renderer').createRenderer; const express = require('express'); const fs = require('fs'); const path = require('path'); const app = express(); // ! 服務(wù)端路由 app.get('*', function (req, res) { const vm = new Vue({ data() { return { url: `服務(wù)端路由 ${req.url}`, count: 100, }; }, template: `<div>{{ url }} - {{ count }}</div>`, }); const renderer = createRenderer({ // 設(shè)置模板 template: fs.readFileSync(path.resolve(__dirname, './index.template.html'), 'utf-8'), }); renderer.renderToString(vm, (err, html) => { res.send(html); }); }); const PORT = 8080; app.listen(PORT, () => { console.log(`服務(wù)器啟動(dòng)在 ${PORT} 端口`); });
./index.template.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>服務(wù)端渲染Demo</title> </head> <body> <h1>這里是模板</h1> <!--! 中間不能帶空格 --> <!--vue-ssr-outlet--> </body> </html>
- 執(zhí)行
index.js
文件:node ./index.js
我們需要注意的一點(diǎn)是,在服務(wù)端渲染組件,我們使用不了 window
、location
等瀏覽器環(huán)境中的對(duì)象,所以如果組件內(nèi)部使用了這種內(nèi)容會(huì)報(bào)錯(cuò)。
同時(shí),在服務(wù)端渲染時(shí)我們要注意,組件的生命周期也會(huì)只執(zhí)行 beforeCreate
和 created
這兩個(gè),所以在此聲明周期里面不能使用 window
,但是可以在其他聲明周期比如 mounted
中使用。還有渲染的數(shù)據(jù),對(duì)于服務(wù)端渲染的組件來說,我們不應(yīng)該發(fā)請(qǐng)求獲取組件數(shù)據(jù),而是應(yīng)該直接渲染時(shí)使用數(shù)據(jù)進(jìn)行渲染。
路由也是如此,在 vue
客戶端使用路由的時(shí)候,我們也需要在服務(wù)端對(duì)路由進(jìn)行匹配,從而得知具體需要渲染的組件是哪個(gè)。
3、同構(gòu) - 客戶端渲染和服務(wù)端渲染
參考文檔(
Vue
文檔中 -Vue
服務(wù)端渲染):ssr.vuejs.org/zh/
./webpack.config.js
/* 客戶端 webpack 配置 */ const path = require('path'); const webpack = require('webpack'); module.exports = { entry: './src/entry-client.js', output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js', }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, }, ], }, resolve: { extensions: ['*', '.js', '.vue'], }, };
./webpack.server.config.js
/* 服務(wù)端 webpack 配置 */ const path = require('path'); const webpack = require('webpack'); const merge = require('webpack-merge'); const baseWebpackConfig = require('./webpack.config'); const config = merge(baseWebpackConfig, { target: 'node', entry: { app: './src/entry-server.js', }, output: { path: __dirname, filename: 'server.bundle.js', libraryTarget: 'commonjs2', }, }); console.log('config ============ ', config); module.exports = config;
./package.json
{ "name": "06", "version": "1.0.0", "description": "", "main": "webpack.config.js", "dependencies": { "babel-core": "^6.26.3", "babel-loader": "^7.1.5", "babel-preset-env": "^1.7.0", "babel-preset-stage-3": "^6.24.1", "express": "^4.17.1", "vue": "^2.6.11", "vue-router": "^3.3.2", "vue-server-renderer": "^2.6.11", "vuex": "^3.4.0", "webpack": "^3.12.0", "webpack-merge": "^4.2.2" }, "devDependencies": { "vue-loader": "^13.7.3", "vue-template-compiler": "^2.6.11" }, "scripts": { "build-server": "webpack --config webpack.server.config.js", "build-client": "webpack --config webpack.config.js" }, "keywords": [], "author": "", "license": "ISC" }
./src/App.vue
<template> <div> <h1>this is App.vue</h1> <router-link to="/">root</router-link> <router-link to="/about">about</router-link> <router-view></router-view> </div> </template>
./src/Home.vue
<template> <div>Home {{ $store.state.timestamp }}</div> </template>
./src/About.vue
<template> <div>about {{ $store.state.timestamp }}</div> </template>
./index.js
/* entry-client 和 entry-server 共同的文件 */ import Vue from 'vue'; import Router from 'vue-router'; import Vuex from 'vuex'; import Home from './Home'; import About from './About'; import App from './App'; Vue.use(Router); Vue.use(Vuex); export function createApp() { const store = new Vuex.Store({ state: { timestamp: new Date().getTime(), }, }); if (typeof window !== 'undefined' && window.store) { store.replaceState(window.store); } const router = new Router({ mode: 'history', routes: [ { path: '/', component: Home }, { path: '/about', component: About }, ], }); const vm = new Vue({ router, store, render: h => h(App), }); return { vm, router, store }; }
./src/entry-server.js
第一種
/* 服務(wù)端渲染 - 入口 */ const express = require('express'); const fs = require('fs'); const path = require('path'); const renderer = require('vue-server-renderer').createRenderer(); const { createApp } = require('./index'); const app = express(); app.use('/dist', express.static(path.join(__dirname, './dist'))); app.get('/build.js', function (req, res) { const pathUrl = path.resolve(process.cwd(), './dist/build.js'); console.log(pathUrl); res.sendFile(pathUrl); }); app.get('*', function (req, res) { const url = req.url; const { vm, router } = createApp(); router.push(url); /* const matchedComponents: Array<Component> = router.getMatchedComponents(location?) 返回目標(biāo)位置或是當(dāng)前路由匹配的組件數(shù)組 (是數(shù)組的定義/構(gòu)造類,不是實(shí)例)。通常在服務(wù)端渲染的數(shù)據(jù)預(yù)加載時(shí)使用。 */ const matchedComponent = router.getMatchedComponents(); if (!matchedComponent) { // 404 處理 } else { renderer.renderToString(vm, function (err, html) { res.send(html); }); } }); const PORT = 8080; app.listen(PORT, () => { console.log(`服務(wù)器啟動(dòng)在 ${PORT} 端口`); }); /* 此時(shí)可以執(zhí)行 yarn build-server 編譯 entry-server 文件,生成 server.bundle.js 執(zhí)行 node ./server.bundle.js 查看服務(wù)端路由的結(jié)果 */
./src/entry-server.js
第二種
/* 服務(wù)端渲染 - 入口 */ const express = require('express'); const fs = require('fs'); const path = require('path'); const renderer = require('vue-server-renderer').createRenderer({ template: fs.readFileSync(path.resolve(process.cwd(), './index.template.html'), 'utf-8'), }); const { createApp } = require('./index'); const app = express(); app.use('/dist', express.static(path.join(__dirname, './dist'))); app.get('/build.js', function (req, res) { const pathUrl = path.resolve(process.cwd(), './dist/build.js'); console.log(pathUrl); res.sendFile(pathUrl); }); app.get('*', function (req, res) { const url = req.url; const { vm, router, store } = createApp(); router.push(url); /* const matchedComponents: Array<Component> = router.getMatchedComponents(location?) 返回目標(biāo)位置或是當(dāng)前路由匹配的組件數(shù)組 (是數(shù)組的定義/構(gòu)造類,不是實(shí)例)。通常在服務(wù)端渲染的數(shù)據(jù)預(yù)加載時(shí)使用。 */ const matchedComponent = router.getMatchedComponents(); if (!matchedComponent) { // 404 處理 } else { renderer.renderToString(vm, function (err, html) { res.send(html); }); } }); const PORT = 8080; app.listen(PORT, () => { console.log(`服務(wù)器啟動(dòng)在 ${PORT} 端口`); });
./src/entry-client.js
/* 客戶端渲染 - 入口 */ import { createApp } from './index'; const { vm } = createApp(); vm.$mount('#app');
./index.template.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> <h1>這里是模板</h1> <!--vue-ssr-outlet--> </div> <script src="/build.js"></script> </body> </html>
執(zhí)行 yarn build-client
編譯客戶端;
執(zhí)行 yarn build-server
編譯服務(wù)端;
執(zhí)行 node ./server.bundle.js
啟動(dòng)服務(wù)器,打開瀏覽器輸入網(wǎng)址 http://localhost:8080/
;
到此這篇關(guān)于Vue.js 狀態(tài)管理及 SSR解析的文章就介紹到這了,更多相關(guān)Vue.js SSR內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Egg Vue SSR 服務(wù)端渲染數(shù)據(jù)請(qǐng)求與asyncData
- 15分鐘學(xué)會(huì)vue項(xiàng)目改造成SSR(小白教程)
- vue ssr+koa2構(gòu)建服務(wù)端渲染的示例代碼
- Vue SSR 即時(shí)編譯技術(shù)的實(shí)現(xiàn)
- Vue使用預(yù)渲染代替SSR的方法
- 如何構(gòu)建 vue-ssr 項(xiàng)目的方法步驟
- vuecli項(xiàng)目構(gòu)建SSR服務(wù)端渲染的實(shí)現(xiàn)
- vue的ssr服務(wù)端渲染示例詳解
- vue中vue-router的使用說明(包括在ssr中的使用)
- 關(guān)于VueSSR的一些理解和詳細(xì)配置
相關(guān)文章
vue.js 底部導(dǎo)航欄 一級(jí)路由顯示 子路由不顯示的解決方法
下面小編就為大家分享一篇vue.js 底部導(dǎo)航欄 一級(jí)路由顯示 子路由不顯示的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03Vue集成three.js并加載glb、gltf、FBX、json模型的場景分析
這篇文章主要介紹了Vue集成three.js,并加載glb、gltf、FBX、json模型,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09vue axios 給生產(chǎn)環(huán)境和發(fā)布環(huán)境配置不同的接口地址(推薦)
這篇文章主要介紹了vue axios 給生產(chǎn)環(huán)境和發(fā)布環(huán)境配置不同的接口地址,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05Vue使用babel-polyfill兼容IE解決白屏及語法報(bào)錯(cuò)問題
這篇文章主要介紹了Vue使用babel-polyfill兼容IE解決白屏及語法報(bào)錯(cuò)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03Element UI 自定義正則表達(dá)式驗(yàn)證方法
今天小編就為大家分享一篇Element UI 自定義正則表達(dá)式驗(yàn)證方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09