Vuex詳細介紹

1. 什麼是Vuex

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。這是官網的說法,其實很簡單:就是一個加強版的data!
在單頁應用中會有一個data函數,裏面就存放了當前頁面的一些數據。比如:

<template>
    <div>
        <p>{{num}}</p>
    </div>
</template>
<script>
export default {
    data(){
        return {
            num:'99'
        }
    }
}
</script>

代碼中的data就相當於我們這裏描述的Vuex。
如果我們的頁面比較簡單,切記千萬不要沒事找事引入Vuex,我們使用Vuex是因爲項目變得複雜之後,有很多數據需要在父組件、子組件和孫組件之間傳遞,處理起來很繁瑣,於是就需要Vuex這樣一個可以對這一部分數據進行統一管理。

2. 全局data就行了,非要這麼複雜?

對於我剛剛提到的需求:處理大量的需要在組件間傳遞的數據,直接定義一個全局的data屬性保存就行了,比如這樣:this.$root.$data。爲什麼還要搞一個這麼複雜的狀態管理?
如果我們按照剛剛所說的搞一個全局變量存放數據其實也行,但是這樣有一個問題,就是數據改變後,不會留下變更過的記錄,這樣不利於調試。
所以我們稍微搞得複雜一點。我們約定組件不能直接修改屬於store實例的state,組件必須通過Mutation來改變state,也就是說,組件裏面應該執行分發(dispatch)事件通知store去改變。這樣約定的好處是,我們能夠記錄所有store中發生的state改變,同時實現能做到記錄變更、保存狀態快照、歷史回滾/時光旅行的先進的調試工具。

3. 先來一個簡單的State

知道了個大概,那我們就實際操作一遍,感覺一下這個聽起來很牛逼的東西用起來到底怎麼樣。
我們使用vue-cli腳手架生成一個Vue項目,並且安裝Vuex,最終的項目結構是這樣的:項目結構
我們在components裏面新建一個組件,然後加入以下代碼:

<p>{{ count }}</p>
<div>
    <p>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
    </p>
</div>
methods:{
    increment(){
    },
    decrement(){
    },
},

接着新建一個vuex文件夾,裏面新建一個store.js文件,並加入以下代碼:

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

Vue.use(Vuex)

// 應用初始狀態
const state = {
    count: 0,
}

// 創建 store 實例
export default new Vuex.Store({
    state,
})

然後我們在組件中就可以使用我們的count變量了:

computed:{
    count(){
        return this.$store.state.count;
    },
}

我們定義一個計算屬性,返回store中的count變量:
結果

3. 計算屬性?引入Getter

這裏我們可以假想一個場景,我們的頁面中現在顯示的數字是0,如果我們的需求是這樣的,頁面中數字如果小於10,就顯示爲00。那我們可以在剛剛的組件中將計算屬性返回的值修改成這樣:return this.$store.state.count > 9 ? this.$store.state.count : '0'+this.$store.state.count;就可以了,但是如果我們有很多組件都使用了這個count的話,那我們在每一個使用這個變量的地方都需要寫一遍這個判斷,那爲什麼不在取數據的時候就把數據整理成想要的樣子呢?就和我們組件中的計算屬性一樣!爲了達到目的,我們修改一下store.js:

import Vue from 'vue'
import Vuex from 'vuex'
import * as getters from './getters' //新增

Vue.use(Vuex)

// 應用初始狀態
const state = {
    count: 0,
}

// 創建 store 實例
export default new Vuex.Store({
    state,
    getters //新增
})

然後我們新建一個getters.js文件:

export const countaddzero = state => {
    return state.count > 9 ? state.count : '0' + state.count;
}

最後我們改一改組件中的計算屬性:

return this.$store.getters.countaddzero;

保存之後發現頁面上的數字果然變成了00。

4. 試試修改State,引入Mutation

我們的本意是要做一個點擊按鈕數字可以增加的一個小demo,現在把數據存到store是完成了。接下來說一下如何修改數據。
爲了方便查看,我們先修改一下store.js這個文件:

import Vue from 'vue'
import Vuex from 'vuex'
import * as getters from './getters'

