超詳細!Vuex手把手教程

1,前言


最近在重溫vue全家桶,再看一遍感覺記憶更深刻,所以專門記錄一下(本文vuex版本為v3.x)。

2,Vuex 是什么


Vuex是專為Vue.js開發的狀態管理模式。它采用集中式存儲,管理所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化(我的理解就是全局變量)。

3,5大屬性說明


state

對象類型,類似于實例的 data屬性,存放數據

getters

對象類型,類似于實例的計算屬性 computed

mutations

對象類型,類似于實例的 methods,但是不能處理異步方法

actions

對象類型,類似于實例的 methods,可以處理異步方法

modules

對象類型,當state內容比較多時,通過該屬性分割成小模塊,每個模塊都擁有自己的 state、mutation、action、getter

4,state


存儲在state中的數據和Vue實例中的data遵循相同的規則,必須是純粹的對象。

4.1 直接訪問

this.$store.state.xxx

4.1 使用mapState映射

<template>
    <div id="communication">
        <p>計數:{{ getCount }}</p>
        <p>學校:{{ getSchool('我是參數') }}</p>
    </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {
            date: 1998
        }
    },
    computed: {
        ...mapState({
            // mapState默認會把state當第一個參數傳進來
            getCount: state => state.count,
            getSchool(state) {
                return (val) => {
                    return state.school + val + this.date
                }
            }
        })
    },
    mounted() {
        // 直接取值
        console.log(this.$store.state.count)
    }
}
</script>

5,getters


getter的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算,并且默認接受state作為其第一個參數,也可以接受其他getter作為第二個參數(如下例)

5.1 先在vuex中定義getters

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清華大學'
    },
    getters: {
        // 返回處理后的state值
        getValue(state) {
            return state.count + '!'
        },
        // 返回調用自身getters處理后的state值
        getGetters(state, getters) {
            return state.school + getters.getValue
        },
        // 接受外部傳參后處理的值(在通過方法訪問時,每次都會去進行調用,而不會緩存結果)
        getParam(state) {
            return (param) => {
                return state.school + param
            }
        }
    },
    mutations: {},
    actions: {},
    modules: {}
})

5.2 直接獲取值

// 取值
console.log(this.$store.getters.getGetters)
// 傳參取值
console.log(this.$store.getters.getParam('param'))

5.3 使用mapGetters映射

<template>
    <div id="communication">
        <p>計數:{{ getGetters }}</p>
        <p>學校:{{ getParam(date) }}</p>
    </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {
            date: 1998
        }
    },
    computed: {
        ...mapGetters([
            'getGetters',
            'getParam'
        ])
    },
    mounted() {
        // 直接取值
        console.log(this.$store.getters.getGetters)
        console.log(this.getParam(this.date))
    }
}
</script>

6,Mutation


通過調用this.$store.commit('xxx'),調用mutation中的方法,更改store中的值

6.1,先在mutations中注冊事件

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清華大學'
    },
    getters: {},
    mutations: {
        // 默認state作為第一個參數
        handleAdd(state) {
            state.count++
        },
        // 接受傳參
        handleChange(state, value) {
            state.school = value
        }
    },
    actions: {},
    modules: {}
})

6.2,在組件中調用方法commit修改值

<template>
    <div id="communication">
        <p>計數:{{ count }}</p>
        <el-button @click="handleStoreAdd">增加</el-button>
        <el-button @click="handleStoreChange">傳參</el-button>
    </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {
            school: '武漢大學'
        }
    },
    computed: {
        ...mapState([
            'count'
        ])
    },
    methods: {
        // 調用修改
        handleStoreAdd() {
            this.$store.commit('handleAdd')
        },
        // 傳遞參數修改
        handleStoreChange() {
            this.$store.commit('handleChange', this.school)
        }
    }
}
</script>

6.3,使用常量定義方法名

新建文件mutation-types.js,定義方法名的常量,并導出

export const ADD_COUNT = 'ADD_COUNT'
export const CHANGE = 'CHANGE'

在store中

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清華大學'
    },
    getters: {},
    mutations: {
        // 默認state作為第一個參數
        [MT.ADD_COUNT](state) {
            state.count++
        },
        // 接受傳參
        [MT.CHANGE](state, value) {
            state.school = value
        }
    },
    actions: {},
    modules: {}
})

在組件中

<template>
    <div id="communication">
        <p>計數:{{ count }}</p>
        <el-button @click="handleStoreAdd">增加</el-button>
        <el-button @click="handleStoreChange">傳參</el-button>
    </div>
</template>

<script>
import { mapState } from 'vuex'
import * as MT from '../../store/mutation-types'
export default {
    name: 'Vuex',
    data() {
        return {
            school: '武漢大學'
        }
    },
    computed: {
        ...mapState([
            'count'
        ])
    },
    methods: {
        // 調用修改
        handleStoreAdd() {
            this.$store.commit(MT.ADD_COUNT)
        },
        // 傳遞參數修改
        handleStoreChange() {
            this.$store.commit(MT.CHANGE, this.school)
        }
    }
}
</script>

6.4,使用mapMutations映射

<template>
    <div id="communication">
        <p>計數:{{ count }}</p>
        <p>計數:{{ school }}</p>
        <el-button @click="handleStoreAdd">增加</el-button>
        <el-button @click="handleStoreChange(schools)">傳參</el-button>
    </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
    name: 'Vuex',
    data() {
        return {
            schools: '武漢大學'
        }
    },
    computed: {
        ...mapState([
            'count',
            'school'
        ])
    },
    methods: {
        ...mapMutations({
            handleStoreAdd: MT.ADD_COUNT,
            handleStoreChange: MT.CHANGE
        })
    }
}
</script>

7,Action


注意,Action提交的是mutation,而不是直接變更狀態,并且可以包含任意異步操作

7.1,在store中定義

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清華大學'
    },
    getters: {},
    mutations: {
        // 默認state作為第一個參數
        [MT.ADD_COUNT](state) {
            state.count++
        },
        // 接受傳參
        [MT.CHANGE](state, value) {
            state.school = value
        }
    },
    actions: {
        add(context) {
            context.commit(MT.ADD_COUNT)
        }
    },
    modules: {}
})

7.2,在組件中使用

<template>
    <div id="communication">
        <p>計數:{{ count }}</p>
        <el-button @click="actionAdd">增加</el-button>
    </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
    name: 'Vuex',
    data() {
        return {
            schools: '武漢大學'
        }
    },
    computed: {
        ...mapState([
            'count',
            'school'
        ])
    },
    methods: {
        ...mapMutations({
            handleStoreAdd: MT.ADD_COUNT,
            handleStoreChange: MT.CHANGE
        }),
        // 調用action的方法,需要使用$store.dispatch
        actionAdd() {
            this.$store.dispatch('add')
        }
    }
}
</script>

7.3,使用mapActions映射

import { mapActions } from 'vuex'

methods: {
    ...mapActions([
        'moduleFn'
    ])
}

或者

import { mapActions } from 'vuex'

methods: {
    ...mapActions([
        fn: 'moduleFn'
    ])
}

7.4,簡化寫法

Action接受一個與store實例具有相同方法和屬性的context參數對象,因此你可以調用context.commit提交一個mutation,或者通過context.statecontext.getters來獲取stategetters,利用ES6的解構,可以簡化寫法。

actions: {
  add({ commit, state }) {
    commit(MT.CHANGE, state.school)
  }
}

7.5,執行異步操作

在vuex中

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    getters: {},
    mutations: {
        // 默認state作為第一個參數
        [MT.ADD_COUNT](state) {
            state.count++
        }
    },
    actions: {
        add({ commit }) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    commit(MT.ADD_COUNT)
                    resolve()
                }, 1000)
            })
        }
    },
    modules: {}
})

在組件中使用async / await或者then / catch處理異步

<template>
    <div id="communication">
        <p>計數:{{ count }}</p>
        <el-button @click="actionAdd">增加</el-button>
    </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
    name: 'Vuex',
    data() {
        return {
            schools: '武漢大學'
        }
    },
    computed: {
        ...mapState([
            'count',
            'school'
        ])
    },
    methods: {
        ...mapMutations({
            handleStoreAdd: MT.ADD_COUNT,
            handleStoreChange: MT.CHANGE
        }),
        // 調用action的方法,需要使用$store.dispatch
        async actionAdd() {
            await this.$store.dispatch('add')
            console.log(1998)
        }
    }
}
</script>

8,Modules


當應用變得非常復雜時,store對象就可能變得相當臃腫。這時候可以將store分割成模塊,每個模塊擁有自己的statemutationactiongetter、甚至是嵌套子模塊,從上至下進行同樣方式的分割。

8.1,準備工作

在store目錄下新建Modules文件夾,在Modules文件夾中新建modulesA.jsmodulesB.js,如下圖

目錄.png

在modulesA.js中寫上局部模塊的statemutationactiongetter,并導出

const moduleA = {
    state: () => ({
        a: '我是moduleA'
    }),
    getters: {},
    mutations: {},
    actions: {}
}

export default moduleA

然后在storeindex.js中引入,并丟進modules對象里

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    getters: {},
    mutations: {},
    actions: {},
    modules: {
        moduleA,
        moduleB
    }
})

8.2,使用modules中注入的模塊的state

在組件中直接使用

this.$store.state.moduleA.xxx

在組件中使用mapState映射

<span>{{ moduleA.xxx }}</span>

import { mapState } from 'vuex'

computed: {
    ...mapState([
        'moduleA'
    ])
}

