[Vuex]Vuex學習手記

1、序言

本篇文章更像是我學習vuex的一個筆記,學習的資源主要是來自官方文檔教程,官方教程已經講的比較細緻了,部分地方也有本身不理解的地方,因此也查過其餘資料來輔助本身理解,本手記在官方的教程上加了一些本身的補充內容,但願能給你帶來一些參考價值,另外也感謝互聯網上其餘分享知識的大佬,讓我少走了些彎路!若是文章有理解不到位的地方,還請各位多批評指正!vue

2、Vuex之初體驗

一、爲什麼使用Vuex

使用Vue開發的過程當中,咱們常常會遇到一個狀態可能會在多個組件之間使用,好比咱們在作項目時使用到的用戶的信息,什麼暱稱、頭像這些,這些信息會在不一樣的組件用到,一旦改變這些狀態,咱們但願其餘組件也跟隨變化,好比用戶充值了100元,或者改變了暱稱,因此這個時候就須要狀態管理模式來集中管理,關於Vuex的詳細介紹能夠移步到官網。git

二、學習以前的準備

本次個人學習都是在官方提供的腳手架搭建的項目下學習的,關於腳手架的使用本次就再也不贅述,能夠移步到Vue CLI,在使用Vue CLI生成的項目時會讓你選擇store,選擇了後會在頁面給你生成一個store.js,這就是最初的store,裏面結構以下:github

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {

  },
  mutations: {

  },
  actions: {

  }
})
複製代碼

3、State

Vuex的核心就是倉庫store,這個store實例會被注入到全部子組件裏面,裏面的state屬性保存着咱們的狀態,好比咱們定義一個狀態count:vuex

export default new Vuex.Store({
  state: {
    count: 10
  },
})
複製代碼

這樣咱們就有一個集中管理的狀態count,那其餘組件如何取到這個count呢,能夠計算屬性來得到:api

export default {
  data() {
    
  },
  computed: {
    count() {
      return this.$store.state.count;
    }
  }
}
複製代碼

由於根實例中註冊 store 選項,該 store 實例會注入到根組件下的全部子組件中,且子組件能經過 this.$store 訪問到。經過計算屬性,咱們就能夠在模板裏面使用模板語法來調用count了,以下:數組

<template>
  <div>
    <p>{{ count }}</p>
  </div>
</template>
複製代碼

mapState

有時候須要獲取多個狀態,可是使用計算屬性會調用屢次,顯得麻煩,這裏藉助mapState方法來獲取state。 使用mapState須要引入該方法bash

import { mapState } from 'vuex';
複製代碼

注意:這裏使用了mapState方法後,computed的寫法有點區別,好比默認狀況你的computed屬性是這樣寫的:異步

data(){
  return{
    msg: 'hello '
  }
}
computed: {
  msg() {
    return this.msg + 'world!';
  }
}
複製代碼

那麼你使用了mapState後須要這樣寫computed,把msg()放入mapState,否則會報錯。函數

data(){
  return{
    msg: 'hello ',
    localCount: 20
  }
}
computed: mapState({
  msg() {  // 最初的
    return this.msg + 'world!';
  },
  // 使用mapState從store中引入state
  count(state) {
    return state.count;
  },
  name(state) {
    return state.firstName + ' ' + state.lastName;
  },
  mixCount(state) { // 結合store和組件狀態進行計算
    return state.count + this.localCount;
  },
})
複製代碼

若是你使用了展開運算符...,那麼computed屬性不須要改造,按正常寫法寫post

computed: { // 使用展開的話能夠按這種方式寫,不然要使用另一種方式,否則要報錯
  msg() {
    return this.$store.state.msg;
  },
  // 這裏返回一個狀態count,
  // 返回多個你能夠這樣寫...mapState(['count', 'firstName', 'lastName'])
  ...mapState(['count'])
},
複製代碼

4、Getter

getter就是對狀態進行處理的提取出來的公共部分,當狀態要進行篩選這些操做時,咱們能夠經過getter處理事後再返回給組件使用,好比咱們在state部分定義了一個list數組:

