超详细!Vuex手把手教程

作者: 鹏多多 | 来源:发表于2021-07-26 10:17 被阅读0次

    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
    

    如果看了觉得有帮助的,我是@鹏多多11997110103,欢迎 点赞 关注 评论;
    END

    往期文章

    个人主页

    相关文章

      网友评论

        本文标题:超详细!Vuex手把手教程

        本文链接:https://www.haomeiwen.com/subject/gxiwmltx.html