8.3,使用modules中注入模塊的getters

在組件中直接使用

this.$store.getters.getModuleA

在組件中使用mapState映射

<p>{{ getModuleA }}</p>

import { mapGetters } from 'vuex'

computed: {
    ...mapGetters([
        'getModuleA'
    ])
}

模塊內部的getter,接受的參數stategetters是模塊的局部狀態對象,而根節點的狀態會作為第三個參數rootState暴露出來

const moduleA = {
    getters: {
        getModuleA(state, getters, rootState) {
            return state.xxx + '---' + rootState.xxx
        }
    }
}

如果需要帶參數

const moduleA = {
    getters: {
        getModuleA(state, getters, rootState) {
            return (value) => {
                return state.a + '---' + value
            }
        }
    }
}

8.4,使用modules中注入模塊的mutations

在組件中直接使用

this.$store.commit('setModuleA') || this.$store.commit('setModuleA', '參數')

在組件中使用mapMutations映射

import { mapMutations } from 'vuex'

methods: {
    ...mapMutations([
        openFn: 'setModuleA'
    ])
}

模塊內部的mutations,默認接受的第一個參數state是模塊的局部狀態對象

const moduleA = {
    mutations: {
        setModuleA(state) {
            state.xxx += 'xxx'
        }
    }
}

如果需要帶參數

const moduleA = {
    mutations: {
        setModuleA(state, value) {
            state.xxx += value
        }
    }
}

8.5,使用modules中注入模塊的actions

在組件中直接使用

this.$store.dispatch('xxx')

在組件中使用mapActions映射

import { mapActions } from 'vuex'

methods: {
    ...mapActions([
        'moduleA'
    ])
}

或者重命名

import { mapActions } from 'vuex'

methods: {
    ...mapActions({
        fn: 'moduleA'
    })
}

對于模塊內部的action,局部狀態通過context.state暴露出來,根節點狀態則為context.rootState

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

8.6,命名空間

默認情況下,模塊內部的actionmutationgetter是注冊在全局命名空間的,這樣使得多個模塊能夠對同一mutationaction作出響應。如果希望模塊具有更高的封裝度和復用性,可以通過給模塊添加namespaced: true的方式使其成為帶命名空間的模塊。當模塊被注冊后,它的所有getteractionmutation都會自動根據模塊注冊的路徑調整命名。

8.6.1,使用

先在模塊moduleB.js中添加namespaced: true

const moduleB = {
    namespaced: true,
    state: () => ({
        b: '我是moduleB'
    }),
    mutations: {},
    actions: {},
    getters: {}
}

export default moduleB

storeindex.js

import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'

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

如果在組件中使用命名空間,需要帶上空間名稱,mapState, mapGetters, mapMutationsmapActions用法一樣。

<script>
import { mapState, mapGetters, mapMutations } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {}
    },
    computed: {
        // 此處注入的是moduleA模塊的數據
        ...mapState('moduleA', [
            'a'
        ]),
        // 需要注入moduleB模塊,就再寫一個
        ...mapState('moduleB', [
            'b'
        ])
    },
    mounted() {
        // 直接使用
        console.log(this.$store.state.moduleA.a)
        console.log(this.$store.state.moduleB.b)
    },
    methods: {}
}
</script>

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

如果你希望使用全局的stategetterrootStaterootGetters會作為第三和第四參數傳入getter,也會通過context對象的屬性傳入action。若需要在全局命名空間內分發action或提交mutation,將{ root: true }作為第三參數傳給dispatchcommit即可

const moduleA = {
    namespaced: true,
    state: () => ({
        a: '我是moduleA'
    }),
    getters: {
        getModuleA(state, getters, rootState, rootGetters) {
            // 使用全局命名空間的state或getters
            return state.a + rootState.count
        }
    },
    mutations: {
        setModuleA(state) {
            console.log(state.a)
        }
    },
    actions: {
        addM({ state, commit, dispatch, rootState, rootGetters }) {
            console.log(rootState)
            console.log(rootGetters)
            // 調用全局命名空間的方法
            dispatch('rootFunction', null, { root: true })
        }
    }
}

export default moduleA

8.6.3,在帶命名空間的模塊注冊全局action

在帶命名空間的模塊注冊全局action,需要添加root: true,并將這個action的定義放在函數handler中,其中,handler的第一個參數namespacedContext就是action中的Context參數

const moduleA = {
    namespaced: true,
    state: () => ({
        a: '我是moduleA'
    }),
    getters: {},
    mutations: {},
    actions: {
        rootFn: {
            root: true,
            handler(namespacedContext, param) {
                console.log(namespacedContext.state)
            }
        }
    }
}

export default moduleA

本次分享就到這兒啦,我是@鵬多多,如果您看了覺得有幫助,歡迎評論,關注,點贊,轉發,我們下次見~

往期文章

個人主頁

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容