export default new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8]
  },
});
複製代碼

咱們想要篩選出數組裏面的偶數而後再在組件裏面使用,那麼篩選的這個工做能夠放在getter裏面來完成。

export default new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8]
  },
  getters: { //  這個主要是對狀態的處理,至關於把狀態處理的方法抽成公共部分來管理了
    modifyArr(state) { // 通常化getter
      return state.list.filter((item, index, arr) => {
        return item % 2 == 0;
      })
    },
    getLength(state, getter) { // 方法裏面傳getter,調用modifyArr來計算長度
      return getter.modifyArr.length;
    }
});
複製代碼

以後再在其餘組件的computed裏面去調用getter來獲取想要的狀態

computed: {
    list() {
      return this.$store.getters.modifyArr;
    },
}
複製代碼

mapGetters

mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性,當咱們想在組件裏面引入多個getter時,可使用mapGetter:

import {mapGetters} from 'vuex';
複製代碼

好比像剛纔在在上面定義的modifyArr,getLength。咱們想引入這個兩個並獲取其值:

computed: {
  ...mapGetter(['modifyArr', 'getLength'])
}
複製代碼

你固然能夠爲其指定別名,不必定非得用store裏面getters定義的名字:

computed: {
  mapGetter({
    arr: 'modifyArr',   // 把 `this.arr` 映射爲 `this.$store.getters.modifyArr`,下面同理
    length: 'getLength'
  })
}
複製代碼

若是你的computed屬性包含其餘計算方法,那你就只能使用展開運算符的寫法,這裏跟mapState有點區別,其餘計算屬性若是寫在mapGetter裏面會報錯,說不存在的getter,因此用如下的寫法:

computed: {
  msg() {
    return this.num * 10;
  },
  ...mapGetters([
    'modifyArr',
    'getLength'
  ])
}
複製代碼

或者指定別名

computed: { 
  msg() {
    return this.num * 10;
  },
  ...mapGetters({
    getList: 'modifyArr',
    length: 'getLength'
  })
}
複製代碼

而後再模板裏面調用:

<template>
  <div>
    <h2>mapGetters的使用演示</h2>
    <p>你的數字:{{ msg }}</p>
    <p>你的數組長度爲:{{ length }}</p>
    <ul>
      <li v-for="(item, index) in getList" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>
複製代碼

5、Mutation

當咱們須要修改store裏面的狀態時,咱們不是在組件裏面直接去修改它們,而是經過mutation裏面的方法來進行修改,這樣有利於追蹤狀態的改變。 好比state裏面有一個count變量,咱們點擊加減按鈕來控制它的值:

mutations: {
  add(state) {
    state.count++;
  },
  reduce(state) {
    state.count--;
  }
},
複製代碼

在其餘組件裏面,咱們經過定義methods並綁定時間來觸發改變,這裏須要使用commit:

methods: {
  add() {
    this.$store.commit('add');
  },
  reduce() {
    this.$store.commit('reduce');
  }
}
複製代碼

提交載荷

這個就是在commit時提交額外的參數,好比我傳了額外的值加到count上面:

mutations: {
  loadAdd(state, payload) {  // 提交載荷,額外參數
    state.count += payload;
  }
},
複製代碼

而後再組件裏面使用:

methods: {
  loadAdd() {
    this.$store.commit('loadAdd', 100); // 傳遞額外參數
  }
}
複製代碼

再這裏官方文檔建議載荷(也就是那個額外參數)最好使用對象來傳,這樣能夠包含多個字段而且記錄的 mutation 會更易讀,像這樣:

this.$store.commit('loadAdd', {
  extraCount: 100
}); // 傳遞額外參數
複製代碼

調用commit時咱們也能夠把全部參數寫在一個對象裏面:

this.$store.commit( {
  type: 'addLoad'
  extraCount: 100
}); // 傳遞額外參數
複製代碼

Mutation 需遵照 Vue 的響應規則

