Vue.js?狀態(tài)管理及?SSR解析
前端狀態(tài)管理出現(xiàn)的意義及解決的問題
隨著前端應用的逐步復雜,我們的組件中需要使用越來越多的狀態(tài)。有的時候我們需要使用子組件將狀態(tài)傳遞給父組件就會比較復雜,數(shù)據(jù)的向上傳遞過程我們可能會使用回調(diào)函數(shù)或是數(shù)據(jù)綁定的形式去處理,就會讓代碼晦澀難懂。

我們需要一種方式,能夠讓數(shù)據(jù)在所有組件中共享,同時能以簡單的方式進行傳遞,這種組織數(shù)據(jù)的方式就是狀態(tài)管理。我們很自然的就想到,把數(shù)據(jù)放到所有需要使用的組件的公共祖先上,在使用時自上而下傳遞即可。
在 vue.js 中,我們主要說的狀態(tài)管理庫就是 vuex,當然,只要你能實現(xiàn)有條理的組織數(shù)據(jù),那么它都可以認為是一種狀態(tài)管理庫。
事實上,我們可以簡單的這樣理解【狀態(tài)管理】這個詞,vuex 實際上做的事情就是:
- 在頂層實現(xiàn)一個數(shù)據(jù)管理的倉庫
store,將所有組件間需要共享的數(shù)據(jù)放置于此; - 同時組件也可以對這個
store內(nèi)的數(shù)據(jù)進行更新,同時更新完之后響應式更新所有使用此數(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 推出,主要是為了處理當時他們的 react 框架下狀態(tài)管理的問題,但在當時來講,整個設(shè)計比較復雜,后來人們簡化了其中的一些理念,但是保留了核心思想,繼而依據(jù)框架實現(xiàn)了很多不同的狀態(tài)管理庫,例如 redux,vuex 等等。其中 redux 大多數(shù)被用在了 react 項目中,而 vuex 就是在 vue 框架中實現(xiàn)的這么一個 flux 架構(gòu)的狀態(tài)管理庫。
**flux 架構(gòu)約定,存放數(shù)據(jù)的地方稱為 store,store 內(nèi)部的 state 是數(shù)據(jù)本身,我們必須通過 action 才能修改 store 里的 state。**這里的 action 指的意思是 行為,在大部分實現(xiàn)里面是一個函數(shù),通過調(diào)用函數(shù)來更改 store 內(nèi)部的 state。

vuex 中,我們可以通過 mutation 來【同步】的改變 state,這時候就可以在組件中通過 commit 進行調(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
可以使用計算屬性獲取 state 中的數(shù)據(jù):
computed: {
count () {
return this.$store.state.count
}
}3.1 mapState 輔助函數(shù)
當一個組件需要獲取多個狀態(tài)的時候,將這些狀態(tài)都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState 輔助函數(shù)幫助我們生成計算屬性。
import { mapState } from "vuex";
computed: {
...mapState(["num"])
}4、Getter
Vuex 允許我們在 store 中定義 getter(可以認為是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據(jù)它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算。
Getter 接受 state 作為其第一個參數(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 會暴露為 store.getters 對象,你可以以屬性的形式訪問這些值:
store.getters.doneTodos; // -> [{ id: 1, text: '...', done: true }]Getter 也可以接受其他 getter 作為第二個參數(shù):
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length;
};
}4.2 通過方法訪問
你也可以通過讓 getter 返回一個函數(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 在通過方法訪問時,每次都會去進行調(diào)用,而不會緩存結(jié)果。
4.3 mapGetters 輔助函數(shù)
mapGetters 輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex';
export default {
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters(['doneTodosCount', 'anotherGetter']),
},
};如果你想將一個 getter 屬性另取一個名字,使用對象形式:
...mapGetters({
// 把 this.doneCount 映射為 this.$store.getters.doneTodosCount
doneCount: 'doneTodosCount'
})5、Mutation
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調(diào)函數(shù) (handler)。這個回調(diào)函數(shù)就是我們實際進行狀態(tài)更改的地方,并且它會接受 state 作為第一個參數(shù):
const store = new Vuex.Store({
state: {
count: 1,
},
mutations: {
increment(state) {
// 變更狀態(tài)
state.count++;
},
},
});你不能直接調(diào)用一個 mutation handler。這個選項更像是事件注冊:“當觸發(fā)一個類型為 increment 的 mutation 時,調(diào)用此函數(shù)。”要喚醒一個 mutation handler,你需要以相應的 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ù)情況下,載荷應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
// 使用方法
store.commit('increment', {
amount: 10
})
對象風格的提交方式:
提交 mutation 的另一種方式是直接使用包含 type 屬性的對象:
store.commit({
type: 'increment',
amount: 10,
});當使用對象風格的提交方式,整個對象都作為載荷傳給 mutation 函數(shù),因此 handler 保持不變:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}5.2 使用常量替代 Mutation 事件類型
使用常量替代 mutation 事件類型在各種 Flux 實現(xiàn)中是很常見的模式。這樣可以使 linter 之類的工具發(fā)揮作用,同時把這些常量放在單獨的文件中可以讓你的代碼合作者對整個 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 風格的計算屬性命名功能來使用一個常量作為函數(shù)名
[SOME_MUTATION] (state) {
// mutate state
}
}
})5.3 Mutation 必須是同步函數(shù)
一條重要的原則就是要記住 mutation 必須是同步函數(shù)。為什么?請參考下面的例子:
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}現(xiàn)在想象,我們正在 debug 一個 app 并且觀察 devtool 中的 mutation 日志。每一條 mutation 被記錄,devtools 都需要捕捉到前一狀態(tài)和后一狀態(tài)的快照。然而,在上面的例子中 mutation 中的異步函數(shù)中的回調(diào)讓這不可能完成:因為當 mutation 觸發(fā)的時候,回調(diào)函數(shù)還沒有被調(diào)用,devtools 不知道什么時候回調(diào)函數(shù)實際上被調(diào)用 —— 實質(zhì)上任何在回調(diào)函數(shù)中進行的狀態(tài)的改變都是不可追蹤的。
5.4 在組件中提交 Mutation
你可以在組件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 輔助函數(shù)將組件中的 methods 映射為 store.commit 調(diào)用(需要在根節(jié)點注入 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可以包含任意異步操作。
讓我們來注冊一個簡單的 action:
const store = new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++;
},
},
actions: {
increment(context) {
context.commit('increment');
},
},
});Action 函數(shù)接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調(diào)用 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。當我們在之后介紹到 Modules 時,你就知道 context 對象為什么不是 store 實例本身了。
實踐中,我們會經(jīng)常用到 ES2015 的參數(shù)解構(gòu)來簡化代碼(特別是我們需要調(diào)用 commit 很多次的時候):
actions: {
increment ({ commit, state, getters }) {
commit('increment')
}
}7、分發(fā) Action
Action 通過 store.dispatch 方法觸發(fā):
store.dispatch('increment');乍一眼看上去感覺多此一舉,我們直接分發(fā) mutation 豈不更方便?實際上并非如此,還記得 mutation 必須同步執(zhí)行這個限制么?Action 就不受約束!我們可以在 action 內(nèi)部執(zhí)行異步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}Actions 支持同樣的載荷方式和對象方式進行分發(fā):
// 以載荷形式分發(fā)
store.dispatch('incrementAsync', {
amount: 10,
});
// 以對象形式分發(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é)點注入 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 什么時候結(jié)束呢?更重要的是,我們?nèi)绾尾拍芙M合多個 action,以處理更加復雜的異步流程?
首先,你需要明白 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(() => {
// ...
});在另外一個 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())
}
}一個
store.dispatch在不同模塊中可以觸發(fā)多個action函數(shù)。在這種情況下,只有當所有觸發(fā)函數(shù)完成后,返回的Promise才會執(zhí)行。
8、嚴格模式
開啟嚴格模式,僅需在創(chuàng)建 store 的時候傳入 strict: true:
const store = new Vuex.Store({
// ...
strict: true,
});在嚴格模式下,無論何時發(fā)生了狀態(tài)變更且不是由 mutation 函數(shù)引起的,將會拋出錯誤(但是數(shù)據(jù)還是會改變)。這能保證所有的狀態(tài)變更都能被調(diào)試工具跟蹤到。

