stay hungry stay foolish

vuex浅析

Vuex

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

以上是官方对 Vuex 的定义,需要注意的时第二点(改变store中的状态的唯一途径时显示的提交commit),这只是该工具推荐的使用原则,但是 Vuex 并没有采取任何的措施去强制开发者必须这么做。之所以要求开发者这么做只是出于框架的核心概念(也是所有状态管理工具的核心原则),只有这么做才能方便状态的追踪管理,否则就失去了状态管理框架的意义。

基本使用:

<!DOCTYPE html>
<html>

<head>
    <title>vuex-demo</title>
    <meta charset="utf-8">
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js"></script>
</head>

<body>
    <div id="app">
        {{$store.state.count}},{{count}}
    </div>
    <script type="text/javascript">
    var store = new Vuex.Store({
        state: {
            count: 1
        },
        mutations: {
            add: (state, num) => {
                state.count += num;
                console.log('count add ', num);
            }
        }
    })
    var vue = new Vue({
        el: '#app',
        store: store,
        computed: Vuex.mapState(['count']) //属性映射
    })

    console.log(vue.$store === store); //true,vue实例的$store属性是store对象的一个引用

    /*store本质和vue组件的data对象一样,是一个响应式的数据对象,只不过store是全局的,
    虽然可以直接操作store中的状态,但不到万不得已,千万不要这么做,这样会失去对状态的跟踪*/
    store.state.count++; 

    /*正确的使用方法时调用mutation去改变状态*/
    store.commit('add', 2); //调用mutation里面的方法
    </script>
</body>

</html>

结果:

getters

getters属性主要用来过滤一些数据,如果不需要过滤,直接使用state访问更加方便。

<body>
    <div id="app">
        <div>{{$store.getters.getCountAddOne}}</div>
        <div>{{getCountAddOne}}</div>
    </div>
    <script type="text/javascript">
    var store = new Vuex.Store({
        state: {
            count: 1
        },
        getters: {
        	getCountAddOne(state){
        		return state.count+1
        	}
        }
    })
    var vue = new Vue({
        el: '#app',
        store: store,
        computed: Vuex.mapGetters(['getCountAddOne']) //将store中的getter映射到局部计算属性
    })
    console.log(Vuex.mapGetters(['getCountAddOne']))
    </script>
</body>

结果:

Mutation

mutation用来更改状态,官方规定mutation方法必须时同步方法,因为对于每一条mutation被记录,devtools 都需要捕捉到前一状态和后一状态的快照这样当状态改变后,这样就可以轻松的使用devtools查看状态的变化了。

<body>
    <div id="app">
        <div>{{count}}</div>
    </div>
    <script type="text/javascript">
    var store = new Vuex.Store({
        state: {
            count: 1
        },
        mutations: {
        	add(state){
        		state.count++;
        	},
        	del(state,num){ //可以接受参数
        		if(typeof num == 'object'){
        			state.count-=num.num;
        		}else{
	        		state.count-=num;
        		}
        	}
        }
    })

    var vue = new Vue({
        el: '#app',
        store: store,
        computed: Vuex.mapState(['count']),
        methods: Vuex.mapMutations(['add']) //映射mutation方法
    })

    vue.add(); //使用映射后的方法
    console.log(vue.count); //2

    store.commit('del',2);
    console.log(vue.count); //0

    store.commit({ //可以使用对象风格去调用mutation
    	type: 'del',
    	num: 2
    });
    console.log(vue.count); //-2
    </script>
</body>

Action

action类似于mutation,不同在于:

  • action提交的是mutation,而不是直接变更状态
  • action可以包含任意异步操作

事实上在 vuex 里面actions只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发mutation就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有mutation必须是同步的这一点(在redux里面就好像reducer必须同步返回下一个状态一样)。

<body>
    <div id="app">
        <div>{{count}}</div>
    </div>
    <script type="text/javascript">
    var store = new Vuex.Store({
        state: {
            count: 1
        },
        mutations: {
        	add(state,num){
        		state.count+=num;
        	},
        	del(state,num){
        		state.count-=num;
        	}
        },
        actions: {
        	add(store,num){
        		return new Promise((resolve)=>{
        			setTimeout(function(){
	        			store.commit('add',num);
	        			resolve();
	        		},1000);
        		})
        	},
        	del(state,num){
        		setTimeout(function(){
        			store.commit('del',num);
        			console.log(vue.count); //2
        		},500)
        	}
        }
    })

    var vue = new Vue({
        el: '#app',
        store: store,
        computed: Vuex.mapState(['count']),
        methods: Vuex.mapActions(['add']) //映射action方法
    })

    vue.add(2).then(function(){
    	store.dispatch('del',1);
    	console.log(vue.count); //3
    });

    console.log(vue.count); //1
    </script>