這個主要是說你再開發過程當中須要向state裏面添加額外數據時,須要遵循響應準則。 這裏我直接照搬官方文檔的說明: Vuex 中的 mutation 也須要與使用 Vue 同樣遵照一些注意事項:

  • 最好提早在你的 store 中初始化好全部所需屬性。

  • 當須要在對象上添加新屬性時,你應該使用 Vue.set(obj, 'newProp', 123), 或者以新對象替換老對象。例如,利用 stage-3 的對象展開運算符

咱們能夠這樣寫:

state.obj = { ...state.obj, newProp: 123 }
複製代碼

仍是舉個栗子: 我在mutation裏面聲明瞭一個方法

addNewState(state, payload) { // 我打算再這兒添加新的屬性到state
  // Vue.set(state, 'newProp', '添加一個新值!'); // 這是一種寫法
  // 這種寫法用新對象替換老對象
  // state= {...state, newProp: '添加一個新值!'} // 這個玩意兒無論用了,用下面的replaceState()方法
  this.replaceState({...state, newProp: '添加一個新值!'})
}
複製代碼

而後再組件裏面去調用,定義一個method:

addNewProp() {
  this.$store.commit('addNewState', {});
}
複製代碼

這樣再執行了這個方法後,會及時更新到state,再組件的computed屬性裏面定義:

newMsg() {
  return this.$store.state.newProp || '還沒添加新值';
}
複製代碼

在模板裏面即時展現,而且不會影響其餘狀態:

<p>添加的新值:{{ newMsg }}</p>
<div><button @click="addNewProp">添加新值</button></div>
複製代碼

Mutation 必須是同步函數

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

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}
複製代碼

mapMutations

這個跟前面的那幾個函數一個道理,都是爲了簡化調用,使用方法以下:

import {mapMutations} from 'vuex';
複製代碼

而後在組件的methods裏面使用,這裏使用官方代碼:

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')`
    })
  }
}
複製代碼

6、Action

Action 相似於 mutation,不一樣在於:

  • Action 提交的是 mutation,而不是直接變動狀態。
  • Action 能夠包含任意異步操做。 前面說過mutation只能包含同步事務,因此在處理異步事務就須要Action,經過Action控制了異步這一過程,以後再去調用mutation裏面的方法來改變狀態。 這裏我直接貼代碼來一目瞭然,首先我定義了一個狀態product:
state: {
  product: 'car'
}
複製代碼

而後再mutation中定義一個方法:

changeProduct(state, payload) {
  state.product = payload.change;
}
複製代碼

在action中定義:

actions: {
  changeProduct(context, payload) { // 這個context是一個與 store 實例具備相同方法和屬性的對象
    // 調用mutation裏的changeProduct方法
    // context.commit('changeProduct', {change: 'ship'});
    // 改爲異步方式
    // setTimeout(() => {
    //   context.commit('changeProduct', {change: 'ship'});
    // }, 1500)
    // 使用載荷
    let temp = 'ship+' + payload.extraInfo; 
    setTimeout(() => {
      context.commit('changeProduct', {change: temp});
    }, 1500)
  }
}
複製代碼

在組件methods中定義事件觸發分發:

methods: {
  selectProduct() {
    // this.$store.dispatch('changeProduct')
    // 載荷方式分發
    // this.$store.dispatch('changeProduct', {
    //   extraInfo: 'sportcar'
    // })
    // 或者這種
    this.$store.dispatch({
      type: 'changeProduct',
      extraInfo: '->sportcar'
    })
  }
},
複製代碼

這樣一個簡易的action就完成了!

mapActions

這裏就再也不贅述了,看名字就知道跟前面幾個叫map開頭的輔助函數相似,用來映射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裏面異步執行後的狀態而後再去修改其餘信息,這個能夠藉助Promise來實現。這裏在state裏面聲明一個狀態:

state: {
  userInfo: { // 這個變量用來測試組合變量
    name: 'lee',
    age: 23
  }
}
複製代碼

