認識Vuex

學前準備


本文主要內(nèi)容來源于官網(wǎng)镶殷,為Vuex的基礎部分總結(jié)晦毙,部分案例也參考了互聯(lián)網(wǎng)上其他分享知識的大佬恋拷。本手記結(jié)合官網(wǎng)內(nèi)容也額外添加了自己的一些的理解准给,希望能給你帶來一些參考價值响巢,如果文章有理解不到位的地方描滔,還請各位多批評指正!以下是本文中可能用到的參考資料:
點擊進入vuex官方教程

點擊進入Vue CLI官方教程

點擊了解Vuex Action中的參數(shù)解構(gòu)為什么那么寫

點擊了解Action和Promise中Promise詳解

為什么使用Vuex


Vuex的官方解答:Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式踪古。它采用集中式存儲管理應用的所有組件的狀態(tài)含长,并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化。

在vue開發(fā)的過程中伏穆,我們經(jīng)常遇到一個狀態(tài)可能會在多個組件之間使用拘泞,比如用戶信息、頭像枕扫、昵稱等陪腌,我們修改了這些信息,那么整個應用中凡是使用了該信息的部分都應該更新烟瞧。想讓vue中所有組件都共享這些信息诗鸭,那么就需要將這些信息進行集中管理,這個時候我們就需要用到Vuex参滴。

通過Vue CLI生成項目模板


日常開發(fā)中只泼,我們大多都是用Vue CLI腳手架來生成一個vue項目,不太清楚腳手架怎么使用的可以移步Vue CLI官網(wǎng)自行查閱卵洗。在使用腳手架生成的項目時會讓我們選擇store,選擇后會在頁面中給我們生成store文件夾请唱,自帶初始化倉庫的index.js,這就是最初的store过蹂,里面結(jié)構(gòu)如下:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {}
})

在模板中我們可以看到state十绑、mutationsactions酷勺、modules這幾個對象本橙,而Vuex中幾乎所有的操作都是圍繞它們展開的,接下來我們就來逐一認識它們脆诉。

State


Vuex的核心倉庫是store甚亭,這個store實例會被注入到所有的子組件里面贷币,而所有子組件中公用的數(shù)據(jù)信息和狀態(tài)就都存儲在State里面。我們來看一個簡單的小栗子亏狰。

export default new Vuex.Store({
  state: {
    count: 102
  }
})

vuex 中所有的狀態(tài)管理都會定義在 state 中役纹,我們在 state中定義了 count ,那么我們就得到了一個集中管理的狀態(tài) count 暇唾,所有的組件都可以直接訪問到這個 count促脉。

<template>
  <div class="index">
    {{count}}
  </div>
</template>

<script>
  export default {
    computed: {
      count () {
        return this.$store.state.count
      }
    }
  }
</script>

因為根實例中注冊 store 選項,該 store 實例會注入到根組件下的所有子組件中策州,且子組件能通過 this.$store 訪問到瘸味。通過計算屬性,我們就可以在模板里面使用模板語法來調(diào)用count了够挂,當然我們也可以在模板中的直接寫入旁仿。有小伙伴會好奇為什么不能寫在data里或其它地方嗎,state寫在計算屬性中是因為模板文件需要響應倉庫中state的變化孽糖,而state變化之后如果寫在data中就不能及時響應渲染到頁面中枯冈,而computed身為計算屬性會監(jiān)聽函數(shù)下面值得變化然后進行重新渲染。

<template>
  <div class="index">
    {{this.$store.state.count}}
  </div>
</template>
  • mapState

如果我們在store中存儲了很多個狀態(tài)梭姓,而在當前模板頁面中又要讀取store里面大量狀態(tài),那么我們在當前頁面采用computed中來return store里面的狀態(tài)這種寫法就會變得很冗余嫩码。Vuex中針對state給我們提供了它的語法糖mapState誉尖,我們可以通過mapState()方法直接獲取state中存儲的狀態(tài),如下栗子:

<template>
  <div class="index">
    {{count}}
    {{total}}
  </div>
</template>

<script>
  // 使用該語法糖我們需提前引入
  import { mapState } from 'vuex'
  export default {
    computed: {
      total () {
        return 10 + this.count
      },
      ...mapState(['count']) // 如需引入多個狀態(tài)可寫成...mapState(['count', 'name', 'six'])
    }
  }
</script>
// 102
// 112

Getter


有時候我們需要從 store 中的 state 中派生出一些狀態(tài)铸题,例如如果state中有一個數(shù)組铡恕,我們要將數(shù)組中所有大于10的數(shù)字挑選出來然后在給組件使用,栗子如下:

<template>
  <div class="index">
    {{modiyArr}}
  </div>
</template>

<script>
  export default {
    computed: {
      modiyArr () {
        return this.$store.state.list.filter(item => item > 10)
      }
    }
  }
</script>
// [20, 40, 50, 66, 77, 88]

我們在computed中完成了代碼邏輯丢间,同時也暴露出了一些問題探熔,就是如果我們需要在多個組件中都完成這個數(shù)組的過濾,就需要在各個組件處都復制這段代碼或者將其抽取到一個共享函數(shù)然后在多處導入它烘挫,無論哪一種方式都不是很理想诀艰。這個時候我們就可以使用vuex中的getter

Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)饮六。就像計算屬性一樣其垄,getter 的返回值會根據(jù)它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算卤橄。

<!-- 使用Getter的第一種用法 -->
<template>
  <div class="index">
    {{this.$store.getters.modiyArr}}
  </div>
</template>

<!-- 使用Getter的第二種用法 -->
<template>
  <div class="index">
    {{modiyArr}}
    {{getLength}}
  </div>
</template>

<script>
  export default {
    computed: {
      modiyArr () {
        return this.$store.getters.modiyArr
      },
      getLength () {
        return this.$store.getters.getLength
      }
    }
  }
</script>

// stoer.js
export default new Vuex.Store({
  state: {
    list: [1, 3, 4, 10, 20, 40, 50, 66, 77, 88]
  },
  getters: {
    // getters中的函數(shù)接收兩個參數(shù)(state, getters)
    modiyArr(state) {
      return state.list.filter(item => item > 10)
    },
    getLength(state, getters) {
        return getters.modiyArr.length // 可以通過getters直接調(diào)用它的其它方法
    }
  }
})
// [20, 40, 50, 66, 77, 88] 

上面的栗子基本都是通過屬性來訪問的绿满,使用getters也可以支持通過方法來訪問,我們來看一個官網(wǎng)的小栗子

// 篩選state中的額todos窟扑,將todos數(shù)組中id為2的對象找出來
// store.js
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => (id) => {
        return state.todos.find(todo  => todo.id === id)
    }
  }
})

<template>
  <div class="index">
    {{checkDoneTodos}}
  </div>
</template>

<script>
  import { mapState, mapGetter } from 'vuex'
  export default {
    computed: {
      checkDoneTodos () {
          return this.$store.getters.doneTodos(2)
      }
    }
  }
</script>
// { id: 2, text: '...', done: false }

注意喇颁,getter 在通過屬性訪問時是作為 Vue 的響應式系統(tǒng)的一部分緩存其中的漏健。getter在通過方法訪問時,每次都會去進行調(diào)用橘霎,而不會緩存結(jié)果蔫浆。

  • mapGetters

mapGetters也是Vuex幫我們封裝好的語法糖,具體用法其實和mapState差不多茎毁。

import { mapGetters } from 'vuex'
export default {
  // ...
  computed: {
  // 使用對象展開運算符將 getter 混入 computed 對象中
    ...mapGetters([
      'modiyArr',
      'getLength',
      // ...
    ])
  }
}

當然...mapGetters本身也是支持對象類寫法的

...mapGetters({
  // 把 `this.editArr` 映射為 `this.$store.getters.modiyArr`
  editArr: 'modiyArr',
  getLength: 'getLength'
})

Mutation


如果我們需要對store.js中的狀態(tài)進行修改克懊,我們是不能在組件里面直接修改的,因為組件里面直接修改倉庫是不會進行全局響應的七蜘,這就違背了我們使用倉庫的初衷谭溉。唯一的方法就是通過提交mutation來進行修改,這樣倉庫中與之對應的狀態(tài)也會被修改進而影響全部組件的數(shù)據(jù)橡卤。Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調(diào)函數(shù) (handler)扮念。這個回調(diào)函數(shù)就是我們實際進行狀態(tài)更改的地方,并且它會接受 state 作為第一個參數(shù):

<template>
  <div class="index">
    <button @click="add">增加</button>
    {{num}}
    <button @click="reduce">減少</button>
  </div>
</template>

<script>
  import { mapState, mapGetter } from 'vuex'
  export default {
    computed: {
      ...mapState(['num'])
    },
    methods: {
      add() {
        this.$store.commit('add')
      },
      reduce () {
        this.$store.commit('reduce')
      }
    }
  }
</script>

export default new Vuex.Store({
  state: {
    num: 10
  },
  mutations: {
    add (state) {
      // 變更狀態(tài)
      state.num++
    },
    reduce (state) {
      state.num--
    }
  }
})