</body>

Module

如果项目很大,可以把状态模块化,不同模块保存不同的状态信息,实例化store时通过modules属性引用不同的模块。默认情况下,所有模块的mutationgettersactions都挂载在store实例下。

基本使用

<body>
    <div id="app">
        {{$store.state.modA.a}},{{$store.state.modB.b}}
    </div>
    <script type="text/javascript">
    var modA = {
        state: {
            a: 1
        },
        mutations: {
            add: (state, num) => { //这里的state代表的是modA的局部state
                state.a += num;
                console.log('a add ', num);
            }
        },
        actions: {
            add: (context) => { //对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象
                console.log(context.state.a,context.state.b) //2 undefined
                context.commit('add',context.rootState.num); //这里还是会调用所有模块名为add的mutation方法,rootState代表根状态对象
            }
        }
    }
    var modB = {
        state: {
            b: 1
        },
        mutations: {
            add: (state, num) => {
                state.b += num;
                console.log('b add ', num);
            }
        }
    }
    var store = new Vuex.Store({
        state: {
            num: 10
        },
        modules: {
            modA: modA,
            modB: modB
        }
    })
    var vue = new Vue({
        el: '#app',
        store: store
    })
    store.commit('add',1) //在没有命名空间的情况下,会调用所有名为add的mutation方法
    console.log(store.state.modA.a); //2
    store.dispatch('add') //在没有命名空间的情况下,会调用所有名为add的action方法
    console.log(store.state.modA.a); //12
    </script>
</body>

结果:

命名空间

通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。

示例:

<body>
    <div id="app">
        {{$store.state.modA.a}},{{$store.state.modB.b}}
    </div>
    <script type="text/javascript">
    var modA = {
        namespaced: true, //有了命名空间后,该模块下的所有mutation,getters,actions都将局部化
        state: {
            a: 1
        },
        mutations: {
            add: (state, num) => {
                state.a += num;
                console.log('a add ', num);
            }
        },
        actions: {
            add: (context) => {
                console.log('modA add action');
                context.commit('add',context.rootState.num); //有了命名空间后,这里只会调用本modA下的mutation

                console.log("context.commit('add',context.rootState.num,{root:true})(在命名空间模块里调用全局mutation)");
                //通过加上第三个参数{root:true},这里会调用除了命名模块之外所有名字为add的mutation
                context.commit('add',context.rootState.num,{root:true});

                console.log("context.dispatch('add',null,{root:true})(在命名空间模块里分发全局action)");
                //通过加上第三个参数{root:true},这里会调用除了命名模块之外所有名字为add的action
                context.dispatch('add',null,{root:true});
            }
        }
    }
    var modB = {
        state: {
            b: 1
        },
        mutations: {
            add: (state, num) => {
                state.b += num;
                console.log('b add ', num);
            }
        },
        actions: {
            add: (context) => {
                console.log('modB add action');
                context.commit('add',context.rootState.num); //这里会调用除了命名模块之外所有名字为add的mutation
            }
        }
    }
    var modC = {
        state: {
            c: 1
        },
        mutations: {
            add: (state, num) => {
                state.c += num;
                console.log('c add ', num);
            }
        },
        actions: {
            add: (context) => { //
                console.log('modC add action');
                context.commit('add',context.rootState.num); //这里会调用除了命名模块之外所有名字为add的mutation
            }
        }
    }
    var store = new Vuex.Store({
        state: {
            num: 10
        },
        modules: {
            modA: modA,
            modB: modB,
            modC: modC
        }
    })
    var vue = new Vue({
        el: '#app',
        store: store
    })

    console.log("store.commit('add',1)")

    store.commit('add',1) //这里会调用除了命名模块之外所有名字为add的mutation

    console.log("store.commit('modA/add',1)")

    store.commit('modA/add',1) //调用modA中名为add的mutation

    console.log("store.dispatch('add')")

    store.dispatch('add') //这里会调用除了命名模块之外所有名字为add的action
    
    console.log("store.dispatch('modA/add')")

    store.dispatch('modA/add') //调用modA中名为add的action

    </script>
</body>

结果: