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
属性引用不同的模块。默认情况下,所有模块的mutation
,getters
,actions
都挂载在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
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter
、action
及 mutation
都会自动根据模块注册的路径调整命名。
示例:
<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>
结果: