Vuex 實戰

作者:殷獻勇、蘇偉

審校:小春

滴滴前端公共團隊

前言:

最早我們在設計《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/Appnimport store from vuex/editor/storenn// 在 Vue 實例的初始化中聲明 store。nnew Vue({n el: body,n components: {n Appn },n storen})n

在根實例中註冊 store 選項,這樣該 store 實例會注入到根組件下的所有子組件中,方便後面我們在每個子組件中調用 store 中 state 里存儲的數據。

然後看一下 vuex 文件夾下的目錄,後面我們會逐個分析每個文件的作用:

└── editorn ├── mutation-types.jsn ├── actionsn │ └── index.jsn ├── mutationsn │ └── index.jsn ├── pluginsn │ └── index.jsn ├── staten │ └── index.jsn └── storen └── index.jsn

創建 store 對象的代碼放在 vuex/editor/store/index.js 中,如下所示:

// vuex/editor/store/index.jsnimport Vuex from vuexnimport state from vuex/editor/statenimport mutations from vuex/editor/mutationsnimport { actionLogPlugin } from vuex/editor/pluginsnnconst store = new Vuex.Store({n state,n mutations,n plugins: [actionLogPlugin]n})nnexport default storen

這裡又聲明了 state 和 mutations 對象,以及聲明了使用到的 plugins。plugins 後面再說,先看 state 和 mutations,相信各位讀者已經對 Vuex 中各個部件的作用已經了如指掌,但是為防遺忘,還是貼一下這張圖吧:

state 是用於存儲各種狀態的核心倉庫,讓我們一瞥 vuex/editor/state/index.js 中的內容:

// 編輯器相關狀態nconst editor = {n ...n}nn// 頁面相關狀態nlet page = {n ...n}nnconst state = {n editor,n pagen}nnexport default staten

state 中存儲了 editorpage 兩個對象,用於存儲不同模塊的狀態。需要說明的是,這裡完全可以使用模塊機制將其拆開,在 editor.js 里存儲編輯器相關的 state 和 mutations,在 page.js 中存儲頁面相關的 state 和 mutations,以使結構更加清晰。不過這裡沒有使用模塊機制,由於模塊數量並不多,也是完全可以接受的。

這些 state 需要反映到組件中。

跳過官方文檔中對為何不使用計算屬性的解釋,我們直接來看最佳實踐:在子組件中通過 vuex.getters 來獲取該組件需要用到的所有狀態:

// src/components/h5/Navbar.vuenn...nexport default {n data () {n return {n ...n }n },n methods: {n ...n },n vuex: {n actions: {n ...n },n getters: {n editor(state) {n return state.editorn },n page(state) {n return state.pagen },n ...n }n }n}n

在 vuex.getters 對象中,每個屬性對應一個 getter 函數,該函數僅接收 store 中 state,也就是總的狀態樹作為唯一參數,然後返回 state 中需要的狀態,然後在組件中就可以以 this.editor 的方式直接調用,類似計算屬性。

再看一下 vuex/editor/mutations/index.js 中的內容:

import * as types from ../mutation-typesnnconst mutations = {n [types.CHANGE_LAYER_ZINDEX] (state, dir, index) {n ...n },n [types.DEL_LAYER] (state, index) {n ...n },n [types.REMOVE_FROM_ARR] (state, arr, itemToRemove) {n ...n },n [types.ADD_TO_ARR] (state, arr, itemToAdd) {n ...n },n [types.DEL_SCENE] (state, index) {n ...n },n ...n}nnexport default mutationsn

具體業務邏輯這裡不展開,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_ZINDEXnexport const DEL_LAYER = DEL_LAYERn

然後我們來到 actions,照例先看一下 vuex/editor/actions/index.js 中的內容:

import * as types from ../mutation-typesnnexport function delLayer( { dispatch }, index) {n dispatch(types.DEL_LAYER, index)n}nnexport function delScene( { dispatch }, index) {n dispatch(types.DEL_SCENE, index)n}nnexport function removeFromArr( { dispatch }, arr, itemToRemove) {n dispatch(types.REMOVE_FROM_ARR, arr, itemToRemove)n}nnexport function addToArr( { dispatch }, arr, itemToAdd) {n dispatch(types.ADD_TO_ARR, arr, itemToAdd)n}n

actions 的主要工作就是 dispatch (中文譯為分發)mutations。初入門的同學可能覺得這是多此一舉,actions 這一步看起來完全可以省略。

事實上,actions 的出現是為了彌補 mutations 無法實現非同步操作的缺陷。所有的非同步操作都可以放在 actions 中,比如如果想在調用 delScene 函數 5 秒後再分發 mutations,可以寫成這樣:

function delScene ({ dispatch }, index) {n setTimeout(() => {n dispatch(types.DEL_SCENE, index)n }, 5000)n}n

觸發 mutations 的代碼不會在組件中出現,但 actions 會出現在每個需要它的組件中,其也是連接組件和 mutations 的橋樑(額,另一條橋樑是 state,見上面那張經典老圖)。在子組件中引入 actions 的方式類似 state,也是註冊在 vuex 選項下:

// src/components/h5/Navbar.vuen...nnimport { n undoAction, n redoAction,n togglePreviewStatus,n ...n} from vuex/editor/actionsnnexport default {n data () {n return {n ...n }n },n methods: {n ...n },n vuex: {n actions: {n undoAction,n redoAction,n togglePreviewStatus,n ...n },n getters: {n ...n }n }n}n

這樣,組件中可以直接調用各個 actions,比如 this.togglePreviewStatus(status),等價於this.togglePreviewStatus( this.$store, status)(還記得我們在 actions 中定義的各個函數的第一個參數是 store 嗎?)。這是最基本的使用 actions 的方式,在此基礎上你還可以玩出別的花樣來,比如給 actions 取別名、定義內聯 actions、綁定所有 actions 等,具體用法參見官方文檔。

回過頭去看 vuex 文件夾下的目錄結構,發現還有一個 plugins 我們沒有介紹。老規矩,先看一下 vuex/editor/plugins/index.js 中的內容:

...nexport function actionLogPlugin(store) {nn store.subscribe((mutation, state) => {nn // 每次 mutation 之後調用n // mutation 的格式為 { type, payload }n ...n })n}n

核心部分在於採用 store.subscribe 註冊了一個函數。

該函數會在每次 mutation 之後被調用。這裡 actionLogPlugin 函數完成的是記錄每次 mutation 操作,實現撤銷重做功能。具體實現邏輯此處不作贅述。

後續我們也會深入地給大家分享 vuex 應用相關的內容

附:

Vuex 2.0 源碼分析知乎地址:知乎專欄


推薦閱讀:

TAG:前端开发 | Vuejs | Vuex |