接着聲明mutation:

mutations: {
    // 如下用來測試組合action
    changeInfo(state, payload) {
      state.userInfo.name = 'lee haha';
    }
}
複製代碼

聲明action:

actions: {
  changeInfo(context, payload) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        context.commit('changeInfo');
        resolve();
      }, 2000)
    })
  }
}
複製代碼

這時咱們在組件裏面定義方法去派發這個action:

data() {
  return {
    status: '信息還沒修改!'
  }
}
methods: {
  modifyInfo() {
    this.$store.dispatch('changeInfo').then(() => {
      this.status = '信息修改爲功';
    });
  }
}
複製代碼

模板展現:

<template>
  <div>
    <h2>組合action</h2>
    <p>{{ status }}</p>
    <p>{{ info.name }}</p>
    <div><button @click="modifyInfo">修改信息</button></div>
  </div>
</template>

複製代碼

當咱們點擊修改信息後,就會派發action,當修改爲功的時候會同步修改上面說的展現信息。 固然其餘定義的action方法也能夠互相使用,這裏直接貼官方代碼了:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  },
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}
複製代碼

7、Module

模塊這部分正如其名,當全部狀態集中在一個對象中時,會變的至關臃腫,這個時候就須要模塊的管理辦法。這裏我仍是用代碼來講明,好比我在store裏面定義了兩個模塊:

// 定義的模塊A
const moduleA = {
  state: {
    name: 'lee',
    age: 23,
  },
  mutations: {

  },
  getters: {

  },
  actions: {

  }
};

// 定義模塊B
const moduleB = {
  state: {
    name: 'wang',
    age: 22
  },
  mutations: {

  },
  getters: {

  },
  actions: {

  }
}
複製代碼

而後再Vuex裏面聲明模塊:

export default new Vuex.Store({
  modules: {
    ma: moduleA,
    mb: moduleB
  },
  state: {
    ........... // 其餘狀態
  }
});
複製代碼

這樣一來,若是咱們想要在組件裏面訪問其餘模塊的狀態,能夠這樣,好比這裏我想調用B模塊裏的狀態:

computed: {
  msg() {
    return this.$store.mb; // 這裏返回的是:{name: 'wang', age: 22}
  }
}
複製代碼

關於模塊內部的局部狀態,這裏跟普通的store用法沒有多大的區別,主要區別如下外部傳進來的狀態,好比對於模塊內部的 action,局部狀態經過 context.state 暴露出來,根節點狀態則爲 context.rootState,這裏截取官方代碼:

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

複製代碼

對於模塊內部的 getter,根節點狀態會做爲第三個參數暴露出來:

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

複製代碼

那麼對於getters、mutations、actions裏面的方法咱們像基本的store那樣調用就能夠了,不存在做用域限制,仍是貼代碼栗子吧,下面是我在store.js裏面定義的模塊B:

const moduleB = {
  state: {
    name: 'wang',
    age: 22,
    desc: 'nope'
  },
  mutations: {
    modifyDesc(state, payload) {
      state.desc = payload.newMsg;
    }
  },
  getters: {

  },
  actions: {

  }
}
複製代碼

在組件裏面,我定義瞭如下內容:

<template>
  <div>
    <h2>七、module使用示例</h2>
    <div>
      <p>名字:{{ name }}</p>
      <p>描述:{{ desc }}</p>
      <button @click="handleClick">修改描述</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      name: this.$store.state.mb.name,
      // desc: this.$store.state.mb.desc 注意這個若是涉及到要在store裏面會被改變的狀態,必定要寫在
      // computed屬性裏面,否則不能及時反饋到視圖上
    }
  },
  computed: {
    desc() {
      return this.$store.state.mb.desc;
    }
  },
  methods: {
    handleClick() {
      this.$store.commit('modifyDesc', {newMsg: 'lao wang is beautiful!'});
    }
  },
}
</script>
複製代碼

這樣,就能夠調用mutation裏面的方法了,getters和actions同理

2019/09/09 補充餘下內容