上面栗子中碧库,我們在state中定義了num柜与,然后通過mutations添加add方法和reduce方法去對num進行修改,在模板文件中我們定義了click事件來改變num的值嵌灰,模板文件中的事件去響應store中的mutations主要是通過commit()來實現(xiàn)的弄匕。

  • 提交載荷(Payload)

你可以向store.commit傳入額外的參數(shù),即 mutation 的 載荷(payload)沽瞭,其實換種方式理解可能更容易迁匠,就是模板文件中的commit可以添加額外的參數(shù),mutations中接收的時候接收一個形參payload驹溃,就代表模板文件中你傳進來的參數(shù)城丧。vuex官網(wǎng)更建議我們payload應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀豌鹤,我們看下面的小栗子:

export default {
  methods: {
    add() {
      //this.$store.commit('add', 100)直接傳遞額外參數(shù)亡哄,不建議,更建議下一種對象的寫法
      this.$store.commit('add', {
        addNum: 100
      })
    }
  }
}

export default new Vuex.Store({
  state: {
    num: 10
  },
  mutations: {
    add (state, payload) {
      state.num+=payload.addNum
    },
    reduce (state) {
      state.num--
    }
  }
})

也可以將所有參數(shù)寫到一個對象里面布疙,那么type就對應mutations中要執(zhí)行的函數(shù)名

export default {
  methods: {
    add() {
      this.$store.commit({
        type: 'add',
        addNum: 100
      })
    }
  }
}
  • Mutation 需遵守 Vue 的響應規(guī)則

這個通俗點說就是你在開發(fā)過程中需要向state里面添加額外數(shù)據(jù)時蚊惯,需要遵循響應準則。官方文檔說既然 Vuexstore 中的狀態(tài)是響應式的灵临,那么當我們變更狀態(tài)時拣挪,監(jiān)視狀態(tài)的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用Vue一樣遵守一些注意事項: 1.最好提前在你的 store 中初始化好所有所需屬性俱诸。 2.當需要在對象上添加新屬性時菠劝,你應該使用Vue.set(obj, 'newProp', 123),或者以新對象替換老對象。例如赶诊,利用對象展開運算符我們可以這樣寫:

state.obj = { ...state.obj, newProp: 123 }

文字很枯燥笼平,我們還是來看個小栗子

<template>
  <div class="index">
    <button @click="add">增加</button>
    {{num}}
    // add方法執(zhí)行之后頁面會立即響應這個新的狀態(tài)
    <div class="new-num">{{this.$store.state.newNum || 0}}</div> 
  </div>
</template>

mutations: {
  add (state, payload) {
    state.num+=payload.addNum
    Vue.set(state, 'newNum', 1000) // 像倉庫中的state新增加一個newNum狀態(tài),初始值為1000
    // state= {...state, newNum: 2000} 這個方法不管用了舔痪,用下面的replaceState()方法
    this.replaceState({...state, newNum: 2000})
  }
}
  • Mutation 必須是同步函數(shù)

下面這種寫法必須避免(直接官方例子加持):

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

我們在模板文件中通過事件去操作mutations時寓调,如果mutations中為異步函數(shù),那么當 mutation 觸發(fā)的時候锄码,回調(diào)函數(shù)還沒有被調(diào)用夺英,因為我們不知道什么時候回調(diào)函數(shù)實際上被調(diào)用——實質(zhì)上任何在回調(diào)函數(shù)中進行的狀態(tài)的改變都是不可追蹤的。

  • mapMutations

其實這幾個語法糖的使用方法都差不多滋捶,這里就直接上官方栗子了痛悯。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
    })
  }
}

Action


Action類似于mutation,不同在于:

  • Action 提交的是 mutation重窟,而不是直接變更狀態(tài)载萌。
  • Action 可以包含任意異步操作。前面說過mutation只能包含同步事務巡扇,所以在處理異步事務就需要Action扭仁,通過Action控制了異步這一過程,之后再去調(diào)用mutation里面的方法來改變狀態(tài)厅翔。

先看官方的一個小栗子來認識它的基礎語法:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Action 函數(shù)接受一個與 store 實例具有相同方法和屬性的 context 對象乖坠,因此你可以調(diào)用 context.commit 提交一個 mutation,或者通過 context.statecontext.getters 來獲取 stategetters刀闷。那為什么這里的參數(shù)不能直接是store本身呢熊泵,這就和Modules有關(guān)了,了解Modules之后就會發(fā)現(xiàn)store總倉庫下面可能會有很多個不同的模塊倉庫涩赢,而每一個不同的模塊倉庫都有自己的Action戈次、state轩勘、mutation筒扒、getter,如果使用store那么store.state拿到的就不會是當前模塊的state绊寻,而context可以理解為當前模塊的store花墩,這樣就不會引起沖突。

實踐中澄步,我們會經(jīng)常用到 ES2015 的 參數(shù)解構(gòu) 來簡化代碼(特別是我們需要調(diào)用 commit 很多次的時候):

actions: {
  increment ({ commit }) { // 這里就是將context展開式寫法 { commit } = context.commit
    commit('increment')
  }
}
  • 分發(fā) Action

Action 通過 store.dispatch 方法觸發(fā):

store.dispatch('increment')

乍一眼看上去感覺多此一舉冰蘑,我們直接分發(fā) mutation 豈不更方便?實際上并非如此村缸,還記得 mutation 必須同步執(zhí)行這個限制么祠肥?Action 就不受約束!我們可以在 Action 內(nèi)部執(zhí)行異步操作梯皿。

上面這一塊內(nèi)容基本來源于官網(wǎng)仇箱,感覺官網(wǎng)對于 Action 函數(shù)的傳參县恕,分發(fā)等基本用法都說的比較詳細,在這里我們只要記住一點Action用來執(zhí)行異步操作倉庫狀態(tài)的情況剂桥。我們還是來做個小栗子更直觀了解吧

<template>
  <div class="index">
    <button @click="add">增加</button>
    {{num}}
  </div>
</template>

<script>
  import { mapState, mapGetter } from 'vuex'
  export default {
    computed: {
      ...mapState(['num'])
    },
    methods: {
      add() {
        this.$store.dispatch('delayAdd')
      }
    }
  }
</script>

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 10
  },
  mutations: {
    add (state, addNum) {
      state.num += addNum
    }
  },
  actions: {
    delayAdd (context) {
      setTimeout(() => {
        context.commit('add', 100)
      }, 1000)
    }
  }
})

上面是一個Action中最基礎的用法忠烛,因為Mutation中不能接受異步操作,所以我們先將要修改的東西放在Mutation中权逗,然后在Action中等待1s之后去操作Mutation進行提交來更新數(shù)據(jù)美尸。至于分發(fā)Action是我們在模板中通過 this.$store.dispatch('delayAdd')來執(zhí)行,這樣我們就完成了一個基礎的Action閉環(huán)demo斟薇。

  • Actions 支持同樣的載荷方式和對象方式進行分發(fā):
// 以載荷形式分發(fā)
store.dispatch('delayAdd', {
  addNum: 100
})

actions: {
  delayAdd (context, payload) {
    setTimeout(() => {
      context.commit('add', payload.addNum)
    }, 1000)
  }
}

// 以對象形式分發(fā)
store.dispatch({
  type: 'delayAdd',
  addNum: 100
})

這里copy一個官方購物車示例师坎,涉及到調(diào)用異步 API 和分發(fā)多重 mutation的栗子來給大家看看,主要是過一遍有個印象

actions: {
 checkout ({ commit, state }, products) {
   // 把當前購物車的物品備份起來
   const savedCartItems = [...state.cart.added]
   // 發(fā)出結(jié)賬請求奔垦,然后樂觀地清空購物車
   commit(types.CHECKOUT_REQUEST)
   // 購物 API 接受一個成功回調(diào)和一個失敗回調(diào)
   shop.buyProducts(
     products,
     // 成功操作
     () => commit(types.CHECKOUT_SUCCESS),
     // 失敗操作
     () => commit(types.CHECKOUT_FAILURE, savedCartItems)
   )
 }
}

注意我們正在進行一系列的異步操作屹耐,并且通過提交 mutation 來記錄 action 產(chǎn)生的副作用(即狀態(tài)變更)。

  • mapActions

Action也有類似的語法糖椿猎,這里就不多贅述了惶岭,其實用法都是差不多的,直接官方栗子

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`

      // `mapActions` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.dispatch('increment')`
    })
  }
}
  • Action結(jié)合Promise

Action 通常是異步的犯眠,那么如何知道 Action 什么時候結(jié)束呢按灶?我們這里假設一種情況,模板上有一個數(shù)據(jù)筐咧,同時有這個數(shù)據(jù)當前的狀態(tài)說明鸯旁,我們?nèi)绻ㄟ^Action去操作這個數(shù)據(jù),那么數(shù)據(jù)改變之后我們希望這個數(shù)據(jù)的當前狀態(tài)說明就馬上更新量蕊。舉個小栗子

<template>
  <div class="index">
    <div class="index-name">姓名:{{userinfo.name}}</div>
    <div class="index-six">性別:{{userinfo.six}}</div>
    <div class="number-hint">狀態(tài):{{defalutText}}</div>
    <div class="click-btn" @click.stop="handleClickChange">
      <button>{{buttonText}}</button>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetter, mapAction } from "vuex";
export default {
  data () {
    return {
      defalutText: "信息暫未修改" ,
      buttonText: "點擊改變信息",
      time: 5
    }
  },
  computed: {
    ...mapState(["userinfo"]),
  },
  methods: {
    handleClickChange () {
      this.changeButton()
      this.$store.dispatch({
        type: 'changeInfo',
        name: '李四',
        six: '女',
        time: this.time
      }).then(res => {
        console.log(res) // 執(zhí)行完畢铺罢,可以開始改變狀態(tài)啦
        this.defalutText = '信息更新成功'
      })
    },
    changeButton () {
      this.buttonText = this.time + 's后信息將被改變'
      let t = setInterval(() => {
        this.time--
        if (this.time != 0) {
          this.buttonText = this.time + 's后信息將被改變'
        } else {
            this.buttonText = '成功改變啦'
            clearInterval(t)
        }
      }, 1000)
    },
  },
};
</script>

// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    userinfo: {
      name: '張三',
      six: '男'
    }
  },
  mutations: {
    changeInfo (state, payload) {
      state.userinfo.name = payload.name
      state.userinfo.six = payload.six
    }
  },
  actions: {
    // 這里直接用參數(shù)解構(gòu)的寫法
    changeInfo ({commit, state}, payload) { // 接收context(解構(gòu)寫法)和載荷payload
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          commit('changeInfo', {
            name: payload.name,
            six: payload.six
          })
          resolve("執(zhí)行完畢,可以開始改變狀態(tài)啦") // 成功之后即可直接在then的回調(diào)中接收
        }, payload.time * 1000)
      })
    }
  }
})

上面的代碼基本就是先展示state中存好的userinfo信息残炮,點擊按鈕之后會有一個time的倒計時來告訴你幾秒之后改變數(shù)據(jù)韭赘,改變之后再將文字改成"成功改變啦"。

其實上面這段代碼是有小瑕疵的势就,因為我們在分發(fā)actionchangeInfo函數(shù)中給的是一個定時器泉瞻,沒有任何延遲信息,所以5s之后返回來的參數(shù)和我們在changeButton()中寫好的改變button狀態(tài)的文字能正好對應上苞冯。而日常開發(fā)中通常這里會寫請求函數(shù)到后端然后等待返回值袖牙,這個過程如果耽誤1s那么們changeButton()中就會出現(xiàn)問題,它會優(yōu)先執(zhí)行this.buttonText = '成功改變啦'舅锄。

而實際因為請求有時間延遲鞭达,可能多出1s就會出現(xiàn)文字改變而上面的信息并未更新。這里特意留給我們自己思考,如何完善這個小bug畴蹭,其實答案很簡單烘贴,就是在then()的回調(diào)中去處理這段邏輯,具體的實現(xiàn)可以自行去了解哦撮胧,對Promise不太了解的可以點擊了解Promise桨踪。

  • 組合Action
利用Promise來實現(xiàn)組和Action

我們先直接上官網(wǎng)的demo小栗子,通過栗子看更明白

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

其實上面的這個小栗子在我們上一塊 Action結(jié)合Promise 中就說的很明白了芹啥,它就是想告訴我們通過Promise.resolve()之后我們就知道這個時候actionA就執(zhí)行完畢了锻离,我們就可以通過鏈式調(diào)用的then()方法來走下一個流程啦∧够常看官方給的actionB

actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
}

vue模板中調(diào)用

store.dispatch('actionA').then((res) => {
  // ... 這里執(zhí)行actionA中異步結(jié)束之后的程序嗤无,可以接受res為actionA中resolve("將返回結(jié)果返回出去")
})
利用 async / await來實現(xiàn)組和Action

asyncawait是ES7中推出的新語法妄呕,直接看官方的栗子熬粗,簡單明了

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}
一個 store.dispatch 在不同模塊中可以觸發(fā)多個 action 函數(shù)布持。在這種情況下,只有當所有觸發(fā)函數(shù)完成后钓账,返回的 Promise 才會執(zhí)行碴犬。

Module


由于使用單一狀態(tài)樹,應用的所有狀態(tài)會集中到一個比較大的對象梆暮。當應用變得非常復雜時服协,store 對象就有可能變得相當臃腫。

為了解決以上問題啦粹,Vuex 允許我們將 store 分割成模塊(module)偿荷。每個模塊擁有自己的statemutation唠椭、action跳纳、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割贪嫂,我們先來做個簡單的栗子寺庄,這里我們就不直接在index.js里面新建module了,因為實際開發(fā)中我們也是將模塊單獨拎出來撩荣,這樣更有利于代碼維護铣揉。如下栗子:

// moduleA.js
const moduleA = {
  state: {
    userInfo: {
      name: 'alen',
      age: 20
    }
  },
  mutations: { ... },
  getters: { ... },
  actions: { ... }
}
export default moduleA

// moduleB.js
const moduleB = {
  state: {
    userInfo: {
      name: 'nemo',
      age: 32
    }
  },
  mutations: { ... },
  getters: { ... },
  actions: { ... }
}
export default moduleB

store倉庫的跟文件index.js中引入模塊A和模塊B

import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'

Vue.use(Vuex)

export default new Vuex.Store({
  state: { ... },
  modules: {
    ma: moduleA,
    mb: moduleB
  }
})

上面的代碼就讓我們輕松完成了模塊的新建和引入饶深,如果我們想在組件里訪問模塊A中的name

<template>
  <div class="index">
    <div class="modulea-name">{{this.$store.state.ma.userInfo.name}}</div> // alen
    <div class="moduleb-name">{{this.$store.state.mb.userInfo.name}}</div> // nemo
  </div>
</template>

上面的代碼我們不難猜出餐曹,起始mambstate都是掛載在根節(jié)點的state下面。所以我們在組件中直接訪問模塊中的state需要先去訪問根節(jié)點的state然后在加上模塊名及對應的參數(shù)敌厘。

  • 模塊的局部狀態(tài)

對于模塊內(nèi)部的 mutationgetter台猴,接收的第一個參數(shù)是模塊的局部狀態(tài)對象。直接官方栗子加持:

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increment (state) {
      // 這里的 `state` 對象是模塊的局部狀態(tài)
      state.count++
    }
  },
  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

同樣,對于模塊內(nèi)部的 action饱狂,局部狀態(tài)通過 context.state 暴露出來曹步,根節(jié)點狀態(tài)則為 context.rootState

