一直以来对vuex
不是很了解,抱着平时能不用就不用的心态,一直都没重视。但是无论是看工作中项目的代码,还是真的遇到组件之间复杂传值的情况,vuex
的使用技能还是要get的。
vuex
官网的解释:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
好抽象,硬着头皮跟着官网教程往下走,给了一个vue
的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| new Vue({ data() { return { count: 0 } }, template: <div>{{ count }}</div>, methods: { increment () { this.count++; } } })
|
这是对于单一组件的单项数据流,通过state
保存数据变量,view
视图展示数据,actions
可以通过事件触发改变数据从而改变视图内容,依次循环,这个被称为单项数据流概念
对于单个的组件,似乎单项数据流可以满足我们的需求,但是一旦当组件之间需要大量的通信,公用同一个data
中的数据,不同view
的methods
触发同一个data
,这个时候,如果通过父子组件传值之类的操作,就十分麻烦了。因为,vuex
也应运而生,可以当作是全局管理变量。
vuex
中的核心概念就是‘仓库’store
和我们一般的全局变量不一样的地方是,假如我们在main.js
中放置了一个全局变量,我只是可以在任何一个组件中方便的通过window.xxx
访问到这个变量,但是无法做到响应式更新,store
的两大特点就是:
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
纸上得来终觉浅,用一个小的demo
测试一下
第一步,项目中需要安装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;
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>
|
访问成功,但是这么获取数据是不是不太美观,我们可以把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:
简单粗暴,就把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
也是可以传递参数的,与之前类似,为了减少不必要的冗余的代码,muatation
也有一个好用的mapMutations
,用法也和之前的mapState
和mapGetters
,不再赘述。
但是,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 => item.done === true) } }, mutations: { increment (state) { setTimeout(() => { state.count++ }, 2000) } }, actions: { increment (context) { console.log(context) context.commit('increment') } } }) export default store;
|
addCount () {
this.$store.dispatch('increment')
};
触发actions
是通过store.dispatch
去实现的,触发顺序是:
addCount
➡️action(dispatch)
➡️mutation(commit)
看下效果:
简单理解vuex
就是这些内容,后续会补充(关于为啥mutation
只能是同步操作以及组合actions