一直以来对vuex 不是很了解,抱着平时能不用就不用的心态,一直都没重视。但是无论是看工作中项目的代码,还是真的遇到组件之间复杂传值的情况,vuex的使用技能还是要get的。
vuex官网的解释:

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

好抽象,硬着头皮跟着官网教程往下走,给了一个vuedemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new Vue({
// state
data() {
return {
count: 0
}
},
// view
template: <div>{{ count }}</div>,
// actions
methods: {
increment () {
this.count++;
}
}
})

这是对于单一组件的单项数据流,通过state保存数据变量,view视图展示数据,actions可以通过事件触发改变数据从而改变视图内容,依次循环,这个被称为单项数据流概念
对于单个的组件,似乎单项数据流可以满足我们的需求,但是一旦当组件之间需要大量的通信,公用同一个data中的数据,不同viewmethods触发同一个data,这个时候,如果通过父子组件传值之类的操作,就十分麻烦了。因为,vuex也应运而生,可以当作是全局管理变量。

vuex中的核心概念就是‘仓库’store 和我们一般的全局变量不一样的地方是,假如我们在main.js中放置了一个全局变量,我只是可以在任何一个组件中方便的通过window.xxx访问到这个变量,但是无法做到响应式更新,store的两大特点就是:

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

纸上得来终觉浅,用一个小的demo测试一下
第一步,项目中需要安装vuex

1
npm install vuex

新建单独的store文件夹,新建index文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.store({
state: {
count: 0,
},
mutations: {
increment (state) {
state.count++
},
}
});
export default store;

然后在main.js中全局引入store,有个需要注意的地方⚠️因为我一开始写的是store.js所以若需引入这个模块则需要按照文件名引入,若是写的index.js文件则可以直接引入文件夹,默认读取index.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Vue from 'vue';
import App from './App';
import router from './router';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import Vuex from 'vuex';
import store from './store/store.js';
import axios from 'axios';
import vconsole from './utils/vconsole.js';
import mock from './mock.js';
Vue.use(ElementUI);
Vue.use(Vuex);
Vue.prototype.$http = axios;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
axios,
vconsole,
mock,
components: { App },
template: '<App />'
})

接下去,我们就可以全局引用这个仓库store了!
annouce.vue如何访问到store里的count呢?试一试用$store去访问数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div id="container">
vuex学习
<h1>{{$store.state.count}}</h1>
</div>
</template>
<script>
export default {
name: 'announce',
data() {
return {}
},
methods: {}
}
</script>

vuex1
访问成功,但是这么获取数据是不是不太美观,我们可以把state放在compute里,修改下上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div id="container">
vuex学习
<h1>{{$store.state.count}}</h1>
</div>
</template>
<script>
export default {
name: 'announce',
data() {
return {}
},
computed: {
count() {
return this.$store.state.count
}
}
methods: {}
}
</script>

效果是一样的就不贴图了,只是放在computed中更直观明显。
当我们一个组件中用到多个全局变量(即vuex中的多个变量),如果都把这些都定义为computed属性的话,会比较冗余,mapstate很好的解决了这个问题。
我们可以使用mapState 辅助函数帮助我们生成计算属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div id="container">
vuex学习
<h1>{{count}}</h1>
</div>
</template>
<script>
import {mapState} from 'vuex';

export default {
name: 'announce',
data() {
return {}
},
computed: mapState(['count'])
methods: {}
}
</script>

再次类比vue中的局部变量,对于平时需要计算属性去实现的功能,在vuex中又该如何实现呢?
类似的,vuex有个getter功能,很好的实现了全局变量的计算属性,和vue的计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。举个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0,
person: 'aiyaya',
todos: [
{ id: 1, text: 'aiyaya', done: true },
{ id: 2, text: 'eason', done: false }
]
},
getters: {
doneTotos: state => { return state.todos.filter(item => item.done === true) }
},
mutations: {
increment(state) { state.count++ }
}
})
export default store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template >      
<div id = "container" >
vuex学习
<h1 >{{count}}</h1 >
<h2 >{{person}}</h2 >
<p >{{$store.getters.doneTotos}}</p >
</div >
</template >
<script >
import { mapState } from 'vuex';
export default {
name: 'announce' ,
data () {
return {}
},
computed: mapState(['count' ,'person']),
methods: {}
}
</script >

效果ok:
getter
简单粗暴,就把geeters安排上了,然鹅,怎么更好的处理它呢?一个是直接放到computed计算属性中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 <template>    
<div id=>"container">
vuex学习
<h1>{{count}}</h1>
<h2>{{person}}</h2>
<p>{{todos}}</p>
</div>
</template>
<script>
export default {
name: 'announce',
data () {
return {}
},
computed: {
todos () {
return this.$store.getters.doneTotos
},
count () {
return this.$store.state.count
},
person () {
return this.$store.state.person
}
},
methods: { }
}
</script>

还有一种方式是用到了mapGetters,这个函数是将 store 中的 getter 映射到局部计算属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div id="container"> vuex学习 <h1>{{count}}</h1>
<h2>{{person}}</h2>
<p>{{doneTotos}}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'announce',
data() {
return {}
},
computed: {
count() {
return this.$store.state.count
},
...mapGetters(['doneTotos']),
person() {
return this.$store.state.person
}
},
methods: {}
}
</script>

这算是读取到了全局变量了,那我们要改变这个值呢?how?根据官方文档提示:

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
mutation就可以类比为vue中的methods,所谓显式提交就是指commmit,相当于事件的触发,在我们之前的demo中我们有定义了一个mutation

1
2
3
4
5
mutations: {
increment (state) {
state.count++
}
}

在组件中通过函数做一次显式提交触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>   
<div id="container">
vuex学习
<h1>{{count}}</h1>
<h2>{{person}}</h2>
<p>{{doneTotos}}</p>
<button @click="addCount">add</button>
</div>
</template>
<script>
import {mapGetters} from 'vuex';

export default {
name: 'announce',
data () {
return { }
},
computed: {
count () {
return this.$store.state.count
},
...mapGetters(['doneTotos']),
person () {
return this.$store.state.person
}
},
methods: {
addCount () {
this.$store.commit('increment')
}
}
}
</script>

效果如下:
mutation
和函数传参一样,显式提交mutation也是可以传递参数的,与之前类似,为了减少不必要的冗余的代码,muatation也有一个好用的mapMutations,用法也和之前的mapStatemapGetters,不再赘述。
但是,muatation有一个短板,它是不支持异步函数的处理的,emmmm…我不信,试一下:
我用了定时器测了一波,并没有报错的说…   
这个后面继续深究,继续探索vuex
为了支持异步操作的提交,我们需要在mutation外面包裹一层Action
官方文档关于Action的描述:

Action 提交的是 mutation,而不是直接变更状态。

Action 可以包含任意异步操作。

亲测一波:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0,
person: 'aiyaya',
todos: [
{id: 1, text: 'aiyaya', done: true},
{id: 2, text: 'eason', done: false}
]
},
getters: {
doneTotos: state => {
return state.todos.filter(item =&gt; item.done === true)
}
},
mutations: {
increment (state) {
setTimeout(() => { state.count++ }, 2000)
}
},
actions: {
increment (context) {
// 提交一个 mutation
console.log(context)
context.commit('increment')
}
}
})
export default store;
addCount () {
    this.$store.dispatch('increment')
};

触发actions是通过store.dispatch去实现的,触发顺序是:

addCount➡️action(dispatch)➡️mutation(commit)
看下效果:
actions
简单理解vuex就是这些内容,后续会补充(关于为啥mutation只能是同步操作以及组合actions