欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue.js?狀態(tài)管理及?SSR解析

 更新時(shí)間:2022年09月12日 10:03:20   作者:沵算what  
這篇文章主要介紹了Vue.js狀態(tài)管理及SSR解析,在vue.js中,我們主要說的狀態(tài)管理庫就是vuex,當(dāng)然,只要你能實(shí)現(xiàn)有條理的組織數(shù)據(jù),那么它都可以認(rèn)為是一種狀態(tài)管理庫

前端狀態(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)頁源代碼:

g

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ù)端渲染組件,我們使用不了 windowlocation 等瀏覽器環(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)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue 中 onclick和@click區(qū)別解析

    Vue 中 onclick和@click區(qū)別解析

    這篇文章主要介紹了Vue 中 onclick和@click區(qū)別,簡單點(diǎn)說就是onclick 只能觸發(fā) js的原生方法,不能觸發(fā)vue的封裝方法,@click 只能觸發(fā)vue的封裝方法,不能觸發(fā)js的原生方法,需要的朋友可以參考下
    2024-02-02
  • Vue.js 60分鐘輕松入門

    Vue.js 60分鐘輕松入門

    Vue.js提供了簡潔、易于理解的API,幫助大家快速靈活掌握Vue.js。這篇文章主要介紹了如何在60分鐘內(nèi)輕松學(xué)習(xí)Vue.js,感興趣的小伙伴們可以參考一下
    2016-11-11
  • vue.js 底部導(dǎo)航欄 一級(jí)路由顯示 子路由不顯示的解決方法

    vue.js 底部導(dǎo)航欄 一級(jí)路由顯示 子路由不顯示的解決方法

    下面小編就為大家分享一篇vue.js 底部導(dǎo)航欄 一級(jí)路由顯示 子路由不顯示的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • Vue集成three.js并加載glb、gltf、FBX、json模型的場景分析

    Vue集成three.js并加載glb、gltf、FBX、json模型的場景分析

    這篇文章主要介紹了Vue集成three.js,并加載glb、gltf、FBX、json模型,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • vue axios 給生產(chǎn)環(huán)境和發(fā)布環(huán)境配置不同的接口地址(推薦)

    vue axios 給生產(chǎn)環(huán)境和發(fā)布環(huán)境配置不同的接口地址(推薦)

    這篇文章主要介紹了vue axios 給生產(chǎn)環(huán)境和發(fā)布環(huán)境配置不同的接口地址,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2018-05-05
  • vue如何在store倉庫中使用路由

    vue如何在store倉庫中使用路由

    這篇文章主要介紹了vue如何在store倉庫中使用路由,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Vue.js中兄弟組件之間互相傳值實(shí)例

    Vue.js中兄弟組件之間互相傳值實(shí)例

    本篇文章主要介紹了Vue.js中兄弟組件之間互相傳值實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • Vue使用babel-polyfill兼容IE解決白屏及語法報(bào)錯(cuò)問題

    Vue使用babel-polyfill兼容IE解決白屏及語法報(bào)錯(cuò)問題

    這篇文章主要介紹了Vue使用babel-polyfill兼容IE解決白屏及語法報(bào)錯(cuò)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 深入理解vue Render函數(shù)

    深入理解vue Render函數(shù)

    本篇文章主要介紹了深入理解vue Render函數(shù),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • Element UI 自定義正則表達(dá)式驗(yàn)證方法

    Element UI 自定義正則表達(dá)式驗(yàn)證方法

    今天小編就為大家分享一篇Element UI 自定義正則表達(dá)式驗(yàn)證方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09

最新評(píng)論