const moduleA = {
  // ...
  actions: {
    // 這里用的解構(gòu)寫法,實際上是context.state, context.commit, context.rootState
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

對于模塊內(nèi)部的 getter 休讳,根節(jié)點狀態(tài)會作為第三個參數(shù)暴露出來:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

這里我們直接舉一個組件操作模塊B進行mutation提交的小栗子

<template>
  <div class="index">
    <div class="moduleb-name">{{this.$store.state.mb.userInfo.name}}</div>
    <div class="moduleb-age">{{this.$store.state.mb.userInfo.age}}</div>
    <button @click="handleClickChangeAge(60)">修改年齡</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {
  methods: {
    handleClickChangeAge(newAge) {
      this.$store.commit({
        type: 'editAge',
        age: newAge
      })
    }
  }
}
</script>

// moduleB.js
const moduleB = {
  state: {
    userInfo: {
      name: 'nemo',
      age: 32,
      hobby: ['football', 'basketball', 'badminton', 'volleyball']
    }
  },
  mutations: {
    editAge (state, payload) {
      state.userInfo.age = payload.age
    }
  }
}

export default moduleB

看上面的代碼會發(fā)現(xiàn)我們通過 commit 提交模塊B里面的內(nèi)容并沒有使用 mb 這個模塊名讲婚,而是直接全局提交就能進行修改,我們再來看看 getters 是不是也是直接可以全局提交修改俊柔。

<template>
  <div class="index">
    <div class="moduleb-hobby">
      <div v-for="(item, index) in hobby" :key="index">{{item}}</div>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {ddd
  computed: {
    modeulaName () {
      console.log(this.$store.state.ma.userInfo.name)
    },
    filteHobby () {
      console.log(this.$store.getters.filteHobby)
      return this.$store.getters.filteHobby
    }
    // 使用語法糖來寫
    // ...mapGetters({
      // hobby: 'filteHobby'
    // })
  }
};
</script>

const moduleB = {
  state: {
    userInfo: {
      name: 'nemo',
      age: 32,
      hobby: ['football', 'basketball', 'badminton', 'volleyball']
    }
  },
  getters: {
    filteHobby (state, getters, rootState) {
      return state.userInfo.hobby.filter(item => item != 'football')
    }
  }
}
export default moduleB

果然 getters 也可以直接通過 this.$store.getters 來操作筹麸,而不需要再加上它所在的模塊名來進行調(diào)用,這里我們來看看官方是這樣說的:

默認情況下雏婶,模塊內(nèi)部的 action物赶、mutationgetter 是注冊在全局命名空間的——這樣使得多個模塊能夠?qū)ν?mutationaction 作出響應。
  • 命名空間

如果希望你的模塊具有更高的封裝度和復用性留晚,你可以通過添加 namespaced: true 的方式使其成為帶命名空間的模塊酵紫。當模塊被注冊后,它的所有 getter错维、actionmutation 都會自動根據(jù)模塊注冊的路徑調(diào)整命名奖地。直接來看小栗子:

// 新建模塊C
const moduleC = {
  namespaced: true,
  state: {
    userInfo: {
      name: 'kity',
      age: 10,
      list: [1, 2, 3, 4]
    }
  },
  getters: {
    filterList (state) {
      return state.userInfo.list.filter(item => item != 1)
    }
  },
  mutations: {
    changeAge (state, payload) {
      state.userInfo.age = payload.age
    }
  },
  actions: {...}
}
export default moduleC

// index.js中導入
import moduleC from './modules/moduleC'
export default new Vuex.Store({
  modules: {
    mc: moduleC
  }
})

// 模板文件
<template>
  <div class="index">
    <div class="modulec-name">{{this.$store.state.mc.userInfo.name}}</div>
    <div class="modulec-age">{{this.$store.state.mc.userInfo.age}}</div>
    <div class="modulec-list">
      <div v-for="(item, index) in list" :key="index">{{item}}</div>
    </div>
    <button @click="handleClickChangeAge(20)">修改年齡</button>
  </div>
</template>

通過 commit 提交 moduleC 中的 changeAge() ,這時候的寫法如下:

<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {
  methods: {
    handleClickChangeAge(newAge) {
      this.$store.commit('mc/changeAge',{
        age: newAge
      })
      this.$store.commit({
        type: 'mc/changeAge',
        age: newAge
      })
    }
  }
};
</script>

通過 getters 獲取 moduleC 中的 filterList()赋焕,一種是返回值直接通過 this.$store 去寫鹉动,一種是語法糖的寫法

<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {
  computed: {
    list () {
      return this.$store.getters['mc/filterList']
    },
    // ...mapGetters({
    //   list: 'mc/filterList'
    // })
  }
};
</script>

當然模塊里面再嵌套模塊也可以,路徑要不要多走一層主要看你的 namespaced: true 有沒有聲明宏邮,這里貼一下官方的代碼:

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,

      // 模塊內(nèi)容(module assets)
      state: () => ({ ... }), // 模塊內(nèi)的狀態(tài)已經(jīng)是嵌套的了泽示,使用 `namespaced` 屬性不會對其產(chǎn)生影響
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // 嵌套模塊
      modules: {
        // 繼承父模塊的命名空間
        myPage: {
          state: () => ({ ... }),
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },

        // 進一步嵌套命名空間
        posts: {
          namespaced: true,

          state: () => ({ ... }),
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

啟用了命名空間的 getteraction 會收到局部化的 getterdispatchcommit蜜氨。換言之械筛,你在使用模塊內(nèi)容(module assets)時不需要在同一模塊內(nèi)額外添加空間名前綴。更改 namespaced 屬性后不需要修改模塊內(nèi)的代碼飒炎。

  • 在帶命名空間的模塊內(nèi)訪問全局內(nèi)容

如果你希望使用全局 stategetter埋哟,rootStaterootGetters 會作為第三和第四參數(shù)傳入 getter,也會通過 context 對象的屬性傳入 action郎汪。

若需要在全局命名空間內(nèi)分發(fā) action 或提交 mutation赤赊,將 { root: true } 作為第三參數(shù)傳給 dispatchcommit 即可。這里直接官方栗子加持

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在這個模塊的 getter 中煞赢,`getters` 被局部化了
      // 你可以使用 getter 的第四個參數(shù)來調(diào)用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在這個模塊中抛计, dispatch 和 commit 也被局部化了
      // 他們可以接受 `root` 屬性以訪問根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}
  • 在帶命名空間的模塊注冊全局 action

若需要在帶命名空間的模塊注冊全局 action,你可添加 root: true照筑,并將這個 action 的定義放在函數(shù) handler 中吹截。例如:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction') // 通過dispatch直接調(diào)用不需要加上命名空間
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: { // 全局的action
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}
  • 在模塊里面使用輔助函數(shù)mapState瘦陈、mapGetters、mapMutations和mapActions

由于存在命名空間波俄,在組件里面采用上面的寫法會出現(xiàn)問題晨逝,這里要想使用輔助函數(shù)來映射模塊里面的東西需要指定空間名稱來告訴輔助函數(shù)應該去哪兒找這些。 這兒我以上面我的C模塊為例懦铺,首先對于 mapSatate 函數(shù)可以這樣玩捉貌,我在全局的 modules 里面聲明了 mc,那我的空間名稱就是 mc

computed: {
  ...mapState('mc', ['name', 'desc']) // 這里模塊里面要使用輔助函數(shù)的話要多傳一個參數(shù)才行
}

然后在模版里面寫 name冬念,desc 即可昏翰,或者可以這樣:

computed: {
  ...mapState('mc', {
    name(state) {
      return state.name;
    },
    desc(state) {
      return state.desc;
    }
  })
}

mapActionsmapMutations刘急、mapGetter都可以向上面一樣類似寫法棚菊,這里我們寫一個mapMutations的栗子參考

<script>
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
  methods: {
    handleClickChangeAge(newAge) {
      // 通過commit提交的寫法
      // this.$store.commit({
      //   type: 'mc/changeAge',
      //   age: newAge
      // })
      // 使用語法糖的寫法
      this.changeAge({
        age: newAge
      })
      // 當...mapMutations中第二個參數(shù)使用對象寫法時,this后面接的函數(shù)名應該是該對象的鍵
      this.editAge({
        age: newAge
      })
    },
    // 語法糖中第一個參數(shù)是對應的路徑叔汁,第二個參數(shù)為數(shù)組時的寫法
    ...mapMutations('mc', ['changeAge'])
    // 第二個參數(shù)為對象時的寫法
    ...mapMutations('mc', {
      editAge: 'changeAge' // 特意區(qū)分了鍵和值统求,值代表mutations中的函數(shù),鍵代表了模板中this調(diào)用的函數(shù)
    })
  }
};
</script>

如果你確實不想在每個輔助函數(shù)里寫空間名稱据块,Vuex 也提供了其它辦法码邻,使用createNamespacedHelpers 創(chuàng)建基于某個命名空間輔助函數(shù),它返回一個對象另假,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數(shù):

import { createNamespacedHelpers } from 'vuex';
const { mapState, mapMutations } = createNamespacedHelpers('mc');

這樣你在寫輔助函數(shù)的時候就不需要單獨指定空間名稱了像屋。 其它類似,就不再贅述了边篮!其實 vuex 官網(wǎng)中對于 Module 這個板塊還有幾個知識點己莺,只是對于了解基礎的話過多的深入可能還會影響自己的消化進度,如果當我們做一個項目龐大到需要建立很多個模塊戈轿,然后模塊中又進行嵌入凌受,那么相信我們對 vuex 已經(jīng)基本都了解了,到時候再去查閱相關(guān)的進階資料也很容易理解思杯。

結(jié)語


vuex 的幾個核心概念的基本認識就都在這里了胜蛉,本文也主要是參考了官網(wǎng)的文檔進行歸納總結(jié)。當然本篇相當于基礎入門篇色乾,實際開發(fā)中使用 vuex 肯定遠遠比這個復雜誊册,但是萬丈高樓平地起,希望對大家有所幫助暖璧,至于其他進階內(nèi)容大家有興趣進官網(wǎng)瀏覽案怯,也可查閱相關(guān)的資料進行學習。如果文章有理解不到位的地方漆撞,還請各位多批評指正殴泰!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浮驳,隨后出現(xiàn)的幾起案子悍汛,更是在濱河造成了極大的恐慌,老刑警劉巖至会,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件离咐,死亡現(xiàn)場離奇詭異,居然都是意外死亡奉件,警方通過查閱死者的電腦和手機宵蛀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來县貌,“玉大人术陶,你說我怎么就攤上這事∶汉郏” “怎么了梧宫?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長摆碉。 經(jīng)常有香客問我塘匣,道長,這世上最難降的妖魔是什么巷帝? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任忌卤,我火速辦了婚禮,結(jié)果婚禮上楞泼,老公的妹妹穿的比我還像新娘驰徊。我一直安慰自己,他們只是感情好堕阔,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布辣垒。 她就那樣靜靜地躺著,像睡著了一般印蔬。 火紅的嫁衣襯著肌膚如雪勋桶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天侥猬,我揣著相機與錄音例驹,去河邊找鬼。 笑死退唠,一個胖子當著我的面吹牛鹃锈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞧预,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屎债,長吁一口氣:“原來是場噩夢啊……” “哼仅政!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盆驹,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤圆丹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后躯喇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辫封,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年廉丽,在試婚紗的時候發(fā)現(xiàn)自己被綠了倦微。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡正压,死狀恐怖欣福,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情焦履,我是刑警寧澤劣欢,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站裁良,受9級特大地震影響凿将,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜价脾,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一牧抵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侨把,春花似錦犀变、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至骇笔,卻和暖如春省店,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笨触。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工懦傍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芦劣。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓粗俱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親虚吟。 傳聞我的和親對象是個殘疾皇子寸认,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355