Vue.use(Vuex)

// 應用初始狀態
const state = {
    count: 0,
}

// 定義所需的 mutations               //新增
const mutations = {                 //新增
    increment(state, val = 1) {     //新增
        state.count += val;         //新增
    },                              //新增
    decrement(state, val = 1) {     //新增
        state.count -= val;         //新增
    }                               //新增
}                                   //新增

// 創建 store 實例
export default new Vuex.Store({
    state,
    getters,
    mutations                       //新增
})

其中的val表示點擊按鈕每次需要增加(減少)多少。
有了mutations我們就可以在組件中對store裏面的state進行修改了,我們先處理一下組件中的點擊事件,當點擊加或者減按鈕的時候纔去修改store裏面的值。我們修改一下組件的methods:

methods:{
    increment(){
        this.$store.commit('increment',2); //新增
    },
    decrement(){
        this.$store.commit('decrement',2); //新增
    },
},

其中的2就是之前提到的每一次增加多少,也就是前文的val。我們約定只能通過commit的方式修改store的變量,爲什麼說是預定呢?手賤的我決定試試:

methods:{
    increment(){
        // this.$store.commit('increment',2); //修改
        this.$store.state.count=1000;         //新增
    },
},

其實這樣也可以修改store裏面的值,但是這樣就不能達到我們剛開始所說的目的了。
一條重要的原則就是要記住mutation必須是同步函數。因爲我們不知道什麼時候回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。

5. 異步修改?引入Action

那如果我們就想異步的修改store的值呢?也是有辦法的,這時候就需要我們的Action出場了:
Action 類似於 mutation,不同在於:

Action 提交的是 mutation,而不是直接變更狀態。
Action 可以包含任意異步操作。
我們還是先改改代碼,我們新建一個actions.js:

export const incrementAsync = ({ commit }, val = 1) => {
    setTimeout(() => {
        commit('increment', val)
    }, 1000)
}
export const decrementAsync = ({ commit }, val = 1) => {
    setTimeout(() => {
        commit('decrement', val)
    }, 1000)
}

這裏我們使用setTimeout模擬一下異步執行。接着修改我們的組件

<p> 
    異步:
    <button @click="increment2">+</button>
    <button @click="decrement2">-</button>
</p>

修改一下methods:

methods:{
    increment2(){
        this.$store.dispatch('incrementAsync',2);
    },
    decrement2(){
        this.$store.dispatch('decrementAsync',2);
    },
},

Action通過store.dispatch方法觸發,乍一眼看上去感覺多此一舉,我們直接分發mutation豈不更方便?實際上並非如此,還記得mutation必須同步執行這個限制麼?Action 就不受約束!我們可以在 action 內部執行異步操作。
最後修改一下store.js文件:
在頂部導入actions:import * as actions from './action'
然後在實例化的時候加入actions:

// 創建 store 實例
export default new Vuex.Store({
    actions,  //新增
    getters,
    state,
    mutations
})

這樣就可以實現異步修改store啦!

6. 不方便維護?引入Module

由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。
爲了解決以上問題,Vuex允許我們將store分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}
const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態

7. 一點點注意事項

當在嚴格模式中使用 Vuex 時,在屬於 Vuex 的 state 上使用 v-model 會比較棘手:

<input v-model="obj.message">

假設這裏的 obj 是在計算屬性中返回的一個屬於Vuex store的對象,在用戶輸入時,v-model會試圖直接修改obj.message。在嚴格模式中,由於這個修改不是在 mutation 函數中執行的, 這裏會拋出一個錯誤。
也就是說其實雙向數據綁定和vuex是會有一點衝突的,不過化解的方法也有:
第一種方法:
給 中綁定 value,然後偵聽input或者change事件,在事件回調中調用 action:

<input :value="message" @input="updateMessage">

也就是不實用v-model。
第二種方法:
雙向綁定的計算屬性

<input v-model="message">
computed: {
    message: {
        get () {
            return this.$store.state.obj.message
        },
        set (value) {
            this.$store.commit('updateMessage', value)
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章