8.1 開發(fā)環(huán)境與發(fā)布環(huán)境
不要在發(fā)布環(huán)境下啟用嚴格模式! 嚴格模式會深度監(jiān)測狀態(tài)樹來檢測不合規(guī)的狀態(tài)變更 —— 請確保在發(fā)布環(huán)境下關(guān)閉嚴格模式,以避免性能損失。
類似于插件,我們可以讓構(gòu)建工具來處理這種情況:
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production',
});vue.js 服務端渲染介紹
大多數(shù)我們使用的 UI 框架如 vue 和 react,都是在客戶端進行渲染,也就是說每個用戶在加載進來我們所有的 html 文件和 js 文件之后,才開始渲染頁面的內(nèi)容。
但是這樣做會有兩個問題,一個是如果用戶網(wǎng)絡速度比較慢,如果我們渲染的內(nèi)容比較多的話,就會產(chǎn)生一個延遲,造成不好的用戶體驗。另一個是某些爬蟲,例如百度的搜索收錄的爬蟲,在爬取你的頁面時,獲取不到你的頁面的真實內(nèi)容,導致站點 SEO 權(quán)重變低。
所以很多需要 SEO 的頁面,都需要在服務端提前渲染好 html 的內(nèi)容,在用戶訪問時先返回給用戶內(nèi)容,這楊對用戶和爬蟲都非常友好。
我們可以通過直接在頁面上右擊查看網(wǎng)頁源代碼,來查看一個頁面是否有服務端渲染。
1、客戶端渲染和服務端渲染
客戶端渲染
<!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 標簽:

