爲管理複雜組件狀態困擾?試試 vue 簡單狀態管理 Store 模式

在 vue 中,通訊有幾種形式:html

  • 父子組件 emit/on
  • vuex 中共享 state
  • 跨組件 EventBus

文檔中的提到的 Store 模式卻鮮有人去使用討論。筆者在研究 ElementUI的Table組件的代碼組織方式,以及在本身 ElementUI 表單編輯項目中實踐以後以爲其在複雜組件組織上很是有用,是一個被忽視的組件通訊方法。vue

簡單狀態管理 store 模式

官方示例代碼:git

var store = {
  debug: true,
  state: {
    message: 'Hello!'
  },
  setMessageAction(newValue) {
    if (this.debug) console.log('setMessageAction triggered with', newValue)
    this.state.message = newValue
  },
  clearMessageAction() {
    if (this.debug) console.log('clearMessageAction triggered')
    this.state.message = ''
  }
}
複製代碼

官方介紹:全部 store 中 state 的改變,都放置在 store 自身的 action 中去管理。這種集中式狀態管理可以被更容易地理解哪一種類型的 mutation 將會發生,以及它們是如何被觸發。當錯誤出現時,咱們如今也會有一個 log 記錄 bug 以前發生了什麼。此外,每一個實例/組件仍然能夠擁有和管理本身的私有狀態github

數據流圖

官方版的介紹過於簡陋,不妨咱們更進一步,學習一下 ElementUI 的 Table 組件是如何用 Store 組織一個複雜組件的vuex

爲何須要 Store 模式

ElementUI 的 Table 組件,功能不少。該組件由父組件 Table.vue 和衆多子組件 layout-observer,table-body,table-column,table-footer,table-header,table-layout 組成。看 ElementUI 文檔就以爲 Table 組件複雜。數據結構

若是把子組件的事件都 emit 到父組件處理,那麼父組件得接收多少事件。而且子組件部分功能會影響父組件的佈局。而且 Table 的部分數據大多數子組件都須要,你要一個一個經過 Porp 傳入嗎?自頂向下的數據流動開發困難。不如把這些共享的數據放在一個地方,咱們天然很容易想到 Vuex,可是 ElementUI 庫引入 ElementUI 引入 Vuex,你以爲合適嗎,而且數據共享僅僅是在 Table 組件裏面,並非全局的數據,所以採用 Store 模式再好不過了。架構

ElementUI 模仿了 Vuex 的使用方式。有興趣的讀者能夠看一下 Table 組件中table-store.jsapp

模仿 Vuex 的一個好處就是我後期若是項目大了,能夠十分平滑的引入 Vuex,而且若是你熟悉 Vuex,使用 Store 模式沒有任何認知成本異步

實踐

筆者用 Store 模式改造了我以前的ElementUI 的表單在線編輯器,以前的主頁面由表單元素資源區,表單屬性編輯區,表單元素拖拽區,表單元素屬性編輯區,JSON表單生成區,代碼生成區。然而整個頁面就維護表單對象,表單元素列表,表單元素屬性這幾個值,然而這些值在多個子組件裏面都起了必定的做用,一開始沒有集中處理,致使數據會意外變化,不知道是那個組件引發的。後使用Store模式集中處理以後,代碼邏輯清楚不少編輯器

項目展現
這種方式其實就是把數據管理,數據更新的功能交給了 Store。若是你熟悉 Vuex 的話,應該很快能理解我在說什麼

經過定義和隔離狀態管理中的各類概念並經過強制規則維持視圖和狀態間的獨立性,咱們的代碼將會變得更結構化且易維護

數據流

聲明一個 Store 對象

const FormStore = function(form, initialState = {}) {
   // 將父組件的示例保存在Store裏面
  if (!form) {
    throw new Error('Form is required.')
  }
  this.form = form

  this.states = { ... }
  // initialState 裏面的值必須是 this.states聲明過的,這樣全部狀態的變化應該都在store裏面能夠查找,並由store控制
  for (let prop in initialState) {
    if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
      this.states[prop] = initialState[prop]
    }
  }
}
複製代碼

mutations

Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的   事件類型 (type)  和 一個   回調函數 (handler)。這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數

咱們這裏也模仿它,注意這裏咱們只放同步的代碼,異步代碼本身處理

