做者:滴滴公共前端團隊javascript
前言:前端
最先咱們在設計《Vue.js權威指南》這本書的時候也一直思考要不要加入 Vuex 相關的內容,也有不少同窗抱怨說咱們沒有加入這個章節。vue
其實總體咱們應用的仍是比較早,也在 1.0 和 2.* 都踩了一些坑,可是也不指望你們在任何複雜不復雜的場景裏面濫用 Vuex。java
後面咱們在 vue 2.0 全家桶源碼分享系列裏面也分享了一篇《Vuex 2.0 源碼分析》,沒有看過的同窗能夠在文末連接查看git
正文:github
Vuex 做爲中大型 Vue 應用中的「御用」集中數據管理工具,在滴滴很早就獲得了普遍使用。本文旨在以儘量簡潔的文字向讀者展現:如何在一個頗具規模的 Vue 應用中組織和管理 Vuex 的代碼。vuex
注:雖然目前 Vuex 的最新版本已經來到 2.x。2.x 在1.0 的基礎上進行了一些優化,提高了命名的語義化以及 加強了模塊的可移植性和可組合性,但基本思想和架構並無改變。shell
本文基於 Vuex 1.0 版本,讀者大可沒必要擔憂出現相似 Angular 1.x 升級到 2.x 式的斷崖式更新。微信
首先,介紹一下項目的背景:
一個採用 Vue.js 編寫的富交互的 H5 編輯器,因爲各個組件中的數據交互繁多,頁面的生成也極度依賴存儲的狀態,使用 Vuex 進行管理便勢在必行。
項目引入 Vuex 的方式以下:架構
import App from 'components/home/App'
import store from 'vuex/editor/store'
// 在 Vue 實例的初始化中聲明 store。
new Vue({
el: 'body',
components: {
App
},
store
})複製代碼
在根實例中註冊 store 選項,這樣該 store 實例會注入到根組件下的全部子組件中,方便後面咱們在每一個子組件中調用 store 中 state 裏存儲的數據。
而後看一下 vuex 文件夾下的目錄,後面咱們會逐個分析每一個文件的做用:
└── editor
├── mutation-types.js
├── actions
│ └── index.js
├── mutations
│ └── index.js
├── plugins
│ └── index.js
├── state
│ └── index.js
└── store
└── index.js複製代碼
建立 store 對象的代碼放在 vuex/editor/store/index.js 中,以下所示:
// vuex/editor/store/index.js
import Vuex from 'vuex'
import state from 'vuex/editor/state'
import mutations from 'vuex/editor/mutations'
import { actionLogPlugin } from 'vuex/editor/plugins'
const store = new Vuex.Store({
state,
mutations,
plugins: [actionLogPlugin]
})
export default store複製代碼
這裏又聲明瞭 state 和 mutations 對象,以及聲明瞭使用到的 plugins。plugins 後面再說,先看 state 和 mutations,相信各位讀者已經對 Vuex 中各個部件的做用已經瞭如指掌,可是爲防遺忘,仍是貼一下這張圖吧:
state 是用於存儲各類狀態的核心倉庫,讓咱們一瞥 vuex/editor/state/index.js 中的內容:
// 編輯器相關狀態
const editor = {
...
}
// 頁面相關狀態
let page = {
...
}
const state = {
editor,
page
}
export default state複製代碼
state 中存儲了 editor 和 page 兩個對象,用於存儲不一樣模塊的狀態。須要說明的是,這裏徹底可使用模塊機制將其拆開,在 editor.js 裏存儲編輯器相關的 state 和 mutations,在 page.js 中存儲頁面相關的 state 和 mutations,以使結構更加清晰。不過這裏沒有使用模塊機制,因爲模塊數量並很少,也是徹底能夠接受的。
這些 state 須要反映到組件中。
跳過官方文檔中對爲什麼不使用計算屬性的解釋,咱們直接來看最佳實踐:在子組件中經過 vuex.getters
來獲取該組件須要用到的全部狀態:
// src/components/h5/Navbar.vue
...
export default {
data () {
return {
...
}
},
methods: {
...
},
vuex: {
actions: {
...
},
getters: {
editor(state) {
return state.editor
},
page(state) {
return state.page
},
...
}
}
}複製代碼
在 vuex.getters
對象中,每一個屬性對應一個 getter 函數,該函數僅接收 store 中 state,也就是總的狀態樹做爲惟一參數,而後返回 state 中須要的狀態,而後在組件中就能夠以 this.editor
的方式直接調用,相似計算屬性。
再看一下 vuex/editor/mutations/index.js 中的內容:
import * as types from '../mutation-types'
const mutations = {
[types.CHANGE_LAYER_ZINDEX] (state, dir, index) {
...
},
[types.DEL_LAYER] (state, index) {
...
},
[types.REMOVE_FROM_ARR] (state, arr, itemToRemove) {
...
},
[types.ADD_TO_ARR] (state, arr, itemToAdd) {
...
},
[types.DEL_SCENE] (state, index) {
...
},
...
}
export default mutations複製代碼
具體業務邏輯這裏不展開,mutations 中主要就是定義各類對 state 的狀態修改。每一個 mutation 函數接收第一個參數爲 state 對象,其他參數則爲一路從組件中觸發 action 時傳過來的 payload。全部的 mutation 函數必須爲同步執行,不然沒法追蹤狀態的改動。
注意到,這裏引入了 mutation-types.js。該文件主要做用爲放置全部的命名 Mutations 的常量,方便合做開發人員釐清整個 app 包含的 mutations。在採用模塊機制時,能夠在每一個模塊內只引入相關的 mutations,也能夠像本項目同樣使用 import * as types
簡單粗暴地引入所有。
mutation-types.js 中內容大體以下:
export const CHANGE_LAYER_ZINDEX = 'CHANGE_LAYER_ZINDEX'
export const DEL_LAYER = 'DEL_LAYER'複製代碼
而後咱們來到 actions,照例先看一下 vuex/editor/actions/index.js 中的內容:
import * as types from '../mutation-types'
export function delLayer( { dispatch }, index) {
dispatch(types.DEL_LAYER, index)
}
export function delScene( { dispatch }, index) {
dispatch(types.DEL_SCENE, index)
}
export function removeFromArr( { dispatch }, arr, itemToRemove) {
dispatch(types.REMOVE_FROM_ARR, arr, itemToRemove)
}
export function addToArr( { dispatch }, arr, itemToAdd) {
dispatch(types.ADD_TO_ARR, arr, itemToAdd)
}複製代碼
actions 的主要工做就是 dispatch (中文譯爲分發)mutations。初入門的同窗可能以爲這是畫蛇添足,actions 這一步看起來徹底能夠省略。
事實上,actions 的出現是爲了彌補 mutations 沒法實現異步操做的缺陷。全部的異步操做均可以放在 actions 中,好比若是想在調用 delScene 函數 5 秒後再分發 mutations,能夠寫成這樣:
function delScene ({ dispatch }, index) {
setTimeout(() => {
dispatch(types.DEL_SCENE, index)
}, 5000)
}複製代碼
觸發 mutations 的代碼不會在組件中出現,但 actions 會出如今每一個須要它的組件中,其也是鏈接組件和 mutations 的橋樑(額,另外一條橋樑是 state,見上面那張經典老圖)。在子組件中引入 actions 的方式相似 state,也是註冊在 vuex 選項下:
// src/components/h5/Navbar.vue
...
import {
undoAction,
redoAction,
togglePreviewStatus,
...
} from 'vuex/editor/actions'
export default {
data () {
return {
...
}
},
methods: {
...
},
vuex: {
actions: {
undoAction,
redoAction,
togglePreviewStatus,
...
},
getters: {
...
}
}
}複製代碼
這樣,組件中能夠直接調用各個 actions,好比 this.togglePreviewStatus(status)
,等價於this.togglePreviewStatus( this.$store, status)
(還記得咱們在 actions 中定義的各個函數的第一個參數是 store 嗎?)。這是最基本的使用 actions 的方式,在此基礎上你還能夠玩出別的花樣來,好比給 actions 取別名、定義內聯 actions、綁定全部 actions 等,具體用法參見官方文檔。
回過頭去看 vuex 文件夾下的目錄結構,發現還有一個 plugins 咱們沒有介紹。老規矩,先看一下 vuex/editor/plugins/index.js 中的內容:
...
export function actionLogPlugin(store) {
store.subscribe((mutation, state) => {
// 每次 mutation 以後調用
// mutation 的格式爲 { type, payload }
...
})
}複製代碼
核心部分在於採用 store.subscribe
註冊了一個函數。
該函數會在每次 mutation 以後被調用。這裏 actionLogPlugin 函數完成的是記錄每次 mutation 操做,實現撤銷重作功能。具體實現邏輯此處不做贅述。
後續咱們也會深刻地給你們分享 vuex 應用相關的內容
附:
Vuex 2.0 源碼分析知乎地址:zhuanlan.zhihu.com/p/23921964
「掘金技術徵文」活動:gold.xitu.io/post/58522d…
歡迎關注DDFE
GITHUB:github.com/DDFE
微信公衆號:微信搜索公衆號「DDFE」或掃描下面的二維碼