第十六节:Vuex4.x 简介及state、getters、mutations、actions详解(OptionApi 和 CompositionApi)

一. Vuex简介

1. 简介

 (官网地址:https://next.vuex.vuejs.org/zh/index.html     在Vue2中使用的详见:https://www.cnblogs.com/yaopengfei/p/14571316.html        本节采用的版本号【4.0.2】)

 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的store模式(或者父子组件传值、缓存等)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

2. 核心总结

 在Vuex中,有五大核心模块,分别是state、getters、mutations、actions、module,下图是每个模块的作用,以及在OptionApi 和 Composition Api中对应的 $store 、辅助函数的两种写法。

3. 快速入门 

(1). 在store文件夹中创建index.js文件

import { createStore } from 'vuex'
// 导入相关常量
import { ADD_N } from './mutation-type'

export default createStore({
    state() {
        return {
            counter1: 100,
            name: 'ypf',
            age: 18,
            height: '1.82m'
        }
    },
    getters: {
        // 第一个参数是固定参数state, 用来获取上面state中的对象,
        ageInfo(state) {
            return `我的年龄是${state.age}`;
        },
        //可以返回一个函数,来实现传参 
        // 外界传过来的值这里用msg接收,这里的getters主要是用来获取其它getter属性的
        nameInfo(state, getters) {
            return function(msg) {
                return `我的名字是${state.name},${msg},${getters.ageInfo}`;
            }
        },
    },
    mutations: {
        increment(state) {
            state.counter1++;
        },
        decrement(state) {
            state.counter1--
        },
        /* 
         参数说明:
         state:用来获取上面state中的数据
         payLoad:用于接收外界传递过来的数据
         */
        // payLoad可以是int类型,表示递增数
        incrementN(state, payLoad) {
            state.counter1 += payLoad;
        },
        // payLoad可以是对象,比如 {myStep:20,name:'ypf'}
        decrementN(state, payLoad) {
            state.counter1 -= payLoad.myStep;
        },
        // payLoad可以是int类型,表示递增数
        [ADD_N](state, payLoad) {
            state.counter1 += payLoad;
        }
    },
    actions: {
        /* 
        两个参数:
        (1). context:是一个和$store实例均有相同方法和属性的对象,可以从其中获取到commit方法来提交一个mutation,
               或者通过 context.state 和 context.getters 来获取 state 和 getters
               可以解构为:{ commit, dispatch, state, rootState, getters, rootGetters }
        (2). payLoad:外界传递过来的数据,可以是各种类型
         */
        incrementAction(context, payLoad) {
            setTimeout(() => {
                context.commit('increment');
            }, 1000);
        },
        // payLoad可以是一个对象  {myStep:20,myStep2:30}
        decrementNAction({ commit, dispatch, state, rootState, getters, rootGetters }, payLoad) {
            if (payLoad.myStep) {
                commit('decrementN', payLoad);
            } else {
                commit('decrement');
            }
        },
        // 通过promise实现捕获异常的结束
        testAdd(context) {
            return new Promise((resolve, reject) => {
                try {
                    context.commit('increment');
                    resolve('执行结束啦')
                } catch (e) {
                    reject('出错了')
                }
            });
        }


    },
    modules: {}
})
View Code

(2). 在入口文件main.js中进行引入 

import { createApp } from 'vue'

// 需要测试不同页面
import App from './pages/09_action_optionApi/App.vue'

// 引入Vuex插件
import store from './store'

createApp(App).use(store).mount('#app')
View Code

 

二. state详解

1. 作用

 State 提供唯一的公共数据源,所有共享的数据都要统一放到 State 中进行存储。

export default createStore({
    state() {
        return {
            counter1: 100,
            name: 'ypf',
            age: 18,
            height: '1.82m'
        }
    },
})

2. Options Api中用法

写法1: $store对象

<h4>counter1:{{$store.state.counter1}}</h4>
<h4>name:{{$store.state.name}}</h4>

写法2:辅助函数mapState

 参数有两种:①数组类型,原名映射   ②对象类型,自定义名

<template>
    <div>
        <!-- 2. mapState函数的用法 -->
        <p>2. mapState函数的用法</p>
        <h4>counter1:{{counter1}}</h4>
        <h4>name:{{name}}</h4>
        <h4>myCounter1:{{myCounter1}}</h4>
        <h4>myName:{{myName}}</h4>
    </div>
</template>
<script>
    import { mapState } from 'vuex'
    export default {
        computed: {
            // 1. 写法1-原名映射(数组类型)
            ...mapState(['counter1', 'name']),
            // 2. 写法2-自定义名(对象类型)
            ...mapState({
                myCounter1: state => state.counter1,
                myName: state => state.name
            })
        }
    }
</script>

3. Composition Api中用法 

 写法1: 通过useStore创建store对象

<template>
    <div>
        <!-- 1. 通过useStore拿到store后去获取某个状态即可 -->
        <h4>myCounter1:{{myCounter1}}</h4>
        <h4>myName:{{myName}}</h4>
    </div>
</template>

<script>
    import { mapState, useStore } from 'vuex';
    import { computed } from 'vue';

    export default {
        setup() {
            // 方案1-通过useStore拿到store后去获取某个状态即可
            const store = useStore();
            const myCounter1 = computed(() => store.state.counter1);
            const myName = computed(() => store.state.name);
            return {
                myCounter1,
                myName
            }
        }
    }
</script>

写法2: 自己封装useState方法  【推荐!】

useState.js代码 

import { useStore, mapState } from 'vuex'
import {computed} from 'vue'

/* 
   封装在setup中获取state中的值
   ①:params: 需要获取的参数名, 可以是数组,也可以是对象
   分别对应两种调用方式
 */
export function useState(params) {
    const store = useStore();
    const storeStateFns = mapState(params);
    const storeState={};
    Object.keys(storeStateFns).forEach(fnKey=>{
        const fn = storeStateFns[fnKey].bind({$store:store});
        storeState[fnKey]=computed(fn);
    })    
    return storeState;
}

使用代码

<template>
    <div>
        <!-- 调用封装后代码 -->
        <h4>counter1:{{counter1}}</h4>
        <h4>name:{{name}}</h4>
        
        <h4>myCounter1:{{myCounter1}}</h4>
        <h4>myName:{{myName}}</h4>
    </div>
</template>
<script>
    // 导入封装
    import { useState } from '../../hooks/version1/useState'

    export default {
        setup() {
            // 写法1:传递数组
            const result1 = useState(['counter1', 'name']);

            // 写法2:传递对象
            const result2 = useState({
                myCounter1: state => state.counter1,
                myName: state => state.name
            });
            return {
                ...result1,
                ...result2
            }
        }
    }
</script>

 

三. getters详解

1. 作用

 getters用于对 State 中的数据进行加工处理形成新的数据

 ① getters 可以对 State 中已有的数据加工处理之后形成新的数据,类似 Vue 的Computed。

 ② State 中数据发生变化,getters 的数据也会跟着变化。

注意:getters中声明的是方法;

           方法的第一个参数为state,可以获取State中属性。可以返回一个函数,来实现传参。

export default createStore({
    state() {
        return {
            counter1: 100,
            name: 'ypf',
            age: 18,
            height: '1.82m'
        }
    },
    getters: {
        // 第一个参数是固定参数state, 用来获取上面state中的对象,
        ageInfo(state) {
            return `我的年龄是${state.age}`;
        },
        //可以返回一个函数,来实现传参 
        // 外界传过来的值这里用msg接收,这里的getters主要是用来获取其它getter属性的
        nameInfo(state, getters) {
            return function(msg) {
                return `我的名字是${state.name},${msg},${getters.ageInfo}`;
            }
        },
    },
})

2. Options Api中用法

写法1: $store对象

<h4>{{$store.getters.ageInfo}}</h4>
<h4>{{$store.getters.nameInfo('哈哈哈') }}</h4>

写法2:辅助函数mapState

 参数有两种:①数组类型,原名映射   ②对象类型,自定义名

<template>
    <div>
        <!-- 2.辅助函数调用 -->
        <h4>{{ageInfo}}</h4>
        <h4>{{nameInfo('呵呵呵')}}</h4>
        <h4>{{myAgeInfo}}</h4>
        <h4>{{myNameInfo('嘿嘿嘿')}}</h4>
    </div>
</template>

<script>
    import { mapGetters } from 'vuex';
    export default {
        computed: {
            // 辅助函数调用 
            // 写法1-传递数组(原名映射)
            ...mapGetters(['ageInfo', 'nameInfo']),
            // 写法2-传递对象(可以自定义名称)
            ...mapGetters({
                myAgeInfo: 'ageInfo',
                myNameInfo: 'nameInfo'
            })
        }
    }
</script>

3. Composition Api中用法 

写法1: 通过useStore拿到store后去获取某个getters即可(不推荐,繁琐)

<template>
    <div>
        <!-- 1.通过useStore拿到store后去获取某个getters即可-->
        <h4>{{ageInfo2}}</h4>
        <h4>{{nameInfo2('嘿嘿嘿嘿1')}}</h4>
    </div>
</template>

<script>
// 原始写法的引入
    import { computed } from 'vue';
    import { useStore } from 'vuex';

    export default {
        setup() {
            // 1. 通过useStore拿到store后去获取某个getters即可(不推荐)
            var store = useStore();
            const ageInfo2 = computed(() => store.getters.ageInfo);
            const nameInfo2 =computed(()=>store.getters.nameInfo);
return { ageInfo2, nameInfo2, } } } </script>

写法2: 自己封装useGetters方法

useGetters.js封装

import { useStore, mapGetters } from 'vuex'
import {computed} from 'vue'

/* 
   封装在setup中获取getters中的值
   ①:params: 需要获取的参数名, 可以是数组,也可以是对象
   分别对应两种调用方式
 */
export function useGetters(params) {
    const store = useStore();
    const storeStateFns = mapGetters(params);
    const storeState={};
    Object.keys(storeStateFns).forEach(fnKey=>{
        const fn = storeStateFns[fnKey].bind({$store:store});
        storeState[fnKey]=computed(fn);
    })    
    return storeState;
}

调用 

<template>
    <div>
        <!-- 2.调用自己的封装 -->
        <h4>{{ageInfo}}</h4>
        <h4>{{nameInfo('哈哈哈哈1')}}</h4>
        <h4>{{myAgeInfo}}</h4>
        <h4>{{myNameInfo('哈哈哈哈2')}}</h4>
    </div>
</template>

<script>
    // 自己封装的引入
    import { useGetters } from '../../hooks/version1/useGetters.js';
    export default {
        setup() {// 2. 自己封装(推荐)
            // 调用1-传递数组类型 
            var result1 = useGetters(['ageInfo', 'nameInfo']);
            // 调用2-传递对象类型
            var result2 = useGetters({
                myAgeInfo: 'ageInfo',
                myNameInfo: 'nameInfo'
            });
            return {
                ageInfo2,
                nameInfo2,
                ...result1,
                ...result2
            }

        }
    }
</script>

补充state和getters抽离共同点代码后,统一封装:

useMapper.js

import { useStore } from 'vuex'
import {computed} from 'vue'

/* 
   抽离useState和useGetters中的通用逻辑
   ①:params: 需要获取的参数名, 可以是数组,也可以是对象
   分别对应两种调用方式
   ②:fn:可以是mapGetters 或 mapState
 */
export function useMapper(params,fn) {
    const store = useStore();
    const storeStateFns = fn(params);
    const storeState={};
    Object.keys(storeStateFns).forEach(fnKey=>{
        const fn = storeStateFns[fnKey].bind({$store:store});
        storeState[fnKey]=computed(fn);
    })    
    return storeState;
}
View Code

useState.js

import { useStore, mapState } from 'vuex'
import { useMapper } from './useMapper'

/* 
   封装在setup中获取state中的值
   ①:params: 需要获取的参数名, 可以是数组,也可以是对象
   分别对应两种调用方式
 */
export function useState(params) {
    return useMapper(params, mapState);
}
View Code

useGetters.js

import { useStore, mapGetters } from 'vuex'
import { useMapper } from './useMapper'

/* 
   封装在setup中获取getters中的值
   ①:params: 需要获取的参数名, 可以是数组,也可以是对象
   分别对应两种调用方式
 */
export function useGetters(params) {
    return useMapper(params, mapGetters);
}
View Code

  

四. mutations详解

1. 作用

mutations 用于变更State中 的数据。

 ① 只能通过 mutation 变更 State中 数据(actions也需要context.commit来间接的修改state),不可以直接操作 Store 中的数据。(禁止 通过 this.$store.state.count 获取后直接修改)

 ② 通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化

注:mutations中声明方法,方法的第一个参数为state,可以获取State中属性,第二个参数可以自定义类型,进行传递。

mutation-type.js

// 自定义常量
export const ADD_N = 'add_n';
import { createStore } from 'vuex'
// 导入相关常量
import { ADD_N } from './mutation-type'

export default createStore({
    state() {
        return {
            counter1: 100,
        }
    },
    mutations: {
        increment(state) {
            state.counter1++;
        },
        decrement(state) {
            state.counter1--
        },
        /* 
         参数说明:
         state:用来获取上面state中的数据
         payLoad:用于接收外界传递过来的数据
         */
        // payLoad可以是int类型,表示递增数
        incrementN(state, payLoad) {
            state.counter1 += payLoad;
        },
        // payLoad可以是对象,比如 {myStep:20,name:'ypf'}
        decrementN(state, payLoad) {
            state.counter1 -= payLoad.myStep;
        },
        // payLoad可以是int类型,表示递增数
        [ADD_N](state, payLoad) {
            state.counter1 += payLoad;
        }
    },
})

2. Options Api中用法

写法1: $store对象

        <h4><button @click="$store.commit('increment')">加1</button></h4>
        <h4><button @click="$store.commit('incrementN',20)">加N</button></h4>

写法2:辅助函数mapMutations

  参数有两种:①数组类型,原名映射   ②对象类型,自定义名

<template>
    <div>
        <h4>counter:{{$store.state.counter1}}</h4>
        <p>2.mapMutations用法</p>
        <h4><button @click="decrement">减1</button></h4>
        <h4><button @click="decrementN({myStep:100})">减N</button></h4>
        <!-- 自定义名称 -->
        <h4><button @click="myIncrement">加1</button></h4>
        <h4><button @click="myIncrementN(200)">加N</button></h4>
    </div>
</template>

<script>
    import { mapMutations } from 'vuex';
    export default {
        methods: {
            // 辅助函数
            // 写法1-传递数组
            ...mapMutations(['decrement', 'decrementN']),
            // 写法2-传递对象
            ...mapMutations({
                myIncrement: 'increment',
                myIncrementN: 'incrementN'
            }),
    }
</script>

补充:可以在自定义的方法你进行$store对象的调用、$store对象的特有对象调用、调用mapMutations中的方法

  export default {
        methods: {// 当然也可以自己封装个方法
            test1() {
                // this.$store.commit('increment');
                //
                // this.myIncrementN(2);

                // 补充一个参数为对象类型的时候的特有写法
                // type属性中写方法名,其它属性则为传递的对象属性哦
                // this.$store.commit({
                //     type: 'decrementN',
                //     myStep: 50,
                //     name:'ypf'
                // })

                // 补充使用自定义常量
                this.$store.commit(ADD_N, 1000);
            }
        },
    }

3. Composition Api中用法 

写法1:

   通过useStore创建store对象,这里的store对象就是OptionApi中的$store,然后使用commit方法进行调用。

写法2.

   直接调用mapMutations辅助函数,然后进行返回即可。

<template>
    <div>
        <h4>counter:{{$store.state.counter1}}</h4>

        <h4><button @click="decrement">减1</button></h4>
        <h4><button @click="decrementN({myStep:100})">减N</button></h4>

        <h4><button @click="myIncrement">加1</button></h4>
        <h4><button @click="myIncrementN(200)">加N</button></h4>

        <h4><button @click="add_n(200)">加N</button></h4>
    </div>
</template>

<script>
    import { mapMutations } from 'vuex';
    // 导入自定义常量
    import { ADD_N } from '../../store/mutation-type.js'
    export default {
        setup() {
            // setup中调用辅助函数mapMutations容易,直接调用即可,不用再自己封装了
            const result1 = mapMutations(['decrement', 'decrementN']);
            const result2 = mapMutations({
                myIncrement: 'increment',
                myIncrementN: 'incrementN'
            });
            // 使用自定义常量的方式
            const result3 = mapMutations([ADD_N]);
            return {
                ...result1,
                ...result2,
                ...result3
            }
        }
    }
</script>

 

五. actions详解

1. 作用

  如果通过异步操作变更数据,必须通过 actions,而不能使用 mutations,但是在 action 中还是要通过触发Mutation 的方式间接变更数据

注:actions中的方法有两个参数 

(1). context:是一个和$store实例均有相同方法和属性的对象,可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters, 可以解构为:{ commit, dispatch, state, rootState, getters, rootGetters }
(2). payLoad:外界传递过来的数据,可以是各种类型

import { createStore } from 'vuex'
export default createStore({
    actions: {
        /* 
        两个参数:
        (1). context:是一个和$store实例均有相同方法和属性的对象,可以从其中获取到commit方法来提交一个mutation,
               或者通过 context.state 和 context.getters 来获取 state 和 getters
               可以解构为:{ commit, dispatch, state, rootState, getters, rootGetters }
        (2). payLoad:外界传递过来的数据,可以是各种类型
         */
        incrementAction(context, payLoad) {
            setTimeout(() => {
                context.commit('increment');
            }, 1000);
        },
        // payLoad可以是一个对象  {myStep:20,myStep2:30}
        decrementNAction({ commit, dispatch, state, rootState, getters, rootGetters }, payLoad) {
            if (payLoad.myStep) {
                commit('decrementN', payLoad);
            } else {
                commit('decrement');
            }
        },
        // 通过promise实现捕获异常的结束
        testAdd(context) {
            return new Promise((resolve, reject) => {
                try {
                    context.commit('increment');
                    resolve('执行结束啦')
                } catch (e) {
                    reject('出错了')
                }
            });
        }
    },
})

2. Options Api中用法

写法1. $store对象

        <h4><button @click="$store.dispatch('incrementAction')">加1(延迟1s)</button></h4>
        <h4><button @click="$store.dispatch('decrementNAction',{myStep:100})">减N</button></h4>

写法2. 辅助函数mapActions

<template>
    <div>
        <h4>counter:{{$store.state.counter1}}</h4>
        <p>2.mapActions用法</p>
        <h4><button @click="incrementAction">加1(延迟1s)</button></h4>
        <h4><button @click="decrementNAction({myStep:100})">减N</button></h4>
        <!-- 自定义名称 -->
        <h4><button @click="myincrementAction">加1(延迟1s)</button></h4>
        <h4><button @click="mydecrementNAction({myStep:100})">减N</button></h4>

        <p>3.在自己封装的方法中调用</p>
        <button @click="test1">test1</button>
    </div>
</template>

<script>
    import { mapActions } from 'vuex';

    export default {
        methods: {
            // 辅助函数用法
            // 1. 用法1-传递数组
            ...mapActions(["incrementAction", "decrementNAction"]),
            // 2. 用法2-传对象(自定义名称)
            ...mapActions({
                myincrementAction: "incrementAction",
                mydecrementNAction: "decrementNAction"
            }),
            test1() {
                // 1.$store也可以在自己定义的方法中使用哦
                // this.$store.dispatch('incrementAction');

                //2. 补充$store对象的特殊调用方式
                this.$store.dispatch({
                    type: 'decrementNAction'
                });

                //
                // this.$store.dispatch({
                //     type: 'decrementNAction',
                //     myStep:101
                // });

            }
        },
        data() {
            return {};
        }
    }
</script>

 补充:可以在自定义的方法你进行$store对象的调用、$store对象的特有对象调用、调用mapActions中的方法

3. Composition Api中用法 

写法1:

  通过useStore创建store对象,然后调用dispatch方法

<template>
    <div>
        <h3>{{$store.state.counter1}}</h3>
        <p>1. 通过useStore拿到store后,然后调用dispatch进行分发</p>
        <h4><button @click="tADD">加1(延迟1s)</button></h4>    
    </div>
</template>

<script>
    import { useStore } from 'vuex';

    export default {
        setup() {
            // 方案1 通过useStore拿到store后,然后调用dispatch进行分发
            const store = useStore();
            const tADD = () => {
                store.dispatch('incrementAction');
            };
                        return {
                tADD,
            }
        }
    }
</script>

<style scoped>
</style>

写法2:

 调用辅助函数mapActions,直接返回即可

<template>
    <div>
        <h3>{{$store.state.counter1}}</h3>
        <p>2.mapActions用法</p>
        <h4><button @click="incrementAction">加1(延迟1s)</button></h4>
        <h4><button @click="decrementNAction({myStep:100})">减N</button></h4>
        <!-- 自定义名称 -->
        <h4><button @click="myincrementAction">加1(延迟1s)</button></h4>
        <h4><button @click="mydecrementNAction({myStep:100})">减N</button></h4>
        
        <p>3. 测试捕获异步的结束</p>
        <h4><button @click="tTest">测试捕获异常结束</button></h4>
        
    </div>
</template>

<script>
    import { mapActions, useStore } from 'vuex';

    export default {
        setup() {//方案二:使用辅助函数 (推荐!! 简洁)
            // 在setup中直接使用辅助函数即可,不需自己再封装了
            const actions1 = mapActions(["incrementAction", "decrementNAction"]);
            // (自定义名称)
            const actions2 = mapActions({
                myincrementAction: "incrementAction",
                mydecrementNAction: "decrementNAction"
            });
            
            // 测试捕获异步的结束
            const tTest=()=>{
                store.dispatch('testAdd').then(res=>{
                    console.log(`获取异步结束后的返回信息:${res}`);
                });
            };
                         
            return {
                ...actions1,
                ...actions2,
                tTest
            }
        }
    }
</script>

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2021-10-22 15:36  Yaopengfei  阅读(1253)  评论(1编辑  收藏  举报