有了Vue-router的基础之后,这一篇我们直奔主题,实现自己的Vuex。
安装VueX
第一步,使用Vuex之前必须先下载。
使用
vue add vuex
来安装Vuex,安装完成之后,项目的目录结构中会自动多出Vuex的文件夹,且在main.js中会多出对Vuex文件的引用。
VueX的简单使用
编辑store下的index.js文件夹如下所示。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter:0
},
getters:{
doubleCounter(state){
return state.counter*2;
}
},
mutations: {
add(state){
state.counter ++;
}
},
actions: {
add({commit}){
setTimeout(() => {
commit('add')
}, 1000);
}
},
modules: {
}
})
编辑View文件夹下的home组件如下所示
<template>
<div class="home">
<p @click="$store.commit('add')">counter:{{$store.state.counter}}</p>
<p @click="$store.dispatch('add')">async counter:{{$store.state.counter}}</p>
<p>double counter: {{$store.getters.doubleCounter}}</p>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'Home',
components: {
HelloWorld
}
}
</script>
如何实现vuex?
找到store文件夹下的index.js文件,注释掉代码对官方Vuex的引用,修改成对我们自己实现的Vuex的引用,并在同级目录下创建对应文件。
接下来思考,如何实现我们自定义的VueX?
移步到store的index.js代码页面
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './myVuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter:0
},
getters:{
doubleCounter(state){
return state.counter*2;
}
},
mutations: {
add(state){
state.counter ++;
}
},
actions: {
add({commit}){
setTimeout(() => {
commit('add')
}, 1000);
}
},
modules: {
}
})
可以看到第七行代码处,我们从myVuex文件中暴露出来的至少是一个含有Store属性的对象,且这个Store肯定是个构造函数,因为在第七行中可以看到使用这个构造函数进行了一个实例化操作。
new Vuex.Store({XXX})
而且,从vue-router的实现方法一文中得知,凡是插件肯定要实现一个install方法,所以至少还需要暴露一个install方法。
最后得出结论,myVuex暴露一个对象,里面至少包含了Store构造函数和install方法。思路清晰,直接开撸
let Vue = null;
class Store{
constructor(options){
// 响应化处理state
this.state = new Vue({
data(){
return options.state
}
})
}
}
function install(_Vue){
Vue = _Vue
Vue.mixin({
beforeCreate(){
if(this.$options.store){
Vue.prototype.$store = this.$options.store
}
}
})
}
export default {
Store,
install
}
没错,就是这么简单,我们将this.state设置成一个新的Vue实例的目的是使它变成响应式的,这样state中的值更新时,Vue会帮我们把所有用到这个变量的地方都更新一次。关于install方法就不再赘述了。当我们这样写完时,再次打开home页面后边可以直接看到成果啦!
tips。由于我们并没有实现getter方法,而在home组件中引用了,所以项目会报错,因此我们需要注释掉home组件中对VueX中home组件的引用。
此时再打开页面。
展示成功。
下一步自然是要实现Vuex的mutations,actions,getter等功能啦。
实现Vuex的mutation功能
由于是我们自己写的mutation,所以duck不必按照官方的写法来写,我们想怎么写就怎么写,只要实现了mutation的功能即可。
第一步,实现mutation之前得先了解,mutation有什么功能 ?
我们在Vuex中配置了mutation的方法之后,就可以在各个地方使用
this.$store.commit('mutation名')
来调用。
既然如此,那么肯定在store的构造函数中需要定义commit方法,它主要实现的功能就是触发mutation中对应的回调。
功能已经弄清楚,那么思路很清晰。
我们在store的构造函数中可以拿到options中的mutation对象,然后将这个对象的键值对保存到一个map中,再在commit中根据键名来触发对应的回调函数即可。store构造函数如下
class Store{
constructor(options){
// 响应化处理state
this.state = new Vue({
data(){
return options.state
}
})
// 将选项中的mutations保存下来
this._mutations = options.mutations;
}
commit(type,...args){
this._mutations[type](this.state,...args);
}
}
仅仅多了三四行代码,我们已经简单的实现了一个mutations的功能。页面表现如下
but!
commit可是能传递参数的!比如我们将mutation中add函数修改成如下形式
mutations: {
add(state,countStep){
if(countStep){
this.countStep += countStep;
}else{
state.counter ++;
}
}
},
同时,将home组件commit后加上参数。
很简单,我们将mutations中的add修改成如果我们传递了参数,那么state中的count就会加上我们传递的参数,否则,则执行自增操作。
将vuex插件换成官方版的看一下实际效果
可以看到,传递了参数之后的commit,每次都会增加5,而不是默认的1。而在我们自定义Vuex中实现这个功能也是相当简单的。
只需要改动两行即可。
改造完成,此时将官方VueX修改成我们自己实现的Vuex。
再看页面表现。
传递参数版commit,改造完成!
实现vuex的action功能
action实际就是触发mutation里面的方法,唯一差别就是在mutation里面执行异步函数devtools无法追踪到更改状态,所以只能在action里面执行异步函数。第二个需要注意的地方是,看一下官方action的示例。
它的参数不是像mutation一样的state,而是一个context上下文。
思路清晰,直接开撸。
等我兴高采烈的加上我无与伦比的代码打开我欲盖弥彰的网页准备查看结果时。
唔…
沉着冷静,仔细分析,定位到我们使用commit的代码行。
commit处于一个回调中,而setTimeout回调中直接调用commit方法直接导致
this的指向丢失了。
所以在store中我们需要固定一下commit和dispatch的指向。
奇怪的知识增加了!
在方法中绑定this指向!
dispatch功能实现完成!
同view-router,我们实现的仅仅只是一些最基本的功能。只是帮助我们更好的理解源码而已。