Vuex 實戰:如何在大規模 Vue 應用中組織 Vuex 代碼 | 掘金技術徵文

做者:滴滴公共前端團隊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 中存儲了 editorpage 兩個對象,用於存儲不一樣模塊的狀態。須要說明的是,這裏徹底可使用模塊機制將其拆開,在 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」或掃描下面的二維碼

相關文章
相關標籤/搜索