前言
我们都知道在用 vue 的时候,简单的父子通信和 EventBus 已经不能满足我们的要求了,嵌套层级过多和难以追踪改变是两个较为主要的问题😵,这个时候可以用 vuex 来解决,想必大家都用过,所以今天跟大家分享的是 vuex 的简单实现,真的是超简单,就几行代码(文章结尾有链接),带你领略 vuex 的精髓,并且在最后会有几个问题答疑(比如时光穿梭、本地持久化等)帮大家巩固一下 vuex。
前置知识
vuex 是基于 vue 的状态管理工具,通俗点讲讲就是变量共享,你要知道 this.$store
本质是个对象,大家可以看下下面这张图,看看 this.$store
到底是个啥(只看有标号的行即可)👇:
还记得我们是怎么使用 vuex 的么?
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 这是插件固定写法,没有什么为什么,官网写的很清楚,这样写 vue 会自动调用插件的 install 方法
const store = new Vuex.Store({
state: {...},
mutations: {...},
actions: {...},
getters: {...},
})
new Vue({
el: '#app',
store
})
很显然,Store 里面大致就传入四个参数,目前我们也只需要这些参数就够了,四个参数又可以分为两类,一类是获取数据,一类是修改数据,就像下面这张图画的一样👇:
我感觉上面这张图画的还挺不错😂,应该挺明了。组件和 store 是分开的,组件获取数据可以通过 $store.state
和 $store.getters
,组件要修改数据可以通过 $store.actions
和 $store.mutations
(当然最终都是 mutations,这也使得便于追踪状态的改变),如此一来,形成了闭环,也符合我们所说的单向数据流的思想,只有一个地方能改数据,不能遍地开花,不然就乱套了。另外 store 也是个单例模式的例子,所有的组件都共用一个全局的 store,每个组件的 $store
都是同一个东西,这点也很好理解。
再补充一句,很多人总是记不清 actions 和 mutations 到底哪个是异步哪个是同步,不妨试试这样记:actions 和 async 都是 a 开头的,所以 actions 是异步,或者 actions 是要调用 api 的,也都是 a 开头的,所以是异步😳。
简单实现
有了上面这些概念,接下来我们就简单用几行代码来简单实现一下吧。首先先写个简单的小框架,就像下面这样(官网上有说明插件的编写方式):
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.js"></script>
<script>
const Vuex = {}
Vuex.install = function (Vue) { // 这个 Vue 是 vue 提供给我们的
console.log('install 方法开始执行')
}
Vue.use(Vuex)
</script>
ok,然后我们在 install 里面写 vuex 的代码即可,现在我们只需要声明一个 store 对象并且赋予 store 两个属性(这里以 state 和 mutations 举例),这个我用很简单的代码演示一下大家就清楚了👇:
Vuex.install = function (Vue) {
console.log('install 方法开始执行')
const store = {} // 声明一个对象
store.state = new Vue({ // 赋予 state 属性,用来获取值
data () {
return {
msg: '哈哈'
}
}
})
store.mutations = { // 赋予 mutations 属性,用来修改值
SETMSG(value) {
store.state.msg = value
}
}
}
上面的代码中要注意的是 state 其实是利用了 vue 的响应式原理,使用了 new Vue()
,由此 state 就变成响应式的了,这是 vue 的特性。注意这里单纯的使用 Object.defineProperty
来定义 state 是不行的,是无法更新视图的,因为它并没有被 vue 进行依赖收集,所以说 vuex 是强依赖于 vue 的。还有就是一开始我们也要把 state 的数据写全,不然后面添加的也是无响应更新的,大家如果有用到过 $set
应该能体会到为什么有时候数据变了,视图没更新那种感觉😬。
👌,现在我们已经有了 store,接下来就是把它挂到每个组件下面了,这里我们使用 Vue.mixin 的混入方式,代码如下:
Vue.mixin({ // 也是固定写法:每个组件都会执行下面这个生命周期
beforeCreate () {
this.$store = store // 于是每个组件都会有 this.$store,并且都指向同一个 store
}
})
当然用 Vue.prototype.$store = store
也能达到同样的效果,事实上,vue-router 也是同样的方式我们才能在每个实例中用 this.$router 来调用。另外如果要说 mixin 和 prototype 这两种挂载方式的区别,我就想到两小点😯:
- mixin 是在 vue 实例上,prototype 是在原型上
- 把 store 挂载在实例上,就不用顺着原型链查找了,提升了一丢丢性能
不知道大家还知道其他原因吗,欢迎在下面留言。
好了,至此,我们大概就写完了一个简单的 demo,现在写个例子来测试一下:
let v1 = new Vue({
el: '#component1',
computed: {
data() {
return this.$store.state.msg
}
}
})
let v2 = new Vue({
el: '#component2',
methods: {
change() {
this.$store.mutations.SETMSG('这是 mutations 触发的值')
}
}
})
console.log(v1, v2)
下面是测试的结果: 当然我们平时用 mutations 是通过 commit 来写的,其实 commit 就是个函数,本质上也是调用 mutations,这里也顺手简单写下 commit👇:
Vuex.install = function (Vue) {
...
store.commit = function(mutationName, value) {
store.mutations[mutationName] && store.mutations[mutationName](value)
}
...
}
...
let v2 = new Vue({
el: '#component2',
methods: {
change() { // 改一下调用方式,结果是一样的
// this.$store.mutations.SETMSG('这是 mutations 触发的值')
this.$store.commit('SETMSG', '这是 mutations 触发的值')
}
}
})
最终效果是一样的,这里就不展示了。 深吸一口气,目前为止我们已经实现超简版的 vuex,接下来是几个问题答疑🤔。
问题答疑
为什么需要 getters
这个东西其实和我们平时写的计算属性一毛一样,state 和 getters 的关系好比 data 和 computed,大家细品一下。
如何区分 state 是不是通过 commit 修改的
我们知道 vuex 中修改 state 就一个通道,就是执行 commit,但其实你不通过 commit 也是能改的,那怎么知道它是通过 commit 修改的呢?就是在执行 commit 的时候加个标志位 _committing
,执行 commit 的时候将 _committing
设置为 true,_committing
为 true 才能修改 state,而其他方式修改的 state 并不会修改 _committing
标志位,这样一来就能判断是不是通过 commit 修改的。 如果你在 vuex 中打开了严格模式,任何非 mutation 更改都会抛出错误。
mapState 等辅助函数的实现
这一类辅助函数本质就是语法糖,这里我们以 mapState 举例子,我们回顾一下用法:
import {mapState} from 'vuex'
export default{
computed:{
...mapState(['msg','user'])
}
}
然后我们在页面中就能用 this.msg 访问,其实调用的还是 this.$store.state.msg,只不过写起来简单点。下面我们看下怎么简单实现,很显然,这里 mapState 是一个函数,接收一个数组:
function mapState (list) {
let obj = {}
list.forEach(stateName => {
obj[stateName] = () => this.$store.state[stateName]
})
return obj
}
👏是的,就这么点代码,其他 map 辅助函数也是也一样的道理。
本地持久化
- 我们可以在每次 commit 的时候把 state 保存到 localStorage 或者 sessionStorage 中,然后页面初始化的时候,先读取本地存储的 state 值,不过要注意频繁存储的问题。
- 我们可以利用 beforeunload 这个事件,在页面卸载之前再把 state 的值存起来,这样效率也挺高的。
时光穿梭
这是 devtoolPlugin 提供的功能,因为开发的时候所有 state 的改变都有记录,“时光穿梭”的功能其实就是能够让我们回到或去到某一状态,实际上就是将当前的 state 替换为记录中的某个 state,我们看下 vuex 的 store 中就为我们提供了一个这样的函数 replaceState(真的是直接替换😂),具体代码如下👇:
replaceState (state) {
this._withCommit(() => { // 这里面就是上面说到的 _commiting 标识
this._vm.state = state
})
}
下面是两个个小截图,希望能够帮助你理解:
小结
如果硬要说 vuex 中最核心的一个点的话,那就是利用了 vue 的响应式,我想这是最为主要的。最后希望本篇文章能够对你有所帮助,不知道写的清不清楚😁,也祝大家百毒不侵,开开心心上班,回见👋。