FormStore.prototype.mutations = {
  setFormAttribute(states, formAttribute) {
    this.states = { ...states, formAttribute }
  },
  setFormItems(states, formItems) {
    this.states = { ...states, formItems }
  },
  setClickedIndex(states, clickedIndex) {
    this.states = { ...states, clickedIndex }
  },
  setFormItemToHandle(states, formItemToHandle) {
    this.states = { ...states, formItemToHandle }
  },
  setItemInFormItems(states, idx, formItem) {
    states.formItems.splice(idx, 1, formItem)
  },
  setFromItems(states, formItems) {
    this.states = { ...states, formItems }
  }
}
複製代碼

commit

複雜數據結構的父子組件的數據通訊用emitv-on事件流容易混亂,尤爲是對象嵌套對象的時候。採用 Store 模式,子組件和父組件之間有了 store 這個橋樑,經過 commit 來分發事件

在 commit 函數裏面,打上一個console.log,事件的變化所有掌握在你的手裏。就像使用 Vuex 同樣

// 定義
FormStore.prototype.commit = function(name, ...args) {
  const mutations = this.mutations
  console.log('emit', name)
  if (mutations[name]) {
    // states 做爲第一個參數
    mutations[name].apply(this, [this.states].concat(args))
  } else {
    throw new Error(`Action not found: ${name}`)
  }
}
// 分發事件
this.store.commit('setFormItemToHandle', val)
複製代碼

使用

在父組件的 data 裏面建立 store,而後把 store 傳入到各個子組件裏面去。代碼邏輯很是清楚

data() {
    const store = new FormStore(this);
    return {
      store
    };
},
computed: {
    form() {
      return this.store.states.formAttribute;
    }
},
methods: {
    genFormItem(val) {
        this.store.commit("setFormItemToHandle", val);
      }
    }
}
複製代碼

Store 模式 vs EventBus

Vuex 的優勢便是 Store 模式的優勢

  1. 易於調試與管理
  2. 和 EventBus 差很少的便捷,雖然作不到全局發事件,接受事件,可是若是有這種狀況的話,爲何不試試 Vuex 呢
  3. 可局部應用,職責專注

EventBus 在代碼量增多的狀況下:

  1. 代碼邏輯性極具降低,可閱讀性變低
  2. 對於每個 action 父組件都須要一個 on(或 dispatch)一個事件來處理
  3. 你將很難查找到每個事件是從哪裏觸發,滿篇都是業務邏輯

Store 模式 vs Vuex

有的時候,咱們可能不知道是否該使用 Vuex,雖然 Redux 的做者 Dan Abramov 的話這麼說:

Flux 架構就像眼鏡:您自會知道何時須要它

可是我可能只是輕微的近視,不帶眼鏡也能夠,可是看東西不太清楚,帶上眼鏡又感受有點累贅,這個時候就須要咱們的 Store 模式

Vuex 負責全局狀態的管理,Store 模式負責局部狀態的交流

Store 模式能夠在你寫一個大型組件的時候,單獨在該組件中使用,不用數據都放在 Vuex 裏面,做爲多個子組件和父組件通訊的橋樑使用。我司後臺擁有幾十個業務,每一個業務下面又會有細分,若是爲了寫組件方便,將其放在Vuex裏面,那麼Vuex將會何等的臃腫

多人開發的時候,每一個人負責的業務有單獨的 Store 也不會互相影響

你甚至可使用多個 Store 去組織你全部的代碼

總結

模仿 Vuex,咱們多了一種組織複雜組件或局部狀態管理的新思路,在你寫複雜的組件,又不想污染全局的 Vuex,又須要將狀態在多個組件中共享,則能夠考慮一下 Store 模式,和 Vuex 同樣方便,和 EventBus 同樣輕量

既然採用了模仿 Vuex 的方式,代碼風格就要貫徹到底,畢竟 Store 模式沒有強力的約束,不能像 ElementUI 同樣,代碼裏面還有直接修改 states 語句(逃

eg: this.store.states.treeData = this.getTableTreeData(value);

體驗我基於 Store 模式改造的 ElementUI 表單編輯器項目,記得點個小星星哦,查看項目地址

參考

相關文章
相關標籤/搜索