Vuex簡明教程

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。(源於官網)vue

狀態的自我管理應該包含:vuex

  • state,驅動應用的數據源;
  • view,以聲明方式將 state 映射到視圖;
  • actions,響應在 view 上的用戶輸入致使的狀態變化。 (源於官網)

Vuex 應用的核心就是 store(倉庫,能夠理解爲容器),store 包含着應用中的大部分狀態(state),用戶不能直接改變 store 中的狀態。改變 store 中的狀態的惟一途徑就是顯式地提交 (commit) mutation(源於官網)redux

開始

/// index.js 入口文件
import Vue from 'vue';
import Vuex from 'vuex';
import main from '../component/main'

Vue.use(Vuex); // 要注意這裏須要啓用 Vuex
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment (state) {
            state.count++
        }
    }
}) // 設置了一個 Store

let vm = new Vue({
    el: '#app',
    render: h => h(main),
    store // 全局注入,全部組件都可使用這個 store
})
...
/// main.vue 子組件
<template>
    <div>
        {{ msg }} - {{ count }}
        <button @click="incream">add count</button>
    </div>
</template>
<script>

export default {
    data () {
        return {
            msg: 'msg'
        }
    },
    computed: {
        count () {
            return this.$store.state.count 
        }
    },
    methods: {
        incream () {
            this.$store.commit('increment');
        }
    }
}
</script>
/// 注入的store,能夠經過 this.$store 獲取
複製代碼

state

Vuex 使用單一狀態樹,用一個對象就包含了所有的應用層級狀態。單一狀態樹讓咱們可以直接地定位任一特定的狀態片斷,在調試的過程當中也能輕易地取得整個當前應用狀態的快照。(源於官網)數組

store 實例中讀取 state 最簡單的方法就是在 computed 中返回某個狀態(源於官網)緩存

若是把數據獲取置於data中,初始化時能夠正常獲取,但更新後並不能同步更新bash

mapState

mapState 輔助函數幫助咱們生成計算屬性(源於官網)app

import { mapState } from 'vuex';
...
computed: mapState({
  count (state) { // 在這個方法中能夠直接使用state,不用使用 this.$store.state
  	return state.count
  },
  count1: state => state.count, // 這種是上面寫法的簡寫
  count2: 'count' // 這種是 state => state.count 的簡寫
}), // 這個方法最終會返回一個對象
...
/// 若是映射的計算屬性的名稱與 state 的子節點名稱相同時,咱們能夠簡化給 mapState 傳一個字符串數組
computed: mapState([
	'count' // 映射 this.count 爲 store.state.count
])
...
/// 通常不會使用上面寫法,由於這樣其餘計算屬性就沒法添加,最好利用這個函數會返回對象的特性,直接使用對象展開的寫法
 computed: {
   ...mapState([
   	'count'
   ]),
   info () {
   	return `${this.msg} info`
   }
 },
複製代碼

Getter

Getter(能夠認爲是 store 的計算屬性),就像計算屬性同樣,Getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算(源於官網,稍改)異步

  • Getter 接受 state 做爲其第一個參數
/// 定義時,能夠把其看成 state 的計算屬性
const store = new Vuex.Store({
    state: {
        count: 0
    },
    getters: {
        setCount (state) {
            return `set count: ${state.count}`
        }
    },
複製代碼
  • 經過屬性直接訪問(this.$stroe.getters
computed: {
  showSetCount () { // 訪問時能夠經過 this.$store.getters 訪問
  	return this.$store.getters.setCount
  },
複製代碼
  • Getter 也能夠接受其餘 getter 做爲第二個參數
getters: {
  setCount (state) {
  	return `set count: ${state.count}`
  },
  allMsg (state, getters) {
  	return `all ${getters.setCount}` // 這裏的 getters 和 state 同樣都是當前 store 下的
  }
}
複製代碼

mapGetters

同上文的 mapState 同樣使用async

import { mapState, mapGetters } from 'vuex';
...
computed: {
  ...mapGetters({
    showSetCount: 'setCount',
    showAllMsg: 'allMsg'
  }),
}
複製代碼

Mutation

mutation 是更改 store 中狀態的惟一方式,每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler),不能直接調用一個 mutation handler,須要經過 store.commit 方法進行調用(源於官網,稍改)函數

  • 回調函數會接受 state 做爲第一個參數
mutations: {
  increment (state) {
  	state.count++
  }
}
...
<button @click="incream">add count</button>
...
methods: {
  incream () {
  	this.$store.commit('increment'); // 經過 store.commit 觸發
  }
}
複製代碼
  • Payload
/// commit 接收第二個參數,這樣外部數據就能夠更新到倉庫中
mutations: {
  increment (state, payload) {
  	state.count += payload.amount
  }
}
...
methods: {
  incream () {
    this.$store.commit('increment', {
    	amount: 20
    });
  }
},
/// 若是你是redux的使用者,可能更習慣對象風格的代碼提交
incream () {
  this.$store.commit({
    type: 'increment',
    amount: 20
  })
}
複製代碼

mapMutations

/// 若是使用了 types 的方式,應該都要用下面方式,調用時直接傳 payload
methods: {
  ...mapMutations({
  	'incream': INCREMENT
  })
},
...
/// 在調用處,直接傳參
<button @click="incream({amount: 20})">add count</button>
複製代碼

須要遵照的規則

  • 最好提早在你的 store 中初始化好全部所需屬性
  • 當須要在對象上添加新屬性時,利用對象展開運算符,使用新對象替換老對象
state.obj = { ...state.obj, newProp: 123 }
複製代碼
  • 使用常量替代 Mutation 事件類型(這是類 Flux 的狀態管理都建議的作法,統一管理,統一維護)
/// types.js
export const INCREMENT = 'INCREMENT';
...
/// index.js
import { INCREMENT } from './types';
...
mutations: {
  [INCREMENT] (state, payload) {
  	state.count += payload.amount
  }
}
...
/// main.vue
import { INCREMENT } from '../script/types';
...
methods: {
  incream () {
    this.$store.commit({
      type: INCREMENT,
      amount: 20
    })
  }
},
複製代碼
  • Mutation 必須是同步函數(保證狀態的變動是可追蹤的),若是須要使用異步方法用 Action

Action

  • action 提交的是 mutation,不能直接變動狀態(state)
  • action 能夠包含異步操做,action 經過 dispatch 觸發
/// index.js
actions: {
	// action 能夠是同步方法,也能夠是異步方法
  increment (context) {
  	context.commit(INCREMENT, {amount: 10})
  }
}
...
/// main.vue
methods: {
  incream () {
  	this.$store.dispatch('increment') // 經過 dispatch 觸發 action
  }
},
複製代碼

context

  • Action 函數接受一個與 store 實例具備相同方法和屬性的 context 對象,能夠調用 context.commit 提交一個 mutation,或者經過 context.state 和 context.getters 來獲取 state 和 getters。(源於官網)
// 使用對象解構簡化操做
increment ({commit}) {
	commit(INCREMENT, {amount: 10})
}
複製代碼
  • Action 函數接受第二個參數用做 payload
increment ({commit}, payload) {
	commit(INCREMENT, {amount: payload.amount})
}
...
/// main.vue
incream () {
	// 以 payload 的形式調用
  this.$store.dispatch('increment', {
  	amount: 15
  })
  ...
  // 還能夠用對象形式調用
  this.$store.dispatch({
    type: 'increment',
    amount: 15
  })
}
複製代碼

mapActions

methods: {
  ...mapActions({
  	incream: 'increment'
  })
}
...
// 在方法調用處傳 payload
<button @click="incream({amount: 15})">add count</button>
複製代碼

組合 Action

能夠在action中寫各類異步方法,來組合複雜的業務邏輯

actions: {
  increment ({commit, dispatch}, payload) { // context 對象也有 dispatch
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit(INCREMENT, {amount: payload.amount}) // 觸發 Mutations 更新
        dispatch('log') // 在一個 action 中觸發另一個 action
        resolve({
        	code: '000000'
        }) // 返回異步處理狀況
      }, 500); // 模擬接口返回
    })
  },
  log () {
  	console.log('日誌.....')
  }
}
...
/// main.vue
<button @click="incream({amount: 15}).then(doneCheck)">add count</button>
...
methods: {
  ...mapActions({
  	incream: 'increment'
  }),
  doneCheck (status) {
  	console.log('status', status); // 這裏能夠拿到異步處理結果來繼續後續邏輯
  }
},
複製代碼

這裏用 Promise 模擬了一個接口處理狀況,在 action 中觸發狀態更新,並觸發另一個 action,返回異步結果供調用函數使用(還可使用 async / await 更加簡化代碼)

Module

使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。

Vuex 容許咱們將 store 分割成模塊(module),每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割(源於官網)

/// index.js
const countModule = {
    state: () => ({ // 子 store 下的 state 用這種函數的返回的形式,緣由和組件內data 定義同樣
        count: 0
    }),
    getters: {
        setCount (state) {
            return `set count: ${state.count}`
        },
        allMsg (state, getters) {
            return `all ${getters.setCount}`
        }
    },
    mutations: {
        [INCREMENT] (state, payload) {
            state.count += payload.amount
        }
    },
    actions: {
        increment ({commit, dispatch}, payload) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    commit(INCREMENT, {amount: payload.amount})
                    dispatch('log')
                    resolve({
                        code: '000000'
                    })
                }, 500);
            })
        },
        log () {
            console.log('日誌.....')
        }
    }
}

const infoModule = {
    state: () => ({
        info: 'store info'
    }),
    getters: {
        allMsg (state) {
            return `show ${state.info}`
        }
    }
}

const store = new Vuex.Store({
    modules: { // 分別定義兩個store經過modules的方式組合在一塊兒
        countModule,
        infoModule
    }
})
...
/// main.vue
/// 使用時,除state外,和以前使用方式一致
    computed: {
        ...mapState({
            count: state => state.countModule.count, // state 要指定模塊
            info: state => state.infoModule.info
        }),
        ...mapGetters({
            showSetCount: 'setCount',
            showAllMsg: 'allMsg'
        }),
    },
    methods: {
        ...mapActions({
            incream: 'increment'
        }),
    }
...
/// 若是想保持 mapState 中數組的簡介寫法,能夠這麼改造下
...mapState([
  'countModule',
  'infoModule'
]), // mapState 中直接導入模塊名
...
{{ countModule.count }} - {{ infoModule.info }}
/// 使用時按模塊名去獲取 state
複製代碼

模塊中只有 state 在使用時須要特別指明模塊,action、mutation 和 getter 由於是註冊在全局 store 上,因此能夠直接使用,經過打印 this.$store 也看到兩個不一樣模塊的 getters 是都加掛在 store 實例對象上

rootState

/// 若是外層 store 中也有 state,能夠經過 rootState 獲取
const store = new Vuex.Store({
    modules: {
        countModule,
        infoModule
    },
    state: {
        copyright: 'xxx company'
    }
})
/// getters、mutations 的第三個參數表示的是 rootState(名字能夠隨意)
getters: {
  setCount (state, getters, rootState) {
    return `set count: ${state.count}`
  },
...
mutations: {
	[INCREMENT] (state, payload, rootState) {
...
/// action中則必須使用(rootState這個變量名)
actions: {
        increment ({commit, dispatch, rootState}, payload) {
複製代碼

命名空間

我感受不必使用,若是咱們在拆分模塊時,模塊名是一個有明顯語意的名字,在定義 action、getters 這些內容時,用 模塊名+屬性/方法名 的方式同樣能達到相同的功效,這樣也能夠很明晰的知道這個方法屬於哪一個模塊。

這樣也能夠有很高的複用性,封裝型也不差,使用起來也簡單。

/// index.js
const infoModule = {
    getters: {
        infoModuleShowInfo (state) { 
            return `show ${state.info}`
        }
    }
...
/// main.vue
 ...mapGetters({
 	showInfo: 'infoModuleShowInfo'
 }),
 ...
/// 模塊名+屬性名,使用時直接用 infoModuleShowInfo,並且能看出是屬於 infoModule
複製代碼

插件

能夠在 store 的根節點下,使用 plugins 的形式加掛插件

const store = new Vuex.Store({
  // ...
  plugins: [myPlugin]
})
複製代碼

Vuex 內置了 logger 插件

import createLogger from 'vuex/dist/logger'

const store = new Vuex.Store({
  plugins: [createLogger()]
})
複製代碼

表單處理

Vuex 的 state 上使用 v-model,須要使用帶有 setter 的雙向綁定計算屬性

const store = new Vuex.Store({
    modules: {
        countModule,
        infoModule
    },
    state: {
        copyright: '2020 company',
        auther: 'rede'
    },
    getters: {
        bookInfo (state) {
            return `${state.auther} @ ${state.copyright}`
        }
    },
    actions: {
        updataAutherAction ({commit}, payload) {
            commit('updataAuther', {
                value: payload.value
            })
        }
    },
    mutations: {
        updataAuther (state, payload) {
            state.auther = payload.value
        }
    }
})
...
/// main.vue
<input type="text" v-model="auther">
<div>{{bookInfo}}</div>
...
computed: {
  ...mapGetters({
  	bookInfo: 'bookInfo'
  }),
  auther:{
    get () {
      return this.$store.state.auther
    },
    set (value) {
      this.updataAutherAction({
        value
      })
    }
  },
...  
複製代碼
相關文章
相關標籤/搜索