查看網(wǎng)頁源代碼:

2、服務端渲染
<!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>服務端渲染</title>
</head>
<body>
<div>你好</div>
</body>
</html>在 Network 的中 Preview 中有數(shù)據(jù),在 Response 中的有 DOM 標簽:

查看網(wǎng)頁源代碼:

客戶端路由
在控制臺中可以看到,切換路由的時候并沒有發(fā)起 ajax 請求。
3、服務端渲染實例
vue.js 的服務端渲染非常簡單,我們只需要在 node.js 中通過 vue-server-renderer 模塊,調(diào)用對應服務端渲染的渲染器對組件渲染即可,他就會生成組件對應的 html 內(nèi)容。渲染成功的 html 標簽,我們可以直接返回到客戶端作為初始請求 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();
// ! 服務端路由
app.get('*', function (req, res) {
const vm = new Vue({
data() {
return {
url: `服務端路由 ${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(`服務器啟動在 ${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>服務端渲染Demo</title>
</head>
<body>
<h1>這里是模板</h1>
<!--! 中間不能帶空格 -->
<!--vue-ssr-outlet-->
</body>
</html>- 執(zhí)行
index.js文件:node ./index.js
我們需要注意的一點是,在服務端渲染組件,我們使用不了 window、location 等瀏覽器環(huán)境中的對象,所以如果組件內(nèi)部使用了這種內(nèi)容會報錯。
同時,在服務端渲染時我們要注意,組件的生命周期也會只執(zhí)行 beforeCreate 和 created 這兩個,所以在此聲明周期里面不能使用 window,但是可以在其他聲明周期比如 mounted 中使用。還有渲染的數(shù)據(jù),對于服務端渲染的組件來說,我們不應該發(fā)請求獲取組件數(shù)據(jù),而是應該直接渲染時使用數(shù)據(jù)進行渲染。
路由也是如此,在 vue 客戶端使用路由的時候,我們也需要在服務端對路由進行匹配,從而得知具體需要渲染的組件是哪個。
3、同構(gòu) - 客戶端渲染和服務端渲染
參考文檔(
Vue文檔中 -Vue服務端渲染):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
/*
服務端 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第一種
/*
服務端渲染 - 入口
*/
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?)
返回目標位置或是當前路由匹配的組件數(shù)組 (是數(shù)組的定義/構(gòu)造類,不是實例)。通常在服務端渲染的數(shù)據(jù)預加載時使用。
*/
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(`服務器啟動在 ${PORT} 端口`);
});
/*
此時可以執(zhí)行 yarn build-server 編譯 entry-server 文件,生成 server.bundle.js
執(zhí)行 node ./server.bundle.js 查看服務端路由的結(jié)果
*/./src/entry-server.js第二種
/*
服務端渲染 - 入口
*/
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?)
返回目標位置或是當前路由匹配的組件數(shù)組 (是數(shù)組的定義/構(gòu)造類,不是實例)。通常在服務端渲染的數(shù)據(jù)預加載時使用。
*/
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(`服務器啟動在 ${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 編譯服務端;
執(zhí)行 node ./server.bundle.js 啟動服務器,打開瀏覽器輸入網(wǎng)址 http://localhost:8080/;
到此這篇關(guān)于Vue.js 狀態(tài)管理及 SSR解析的文章就介紹到這了,更多相關(guān)Vue.js SSR內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue 本地環(huán)境跨域請求proxyTable的方法
今天小編就為大家分享一篇vue 本地環(huán)境跨域請求proxyTable的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue router 跳轉(zhuǎn)時打開新頁面的示例方法
這篇文章主要介紹了vue router 跳轉(zhuǎn)時打開新頁面的示例方法,本文通過示例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-07-07
淺談vue中computed屬性對data屬性賦值為undefined的原因
本文主要介紹了淺談vue中computed屬性對data屬性賦值為undefined的原因,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
vue3+vue-cli4中使用svg的方式詳解(親測可用)
最近在做個vue的項目,從各種github上的開源庫上借鑒開發(fā)方法,給大家分享下,這篇文章主要給大家介紹了關(guān)于vue3+vue-cli4中使用svg的相關(guān)資料,需要的朋友可以參考下2022-08-08

