前言
我们知道,在 Vue 中父子组件传值是最常用的知识点之一,项目中功能组件和服务组件的封装都会有各种各样的数据传递,用 props 定义字段或者是子组件 emit 来通信。但是,当我们的项目的复杂度逐渐增长的时候,组件会越来越多,而且一些组件并不存在调用关系,一些数据需要共享的时候,那么问题就来了:
- 传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力;
- 采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝,代码冗余,慢慢的难以维护。
所以我们在项目中经常是全局定义一个实例“全局单例模式”,当然你也可以定义多个实例来共享一个数据源,我们来看看官方图好了,来的比较直接一些:
我们可以看到,下方的两个组件,如果不使用 Vuex,或许就需要我们定义两个组件来维护两份同一个接口返回的数据;但是两个组件是各取所需使用返回的个别字段,不仅增加了请求,还不能同步。如果用 Vuex 来打理这份数据,共享同一个 store 数据池,当 store 中的数据改变的时候,两个组件中的数据状态都会第一时间同步,所有 store 中 state 的改变,都放置在 store 自身的 action 中去管理。
这种集中式状态管理能够更容易地理解哪种类型的 Mutation 将会发生,以及它们是如何被触发。就是说,action 做了一个触发 Mutation 的动作,分发 dispatch 事件去通知 store 要改变数据了!!!总之,Vuex 大家都理解为一种各种状态的集中管理中心就好了。
准备
在这里大家需要安装好 Vuex 。
Vue 中我们推行极简的单向数据流,但是就像前面我们说的,当我们多个视图组件依赖于同一状态、来自不同视图组件的行为需要变更同一状态的时候,就会很头疼,经常组件之间来回复制粘贴,如果组件树能有一个被统一管理的数据池来获取数据和触发行为,何乐而不为呢?
需要注意的地方:
- 一个 Vuex 核心是 store,相当于一个池塘,而你的很多 state 可以当作水,store 包含着 state。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 我们不能直接改变 store 中的state状态。我们应执行 action 来分发(dispatch)事件通知 store 去改变,从而显式地提交 (commit)记录变更(mutation)。
- Vuex 依赖 ES6 的 promise,大家注意浏览器的兼容
挂载实例、文件准备
我们在 vue-cli 脚手架搭建的项目中,因为是全局单例模式,所以我们先创建一个 store 文件夹,用来放我们的相关文件,我是按照模块来划分数据池,大家可以根据自己的需要,写到一起也是可以的,来看看文件目录结构,这就是刚刚新建的目录,我们在模块中有一个狗狗的模块,来集中管理全局关于狗狗的数据:
我们接着定义 index.js,其实很简单,就是引入 Vuex,这里的 vue.use(Vuex),将状态从根组件“注入”到每一个子组件中,然后 new 一个 store 推出去:
然后我们需要在 main.js 中引用一下,像 router 一样。
我们来看看浏览器中的 store 现在是什么状态,随便添加一个测试文本,因为我们的狗狗模块暂时是空的。
可以看到我们已经成功的有一个 store 了!在组件内,可以使用 this.$store.state.testText 访问到,或许计算属性是一个不错的选择呢~
恭喜你!完成了第一步,我们已经定义好了我们的数据池,还挂载到了我们全局实例上面,另外定义了一个狗狗模块,引入到了 store,接下来就让我们开始 dog 模块的编写吧!
dog 狗狗模块
我们可以看到下图中,我们定义了 type 类型,还有一个默认的 state 对象,里面是名字、年纪、好朋友。
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 Mutation。Vuex 中的 Mutation 非常类似于事件:每个 Mutation 都有一个字符串的 事件类型(type)和 一个 回调函数(handler)。
这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。就像是事件注册:“当触发一个类型为 increment 的 Mutation 时,调用此函数”。
要唤醒一个 Mutation 的回调,我们需要以相应的 type 调用 store.commit 方法。
比如我们改变一下狗狗的名字,可以看到,我们用类型 DOG 定义了一个事件,参数也是一目了然,我们可以看到第一个参数是 state,第二个参数是新的名字,在 Mutations 中,我们叫做载荷(Payload):
通常情况下,Payload 是一个对象,这样属性可读性会比单独的好很多,所以我们修改一下:
敲黑板!敲黑板!
- Mutation 函数必须是同步函数,因为我们前面说了,Mutation 其实是一个追踪记录,当 Mutation 触发的时候,回调函数还没有被调用,如果我们把异步请求放到这里,就会导致我们没办法实时的追踪状态,任何回调函数中的状态都是不可被追踪记录的
- Mutation 需遵守 Vue 的响应式规则(不知道的可以自己去官网看一下,这里简单的说一句)
- 最好提前在你的 store 中初始化好所有所需属性,就像我们在组件中的 data 一样,我们在改变的时候,最好先定义一下
- 因为 Vue 的响应规则,如果不是定于过的,就需要 Vue.set(obj, 'dog', 'rrrr')
在组件中使用 Mutations
第一步,定义注册,export dog 模块里面包含 state 还有 Mutations,当然,后面的 action 我们也可以这样:
后面的使用也是很简单的,注意我截图中的细节。
第二步,组件中调用
直接使用 this.$store.commit('xxx')
提交 Mutation:
使用辅助函数 mapMutations:
第三步,页面可以做显示更新数据,最后我们可以在辅助工具里面看到结果:
可以很明显的看到我们的 name 改变了两次。
到这里相信大家都知道我们的 Mutatons 是怎么做的了,或许刚开始概念性的东西很绕,但是至少我们会用了,不是么!大家可以尝试着改变一下 age 来体验一下啦。
那么一个问题来了,既然 Mutation 只能是同步函数,那我异步请求的该怎么办呢?别急~~~我们接着走起。
action
action 其实和 Mutation 很像的,不论是定义的方式还是调用时候的辅助函数等,最大的亮点不同官网也说了:Action 提交的是 Mutation,而不是直接变更状态,Action 可以包含任意异步操作。
我们先来定义一个 actions,因为是异步操作,所以我会模拟 1 个 http 请求来假装异步了。哈哈哈,不过还是可以让大家感受到这是真的一个请求!!!
第一步,定义
与 Mutation 不同的是,action 的第一个参数接收一个上下文对象 context,起承上启下的作用,而 context 的属性和方法,是和 store 一模一样的,但是它并不是 store 对象,具体可以查看官网解释,context 中我们常用的就是 commit,来提交一个 Mutation,就像 redux 中的一样,action 只是发起了一个动作,然后我们用定时器模拟异步操作。
第二步,组件中分发使用
与我们的 Mutation 在组件中使用的时候,是通过 this.$store.commit('....')
来使用的类似,我们 action 在组件中使用的时候,是执行了分发的动作,具体来看看,或许直接说的话,不然咋对分发这个词不怎么很喜欢呢?
等待 2 秒之后,发现 name 改变,响应的 Mutation 也改变了,这是因为我们的 action 发起了一个动作,Mutaion 是动作的执行者,毕竟,大家都看到,我可没有在 action 做小动作来改变 name,所有改变 name 的动作都给了 Mutation,可不能冤枉 action。
发现没!!!!是不是和 Mutation 调用差不多呢,只不过我们的 Mutation 是同步的,是 commit,而我们的 action 是异步,我们可以在任何时候去调用,如果你切换及时的 2 秒后你就会看到你的 name 变成了 action 中定义的那个默认值咯,当然,你也可以传参进去新的值,覆盖默认的值,我们用辅助函数看看。
注意:辅助函数是需要 import 提钱引进来的;调用的时候不用写 dispatch,已经集成了,dispatch 本身返回的就是一个 promise,所以可以在 then 里面做一点事情~~~
是不是很惊喜的发现,辅助函数也是可以的呢,不过我咋感觉直接调用和通过辅助函数的时间不一样呢?或许大家可以测试一下哦~看看结果~~这个 name 是辅助函数传参进去的,取代了 action 中定义的默认值。
是不是发现,action 也会了呢~~~来让我们看一个稍微完整版的 action(带模拟请求):
当然,更多的复杂异步操作小伙伴们也可以结合各种新语法来实现,比如 async/await、以及 ES6 的解构赋值我想大家都应该知道,可以试着把 action 中的 context 用解构赋值替换一下啊~~~
getter
有时候,我们需要从 store 中派生出一些状态,比如狗狗的名字进行一个过滤,大于 2 岁的分组,小于 18 岁的分组等等,这个时候就需要我们对 store 来派生一个 getters,就像官方所说,你可以理解为,getters 是 store 的一个计算属性,话不多说,我们直接定义:
看到我们定义的一个过滤了么,判断名字是不是等于狗狗,然后在不同的生命周期去获取,可能会有很大的用处呢~~~
当然调用的时候也是 so easy 啦。
看!getters 就这么简单。
总结:
其实如果大家理解了这个状态管理的模式,就会发现,本身的调用并不是问题,甚至可以说很简单,而难处理的还是我们普通的数据,就像 getters 的这个过滤,定义、调用,没什么可以说的甚至,但是过滤的过程呢?文中只是判断了一下简单的相等,如果异步请求/同步更新中发生改变,页面一个列表数组的各种操作结合起来,多个组件共享一个数据中心的时候,或许才能感受到 Vuex 的强大之处。