命名空間模塊

默認狀況下,mutations、actions、getters這些都是註冊在全局上面的,你能夠直接調用,若是但願你的模塊具備更高的封裝度和複用性,你能夠經過添加 namespaced: true 的方式使其成爲帶命名空間的模塊。當模塊被註冊後,它的全部 getter、action 及 mutation 都會自動根據模塊註冊的路徑調整命名。 首先我新建一個js文件用來聲明模塊C:

/* 
* 這個文件用來聲明模塊C
*/

export const moduleC = {
  namespaced: true,
  state: {
    name: 'moduleC',
    desc: '這是模塊C,用來測試命名空間的!',
    list: [1, 2, 3, 4]
  },
  getters: {
    filterList(state) {
      return state.list.filter((item, index, arrSelf) => {
        return item % 2 !== 0;
      });
    }
  },
  mutations: {
    modifyName(state, payload) {
      state.name = payload.newName;
    }
  },
  actions: {
    
  }
}
複製代碼

而後在store.js裏面引入:

import { moduleC } from './module_c.js';

export default new Vuex.Store({
  modules: {
    mc: moduleC
  },
});
複製代碼

要想這個模塊成爲帶有命名空間的模塊,在上面聲明屬性namespaced: true就能夠了,那麼裏面的mutations、getters和actions裏面的方法的調用就要多走一層路徑,好比我在組件裏面去調用mutations裏面的方法(getters和actions同理):

methods: {
  modify() {
    // this.$store.commit('mc/modifyName', {
    //   newName: '命名空間模塊C'
    // })
    this.$store.commit({
      type: 'mc/modifyName',
      newName: '命名空間模塊C'
    })
  }
}
複製代碼

固然模塊裏面再嵌套模塊也能夠,路徑要不要多走一層主要看你的namespaced: true有沒有聲明,這裏貼一下官方的代碼:

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

      // 模塊內容(module assets)
      state: { ... }, // 模塊內的狀態已是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
      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']
          }
        }
      }
    }
  }
})
複製代碼

在帶命名空間的模塊內訪問全局內容

若是想要在模塊內部的getters、mutations和actions裏面訪問到全局的內容,這兒Vuex已經封裝好了,你只須要多傳幾個參數便可。官方演示來一波,簡單明瞭:

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在這個模塊的 getter 中,`getters` 被局部化了
      // 你可使用 getter 的第四個參數來調用 `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) { ... }
    }
  }
}
複製代碼

在模塊裏面使用輔助函數mapState、mapGetters、mapMutations和mapActions

因爲存在命名空間,在組件裏面採用上面的寫法會出現問題,這裏要想使用輔助函數來映射模塊裏面的東西須要指定空間名稱來告訴輔助函數應該去哪兒找這些。 這兒我以上面個人C模塊爲例,首先對於mapSatate函數能夠這樣玩,我在全局的modules裏面聲明瞭mc,那個人空間名稱就是mc:

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

而後在模版裏面寫name,desc便可,或者能夠這樣:

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

對於actions、mutations和getters方式相似,主要是要指定空間名稱,好比對於聲明的mutations:

methods: {
  ...mapMutations('mc', ['modifyName'])
}
複製代碼

若是你確實不想在每一個輔助函數裏寫空間名稱,Vuex也提供了其它辦法,使用createNamespacedHelpers建立基於某個命名空間輔助函數,它返回一個對象,對象裏有新的綁定在給定命名空間值上的組件綁定輔助函數:

import { createNamespacedHelpers } from 'vuex';

const { mapState, mapMutations } = createNamespacedHelpers('mc');
複製代碼

這樣你在寫輔助函數的時候就不須要單獨指定空間名稱了。 其它相似,恕我就再也不贅述了!

8、結語

本篇至關於基礎入門篇,其餘內容你們有興趣進官網瀏覽便可(#滑稽保命)。相關的代碼我已經傳到github上了,感興趣就下載來看看吧! 演示代碼

相關文章
相關標籤/搜索