作者:滴滴公共前端團隊
前言:
最早我們在設計《Vue.js權威指南》這本書的時候也一直思考要不要加入 Vuex 相關的內容,也有很多同學抱怨說我們沒有加入這個章節。
其實整體我們應用的還是比較早,也在 1.0 和 2.* 都踩了一些坑,但是也不期望大家在任何複雜不復雜的場景裡面濫用 Vuex。
後面我們在 vue 2.0 全家桶原始碼分享系列裡面也分享了一篇《Vuex 2.0 原始碼分析》,沒有看過的同學可以在文末連結檢視
正文:
Vuex 作為中大型 Vue 應用中的“御用”集中資料管理工具,在滴滴很早就得到了廣泛使用。本文旨在以儘可能簡潔的文字向讀者展示:如何在一個頗具規模的 Vue 應用中組織和管理 Vuex 的程式碼。
注:雖然目前 Vuex 的最新版本已經來到 2.x。2.x 在1.0 的基礎上進行了一些優化,提升了命名的語義化以及 增強了模組的可移植性和可組合性,但基本思想和架構並沒有改變。
本文基於 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”或掃描下面的二維碼