本文将讲解,如何手动实现一个mini版的vuex,(本文中我们称为KStore)
我们的KStore要实现的功能:
- state
- commit
- dispatch
- getters
环境搭建
这一步主要做的就是:不使用官方的vuex库,而使用我们自己的。
-
创建项目
vue create myapp npm run serve -
新建
src/KStore/index.js、src/KStore/vuex.js文件//src/KStore/index.js文件 import Vue from "vue"; import Vuex from "./vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: { counter: 1, }, getters: { doubleCounter(state) { return state.counter * 2; }, }, mutations: { add(state) { state.counter += 1; }, }, actions: { add({ commit }) { commit("add"); }, }, modules: {}, }); // src/KStore/vuex.js文件 class Store { // 2.实现state // 3.实现commit // 4.实现dispatch // 5.实现getters } function install() { // 1.实现install方法 } export default { Store, install, }; -
修改
main.js文件import store from "./store"; // 修改为 import store from "./KStore"; -
home.vue中添加对vuex的使用,方便我们测试<template> <div class="home"> <p style="font-size: 30px"> state:{{ $store.state.counter }} <br /> getters:{{ $store.getters.doubleCounter }} <br /> <button @click="$store.commit('add')">Mutation +</button> <button @click="$store.dispatch('add')">Actions +</button> </p> </div> </template> -
看下浏览器,报错正常,接下来我们就实现我们的vuex
install方法
install方法,是每个插件必备的方法也是格式,主要是给vue的原型拓展方法,我们这个也不例外。
代码变成如下这样:
let Vue;
class Store {}
function install(_Vue) {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
// if判断是因为我们只在根实例里面传入了store,混入的话每个组件都会执行,所以我们要加if判断
if (this.$options.store) {
Vue.prototype.$store = this.$options.store;
}
},
});
}
export default {
Store,
install,
};
看下此时的浏览器,$store已经可以访问到了。
state
用法
this.$store.state.xxx
实现思路
通过this.$options接收用户传入的参数,响应式的方式通过new Vue(),因为用户不能直接修改state里面的值,只能通过commit方法来修改state,所以用get、set访问器设置访问拦截。
在上一篇文章:手写mini版的vue-router,中我们用了一种响应式方法,今天我们来用另一种
new Vue()的方式,俗称借鸡生蛋。哈哈哈哈!!!
注意事项
1.说一下这边为什么要用$$state?
是因为如果你data中的变量是以
$$开头的,那么Vue就不会去代理这个变量,这个机制是Vue内部的机制,记住就可以。
代码实现
let Vue;
class Store {
constructor(options) {
this.$options = options;
this._vm = new Vue({
data() {
return {
$$state: options.state,
};
},
});
}
// 实现state
get state() {
return this._vm._data.$$state;
}
set state(val) {
console.log("不能修改state值");
return;
}
}
function install(_Vue) {
...
}
export default {
Store,
install,
};
因为我们现在getters还没实现,所以要把Home.vue文件对getters的引用注释掉,否则看不到效果,会报错!!
commit
用法
this.$store.commit("add",1);
实现思路
首先我们通过commit方法中传递的参数,确定执行mutation[event]方法,再把this.state当作参数传入fn中,这就是我们实现commit的心路历程。
代码实现
let Vue;
class Store {
constructor(options) {
this.$options = options;
this._vm = new Vue({
data() {
return {
$$state: options.state,
};
},
});
}
// 实现state
get state() {
return this._vm._data.$$state;
}
set state(val) {
console.log("只能通过commit修改state的值");
return;
}
// 实现commit方法
commit(event, payload) {
const fn = this.$options.mutations[event]; //找到对应mutations里面的函数
if (!fn) {
console.error("没有此mutation方法");
return;
} //判断有无此方法
fn(this.state, payload); //传入state值,执行此方法
}
}
function install(_Vue) {
...
}
export default {
Store,
install,
};
-
成果演示
点击mutation按钮,数值就会加一。如此,完美实现commit,是不是很简单!
dispatch
用法
// actions中声明
{
actions:{
add({commit},payload){
commit("add");
}
}
}
// 页面中调用
this.$store.dispatch("add",1);
实现思路
dispatch实现思路和commit的实现思路一样,都是通过传入的参数event,从actions里面确定执行哪个函数。
注意事项
dispatch中要调用commit,所以我们需要注意commit的执行上下文环境,通俗一点说就是this的指向
代码实现
let Vue;
class Store {
constructor(options) {
this.$options = options;
this._vm = new Vue({
data() {
return {
$$state: options.state,
};
},
});
}
get state() {
return this._vm._data.$$state;
}
set state(val) {
console.log("只能通过commit修改state的值");
return;
}
commit(event, payload) {
...
}
// 实现dispatch方法
dispatch(event, payload) {
const fn = this.$options.actions[event];//找到对应actions里面的函数
if (!fn) {
console.error("没有此mutation方法");
return;
}
fn(this, payload);
}
}
function install(_Vue) {
...
}
export default {
Store,
install,
};
如果按照以上代码实现的话,将会报如下错误:
解决办法,自然是通过call、apply、bind指定this指向,此处显然用bind更合适。
let Vue;
class Store {
constructor(options) {
this.$options = options;
this._vm = new Vue({
data() {
return {
$$state: options.state,
};
},
});
this.commit = this.commit.bind(this);// 重要代码,绑定this指向。
}
...
}
function install(_Vue) {
...
}
export default {
Store,
install,
};
-
成果演示
getters
用法
this.$store.getters.xxx
实现思路
getters是只读属性,我们可以利用闭包保存对函数的引用,同时设置computed
代码实现
let Vue;
class Store {
constructor(options) {
this.$options = options;
// 显式绑定this指向(dispatch中调用commit,commit的指向是undefined)
this.commit = this.commit.bind(this);
// 实现getters
this.getters = {};
let computed = {};
var store = this;
Object.keys(this.$options.getters).forEach((key) => {
const fn = this.$options.getters[key];
computed[key] = function () {
return fn(store.state);
};
Object.defineProperty(this.getters, key, {
get() {
return store._vm[key];//computed里面的值,会被代理到实例上,所以直接访问_vm就可以
},
});
});
this._vm = new Vue({
data() {
return {
$$state: options.state,
};
},
computed,
});
}
get state() {
return this._vm._data.$$state;
}
set state(val) {
console.log("只能通过commit修改state的值");
return;
}
commit(event, payload) {
...
}
dispatch(event, payload) {
...
}
}
function install(_Vue) {
...
}
export default {
Store,
install,
};
总结
上一篇写了实现一个mini版的vue-router,这一篇实现了一个mini版的vuex,其实都是为了方便以后回顾。也就顺手写的详细一点,跟大家分享一下,不是很会写文章,望见谅!
- 最后贴上完整代码(直接可以运行)
let Vue;
class Store {
constructor(options) {
// 接收用户传入的参数
this.$options = options;
// 显式绑定this指向(dispatch中调用commit,commit的指向是undefined)
this.commit = this.commit.bind(this);
// 实现getters
this.getters = {};
let computed = {};
var store = this;
Object.keys(this.$options.getters).forEach((key) => {
const fn = this.$options.getters[key];
computed[key] = function () {
return fn(store.state);
};
Object.defineProperty(this.getters, key, {
get() {
return store._vm[key];
},
});
});
// 响应式处理
this._vm = new Vue({
data() {
return {
$$state: options.state,
};
},
computed,
});
}
// 实现state
get state() {
return this._vm._data.$$state;
}
set state(val) {
console.log("只能通过commit修改state的值");
return;
}
// 实现commit
commit(event, payload) {
const fn = this.$options.mutations[event]; //找到对应mutations里面的函数
if (!fn) {
console.error("没有此mutation方法");
return;
} //判断有无此方法
fn(this.state, payload); //传入state值,执行此方法
}
// 实现dispatch
dispatch(event, payload) {
const fn = this.$options.actions[event];
if (!fn) {
console.error("没有此mutation方法");
return;
}
fn(this, payload);
}
}
function install(_Vue) {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
// if判断是因为我们只在根实例里面传入了store
if (this.$options.store) {
Vue.prototype.$store = this.$options.store;
}
},
});
}
export default {
Store,
install,
};