簡單的理解就是你在state中定義了一個數據以後,你能夠在所在項目中的任何一個組件裏進行獲取、進行修改,而且你的修改能夠獲得全局的響應變動。 javascript
Vuex 應用的核心就是 store(倉庫)。「store」基本上就是一個容器,它包含着你的應用中大部分的狀態 (state)。
Vuex 和單純的全局對象有如下兩點不一樣:html
src├──
├── index.html
├── main.js
├── components
└── store
├── index.js # 咱們組裝模塊並導出 store 的地方
├── state.js # 根級別的 state
├── getters.js # 根級別的 getter
├── mutation-types.js # 根級別的mutations名稱(官方推薦mutions方法名使用大寫)
├── mutations.js # 根級別的 mutation
├── actions.js # 根級別的 action
└── modules
├── m1.js # 模塊1
└── m2.js # 模塊2
複製代碼
首先在項目中安裝vuexvue
npm install vuex --save 注意:這裏必定要加上--save由於這個包咱們在生產環境中也要使用。java
而後 在src文件目錄下新建一個名爲store的文件夾,爲方便引入並在store文件夾裏新建一個index.js,裏面的內容以下:git
import Vue from "vue";
import Vuex from "vuex";
import state from "./state";
import mutations from "./mutations";
import getters from "./getters";
import actions from "./actions";
import userinfo from "./module/userinfo";
Vue.use(Vuex);
const store = new Vuex.Store({
state, // state:state 的簡寫
getters,
mutations,
actions,
modules: {
userinfo
}
});
export default store;
複製代碼
接下來,在 main.js裏面引入store,而後再全局注入一下,這樣一來就能夠在任何一個組件裏面使用this.$store了:github
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store/index"; // 引入store
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
複製代碼
說了上面的前奏以後,接下來就是歸入正題了,就是開篇說的state的玩法。 vuex
簡單理解:在 store 中的 state 對象,能夠理解爲 Vue 實例中的 data 對象,它用來保存最基本的數據。 npm
export default {
nums: 0, // 數量
price: 100, // 金額
count: 0, // 總計
obj: {} // 定義一個空對象
}
複製代碼
<template>
<div class="hello">
<div>{{price}}</div>
</div>
</template>
<script>
import store from '../store/index.js'; // 1. 對象字面量方法需引入store
export default {
computed: {
// 1. 對象字面量方法獲取
// price () {
// return store.state.price
// },
// 2.經過在根實例中註冊 store 選項,該 store 實例會注入到根組件下的全部子組件中,且子組件能經過 this.$store 訪問到。
price () {
return this.$store.state.price
}
}
}
</script>
複製代碼
實際上作完上面的步驟你已經能夠用this.$store.state.price在任何一個組件裏面獲取price定義的值了,但這不是理想的獲取方式;當數據多時這種方法明顯效率太低,因此 Vuex 中提供了 mapState 方法用於批量映射 store 中的狀態。 api
<template>
<div class="hello">
<div>{{a}}和{{b}}</div>
</div>
</template>
<script>
import { mapState} from 'vuex';
export default {
computed: {
// 3.輔助函數
...mapState({
a: state => state.nums,
b: 'price'
}),
}
}
</script>
複製代碼
上例中a. 能夠經過 ES6 中的箭頭函數進行數據的映射,b. 當計算屬性的名稱與 state 的屬性名一致時可能直接經過字符串賦值。數組
若是全部計算屬性的名稱都與 state 一致,能夠在 mapState 中以數組的方式進行映射。若是 Vue 中已經存在計算屬性,能夠經過 ES6 的對象展開運算符 (…) 進行組合。
<template>
<div class="hello">
<div>{{nums}}和{{price}}</div>
</div>
</template>
<script>
import { mapState} from 'vuex';
export default {
computed: {
// 3.輔助函數
...mapState(['nums', 'price'])
}
}
</script>
複製代碼
在 Vuex 模塊化中,state 是惟一會根據組合時模塊的別名來添加層級的,後面的 getters、mutations 以及 actions 都是直接合並在 store 下**。**
簡單理解:在vuex中,更改state 的方式只有提交mutation.你們能夠把他就想象成vue中methods 中的一個方法。
// mutation.js
import * as types from './mutation-types'
export default {
[types.SET_NUMS] (state, nums) {
// 必寫state
state.nums = nums
},
[types.SET_PRICE] (state, price) {
state.price = price
},
// 改變state狀態
increments (state) {
state.count = state.count + 1
},
// 提交載荷
incrementsTwo (state, payload) {
state.count += payload
},
// 提交載荷
incrementsThree (state, payload) {
state.count = state.count + payload.num1
},
// 對象風格的傳參
incrementsFour (state, payload) {
state.count = state.count + payload.num1 + payload.num2
}
}
複製代碼
// HelloWorld.vue文件
<template>
<div class="hello">
<div @click="increments">mutation提交</div>
<div>mutation提交的值:{{count}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
methods() {
increments () {
this.$store.commit('increments')
}
}
}
</script>
複製代碼
想要改變狀態的時候都是用this.$store.commit的方式
每個 mutation 都有一個字符串的事件類型和一個回調函數,每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數:
第一種方式:提交載荷(Payload)
你能夠向 this.$store.commit 傳入額外的參數,即 mutation 的 載荷(payload):
// HelloWorld.vue文件
<template>
<div class="hello">
<div @click="incrementsTwo">mutation提交載荷按鈕</div>
<div>mutation提交載荷的狀態:{{count}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
methods() {
incrementsTwo () {
this.$store.commit('incrementsTwo', 10)
}
}
}
</script>
複製代碼
官方推薦,載荷應該是一個對象,這樣能夠包含多個字段而且記錄的 mutation 會更易讀:
// xxx.vue文件
<template>
<div class="hello">
<div @click="incrementsThree">mutation對象方式提交載荷按鈕</div>
<div>mutation對象方式提交載荷的狀態:{{count}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
methods() {
incrementsThree () {
this.$store.commit('incrementsThree', {num1: 30})
}
}
}
</script>
複製代碼
**第二種方式:對象風格的傳參方式
**提交 mutation 的另外一種方式是直接使用包含 type 屬性的對象:
// xxx.vue文件
<template>
<div class="hello">
<div @click="incrementsFour">4)mutation----對象風格傳參按鈕</div>
<div>mutation對象風格傳參狀態:{{count}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
methods() {
incrementsFour () {
this.$store.commit({
type: 'incrementsFour', // 事件名
num1: 30, // 參數1
num2: 20 // 參數2
})
}
}
}
</script>
複製代碼
Vue.set(obj, 'newProp', 123)
, 或者state.obj = { ...state.obj, newProp: 123 }
複製代碼
例如:
//mutation.js文件
export default {
// { ...state.obj, ...payload } 是指以新對象替換老對象
changeNum1 (state, payload) {
state.obj = { ...state.obj, ...payload }
}
}
// HelloWorld.vue文件
<template>
<div class="hello">
<button @click="changeName">對象新增屬性按鈕</button>
<div>對象新增屬性測試:{{name}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
data () {
return {
name: { // 定義name對象
a: 'aaaa'
}
}
},
methods() {
// 5.以新對象替換老對象
changeName () {
// this.name.b = 'bbbb' // 這樣新增屬性是錯誤的
this.$set(this.name, 'b', 'bbbb') // 當須要在對象上添加新屬性時,你應該 Vue.set(obj, 'xxx', xx)
// this.name = { ...this.name, b: 'bbbb' } // 以新對象替換老對象。例如,利用 stage-3 的對象展開運算符咱們能夠這樣寫
}
}
}
</script>
複製代碼
使用常量替代 mutation 事件類型在各類 Flux 實現中是很常見的模式。這樣可使 linter 之類的工具發揮做用,同時把這些常量放在單獨的文件中可讓你的代碼合做者對整個 app 包含的 mutation 一目瞭然:
// mutation-types.js
export const SET_NUMS = 'SET_NUMS' // 數量
export const SET_PRICE = 'SET_PRICE' // 加個
export const SET_FIRSTNAME = 'SET_FIRSTNAME' // firstname
export const SET_LASTNAME = 'SET_LASTNAME' // lastname
//mutations.js
import * as types from './mutation-types'
export default {
// 使用常量替代 Mutation 事件類型
[types.SET_NUMS] (state, nums) {
// 必寫state
state.nums = nums
},
// 使用常量替代 Mutation 事件類型
[types.SET_PRICE] (state, price) {
state.price = price
}
}
// HelloWorld.vue
<template>
<div class="hello">
<button @click="add">add事件</button>
<div href="#">add事件操做後的nums值:{{a}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
data () {
return {
name: { // 定義name對象
a: 'aaaa'
}
}
},
computed: {
...mapState({
a: state => state.nums, //獲取nums狀態
b: 'price',
count: 'count'
}),
},
methods() {
add () {
let nums = this.a
nums++
this.setNums(nums)
},
...mapMutations({
setNums: type.SET_NUMS,
setprice: type.SET_PRICE
})
}
}
</script>
複製代碼
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
複製代碼
在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:由於當 mutation 觸發的時候,回調函數尚未被調用,devtools 不知道何時回調函數實際上被調用 —— 實質上任何在回調函數中進行的的狀態的改變都是不可追蹤的。
你能夠在組件中使用 this.$store.commit(‘xxx’) 提交 mutation,或者使用 mapMutations 輔助函數將組件中的 methods 映射爲 store.commit 調用(須要在根節點注入 store)
// mutation-types.js
export const SET_NUMS = 'SET_NUMS' // 數量
export const SET_PRICE = 'SET_PRICE' // 加個
export const SET_FIRSTNAME = 'SET_FIRSTNAME' // firstname
export const SET_LASTNAME = 'SET_LASTNAME' // lastname
//mutations.js
import * as types from './mutation-types'
export default {
// 使用常量替代 Mutation 事件類型
[types.SET_NUMS] (state, nums) {
// 必寫state
state.nums = nums
},
// 使用常量替代 Mutation 事件類型
[types.SET_PRICE] (state, price) {
state.price = price
}
}
// HelloWorld.vue
<template>
<div class="hello">
<button @click="add">add事件</button>
<div href="#">add事件操做後的nums值:{{a}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
data () {
return {
name: { // 定義name對象
a: 'aaaa'
}
}
},
computed: {
...mapState({
a: state => state.nums, //獲取nums狀態
b: 'price',
count: 'count'
}),
},
methods() {
add () {
let nums = this.a
nums++
this.setNums(nums)
},
// mapMutations輔助函數
...mapMutations({
setNums: type.SET_NUMS, // 將 `this.setNums()` 映射爲 `this.$store.commit('setNums')`
setprice: type.SET_PRICE // 將 `this.setprice()` 映射爲 `this.$store.setprice('setNums')`
})
}
}
</script>
複製代碼
在 mutation 中混合異步調用會致使你的程序很難調試。例如,當你能調用了兩個包含異步回調的 mutation 來改變狀態,你怎麼知道何時回調和哪一個先回調呢?這就是爲何咱們要區分這兩個概念。在 Vuex 中,mutation 都是同步事務:接下來聊聊actions
Action 相似於 mutation,不一樣在於:
// state.js
export default {
nums: 0, // 數量
price: 100, // 金額
count: 0, // 總計
obj: {} // 定義一個空對象
}
// mutations
export default {
incrementsFour (state, payload) {
state.count = state.count + payload.num1 + payload.num2
}
}
// actions.js
import * as types from './mutation-types'
export default {
// 註冊一個簡單的action
increment ({ commit }, payload) {
commit({
type: 'incrementsFour',
num1: payload.num1,
num2: payload.num2
})
},
}
複製代碼
Action 經過 this.$store.dispatch方法觸發:
// state.js
export default {
nums: 0, // 數量
price: 100, // 金額
count: 0, // 總計
obj: {} // 定義一個空對象
}
// mutations
export default {
incrementsFour (state, payload) {
state.count = state.count + payload.num1 + payload.num2
}
}
// actions.js
import * as types from './mutation-types'
export default {
// 註冊一個簡單的action
increment ({ commit }, payload) {
commit({
type: 'incrementsFour',
num1: payload.num1,
num2: payload.num2
})
},
}
// HelloWorld.vue
<template>
<div class="hello">
<button @click="useAction">action調用</button>
<div>被action調用後的值:{{count}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations} from 'vuex';
export default {
data () {
return {
name: { // 定義name對象
a: 'aaaa'
}
}
},
computed: {
...mapState({
a: state => state.nums, //獲取nums狀態
b: 'price',
count: 'count'
}),
},
methods() {
useAction () {
// action的dispatch分發
this.$store.dispatch('increment', { num1: 40, num2: 30 })
}
}
</script>
複製代碼
乍一眼看上去感受畫蛇添足,咱們直接分發 mutation 豈不更方便?實際上並不是如此,還記得 mutation 必須同步執行這個限制麼?Action 就不受約束!咱們能夠在 action 內部執行異步操做:
actions: {
increment ({ commit }, payload) {
setTimeout(() => {
commit({
type: 'incrementsFive',
num1: payload.num1,
num2: payload.num2
})
}, 1000)
}
}
複製代碼
Actions 支持一樣的載荷方式和對象方式進行分發:
useAction () {
// 1.action的dispatch分發 action支持以載荷形式分發
// this.$store.dispatch('increment', { num1: 40, num2: 30 })
// 2.action的dispatch分發 action支持以對象形式分發
this.$store.dispatch({ type: 'increment', num1: 40, num2: 30 })
}
複製代碼
你在組件中使用 this.$store.dispatch('xxx')
分發 action,或者使用 mapActions
輔助函數將組件的 methods 映射爲 store.dispatch
調用(須要先在根節點注入 store
):
// HelloWorld.vue
<template>
<div class="hello">
<button @click="useAction">action調用</button>
<div>被action調用後的值:{{count}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations,mapActions} from 'vuex';
export default {
computed: {
...mapState({
a: state => state.nums, //獲取nums狀態
b: 'price',
count: 'count'
}),
},
methods() {
useAction () {
this.changeIncrement()
},
// mapAction輔助函數
...mapActions({
changeIncrement: 'increment' // 將 `this.changeIncrement()` 映射爲 `this.$store.dispatch('increment')`
}),
}
</script>
複製代碼
咱們如何才能組合多個 action,以處理更加複雜的異步流程?
// action.js
import * as types from './mutation-types';
export default {
changeNumAndPrice ({ commit }) {
// 自定義觸發mutations裏函數的方法,context與store 實例具備相同方法和屬性
commit(types.SET_NUMS, 100)
commit(types.SET_PRICE, 100)
},
// 註冊一個簡單的action
increment ({ commit }, payload) {
commit({
type: 'incrementsFive',
num1: payload.num1,
num2: payload.num2
})
},
actionB (context) {
console.log('失敗')
},
// 分組action
actionA ({ dispatch }, payload) {
return dispatch('increment', {
num1: payload.num1,
num2: payload.num2
}).then(() => {
return dispatch('actionB')
})
}
}
// HelloWorld.vue
<template>
<div class="hello">
<button @click="useAction">action調用</button>
<div>被action調用後的值:{{count}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations,mapActions} from 'vuex';
export default {
computed: {
...mapState({
a: state => state.nums, //獲取nums狀態
b: 'price',
count: 'count'
}),
},
methods() {
useAction () {
this.changeIncrement({ num1: 20, num2: 30 })
},
// mapAction輔助函數
...mapActions({
changeIncrement: 'actionA'
}),
}
</script>
複製代碼
有一點要注意的是,將 store 中的 state 綁定到 Vue 組件中的 computed 計算屬性後,對 state 進行更改須要經過 mutation 或者 action,在 Vue 組件中直接進行賦值 (this.myState = ‘ABC’) 是不會生效的。
有時候咱們須要根據store 中的 state 來派生出一些其餘狀態,例如對列表進行過濾並計數,再好比:依據商品單價和商品數量計算商品總價。這時候可使用getters計算屬性:
// getters.js
export default {
total: state => { // 商品總價
return state.nums * state.price
}
}
// HelloWorld.vue
<template>
<div class="hello">
<button @click="add">add事件</button>
<div>getters獲取的值:{{total}}</div>
<button @click="changePrice">改變價格</button>
<div>{{total}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations,mapActions} from 'vuex';
export default {
computed: {
...mapState({
a: state => state.nums, //獲取nums狀態
b: 'price',
count: 'count'
}),
// 1.獲取getters
total () {
return this.$store.getters.total
}
},
methods() {
add () {
let nums = 1
nums++
this.setNums(nums)
},
// changePrice
changePrice () {
let nums = 3
nums++
this.setprice(nums)
},
// 6. mapMutations輔助函數
...mapMutations({
setNums: type.SET_NUMS, // 將 `this.setNums()` 映射爲 `this.$store.commit('setNums')`
setprice: type.SET_PRICE // 將 `this.setprice()` 映射爲 `this.$store.setprice('setNums')`
}),
}
</script>
複製代碼
mapGetters
輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性:
// getters.js
export default {
total: state => { // 商品總價
return state.nums * state.price
}
}
// HelloWorld.vue
<template>
<div class="hello">
<button @click="add">add事件</button>
<div>getters獲取的值:{{total}}</div>
<button @click="changePrice">改變價格</button>
<div>{{total}}</div>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations,mapActions,mapGetters} from 'vuex';
export default {
computed: {
...mapState({
a: state => state.nums, //獲取nums狀態
b: 'price',
count: 'count'
}),
// 2.mapGetters輔助函數
...mapGetters(['total'])
},
methods() {
add () {
let nums = this.a
nums++
this.setNums(nums)
},
// changePrice
changePrice () {
let price = this.price
price++
this.setprice(price)
},
// 6. mapMutations輔助函數
...mapMutations({
setNums: type.SET_NUMS, // 將 `this.setNums()` 映射爲 `this.$store.commit('setNums')`
setprice: type.SET_PRICE // 將 `this.setprice()` 映射爲 `this.$store.setprice('setNums')`
}),
}
</script>
複製代碼
由於在大多數的項目中,咱們對於全局狀態的管理並不只僅只有一種狀況的需求,有時有多方面的需求,好比寫一個商城項目,你所用到的全局state多是關於購物車這一起的也有多是關於用戶信息這一起的;像這樣的狀況咱們就要考慮使用vuex中的 modules 模塊化了,具體怎麼使用modules呢?請往下看
首先,在store文件夾下面新建一個modules文件夾,而後在modules文件裏面創建須要管理狀態的js文件,既然要把不一樣部分的狀態分開管理,那就要把它們給分紅獨立的狀態文件了,以下圖:
// store文件下index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import userinfo from './module/userinfo'
Vue.use(Vuex)
const store = new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
userinfo
}
})
export default store
複製代碼
命名空間:namespaced: true, 爲何要加這個呢?默認狀況下,模塊內部的 action、mutation 和 getter 是註冊在全局命名空間的。好比說:你在全局的action.js下定義了一個方法,而後在某個模塊下定義了相同的方法就會產生覆蓋,加命名空間的目的是爲了在某個模塊下定義的方法加上模塊名,與全局的方法區分開。
// module文件下的userinfo.js
import * as types from '../mutation-types'
export default {
namespaced: true, // 命名空間
state: {
firstName: '--',
lastName: '--'
},
getters: {
fullName (state) {
return state.firstName + state.lastName
}
},
mutations: {
[types.SET_FIRSTNAME] (state, payload) {
state.firstName = payload
},
[types.SET_LASTNAME] (state, payload) {
state.lastName = payload
}
},
actions: {
changeName ({ commit }) {
// 注意: 想要訪問跟級別的狀態可使用rootState
setTimeout(() => {
commit(types.SET_FIRSTNAME, 'ling')
commit(types.SET_LASTNAME, 'xi')
}, 1000)
}
}
}
複製代碼
// HelloWorld.vue調用
<template>
<div class="hello">
<h1>獲取userinfo模塊的狀態:firstName: {{firstName}} lastName: {{lastName}}</h1>
</div>
</template>
<script>
import * as type from '../store/mutation-types'
import { mapState,mapMutations,mapActions,mapGetters} from 'vuex';
export default {
computed: {
...mapState('userinfo', ['firstName', 'lastName']),
},
methods() {
...mapActions('userinfo', ['changeName'])
},
created () { // 調用userinfo的changeName方法
this.changeName()
}
}
</script>
複製代碼
參考文獻:meiminjun.github.io/vue-vuex/
官方文檔:vuex.vuejs.org/zh/guide/ac…