实现一个vuex
设计理念:
- 集中式管理
- 状态可预测
之所以的可预测的:是因为这个就是一个单向数据流,不管是直接进程commit还是通过异步的action来进行修改,最后都要通过commit给mutation然后再对state里面的数据进行变化的,所以我们就可以在这个commit的过程中,实现一些 拦截、监听器、中间件,这样就可以预测状态的改变监听状态的改变了
这个就是状态可预测的原因了
之所以把state修改了,组件中使用了这个vuex里面的数据也会改变是因为,把state里面的数据声明为了响应式的,所以通过mutation修改state之后,数据的依赖就会重新执行render方法,这样就可以进行修改state然后依赖也会改变了,这样就可以得到一个虚拟DOM,然后就和真实DOM进行diff比较,再进行DOM树修改即可重新进行渲染了
我们先把vuex整合进来
vue add vuex
之后在项目目录中就会出现一个 store/index.js 文件了
可以看到
Vue.use(Vuex)
所以就猜测和vue-router一样
在Vue.use里面是进行了一个this.$store挂载操作
就可以通过 this.$store.state.xxx 来访问到下面通过new Vuex.store({}) 新建的vuex实例了
需要做的:
- state声明为响应式数据
- commit和mutations中的方法有关
- dispatch和actions中的方法有关
其实还有一个getter
另外一个疑问点:
mutations: {
add(state) {
}
},
mutation中方法的第一个参数,state是怎么来的?
下面是一个基本的使用:
<template>
<div id="app">
<h1>{{$store.state.counter}}</h1>
<button @click="$store.commit('add')">加1</button>
<button @click="$store.dispatch('add')">async 加1</button>
</div>
</template>
import Vue from 'vue'
import Vuex from 'vuex'
// 挂载this.$state
Vue.use(Vuex)
export default new Vuex.Store({
// 通过this.$store.state.xxx 访问
state: {
counter: 0
},
mutations: {
// 这里的state从哪里来的
add(state) {
return state.counter++;
}
},
actions: {
// 参数是什么?哪里来的
add({commit}) {
setTimeout(() => {
commit("add");
}, 1000);
}
},
modules: {
}
})
其中的代码思路就是:
- 实现一个store类
- 并且把给类实例挂载到this.$store上
如果是模仿vue-router,那么下面就是Store的一个基本写法:
但是我们可以看到在store/index.js引入的时候
import Vue from 'vue'
import Vuex from 'vuex'
// 挂载this.$state
Vue.use(Vuex)
export default new Vuex.Store({})
是通过了一个叫做Vuex的
所以要改成
这样的话,当通过Vue.use(Vuex)的时候,是可以通过Vuex.install来执行install方法的,也可以通过new Vuex.Store来new一个对应的对象
第一件事就是:挂载$store
然后在class Store里面把,拿到的options里面的state数据转化成是响应式数据
我们在vue-router中使用的而是 Vue.util.defineReactive()这个方法的
但是在state中是可能有多个数据,并且还可能有对象的嵌套的
今天我们就用new Vue的方法,就是把数据放在里面,曲线救国,去变成响应式
new Vue的话,vue会把里面的data递归的都变成是响应式的数据的
然后外面是通过 this.$store.state.xxx来进行访问的
并且还要把这个生成的响应式数据暴露到外面
代码如下:
import Vue from 'vue'
import Vuex from './vuex'
// 挂载this.$state
Vue.use(Vuex)
export default new Vuex.Store({
// 通过this.$store.state.xxx 访问
state: {
counter: 0
},
mutations: {
// 这里的state从哪里来的
add(state) {
return state.counter++;
}
},
actions: {
// 参数是什么?哪里来的
add({commit}) {
setTimeout(() => {
commit("add");
}, 1000);
}
},
modules: {
}
})
下面就开始实现commit方法
class Store {
constructor(options) {
// state响应式处理
// 后面就可以通过this.$store.state.xxx来进行访问了
this.state = new Vue({
data: options.state
});
this._mutations = options.mutations;
}
commit(type, payload) {
const entry = this._mutations[type];
if (!entry) {
console.error("unknown mutation type");
}
entry(this.state, payload);
}
}
这里就回答了刚刚的一个问题,就是mutation函数里面的state是怎么拿到的
其中的payload,是用户传递的参数
优化:?????????????
在对state进行响应式的时候,源码中,其实为了防止用户直接对vue实例进行操作,就是不想把new Vue直接暴露到外面,它是这样做的
所以就实现了一个get方法,而不能set,这样就是对外界进行了一些隐藏,如果用户set的话会报错
所以只能通过mutation,而不能直接this.state进行改变了
// 1、插件:挂载$store
// 2、实现Store
let Vue;
class Store {
constructor(options) {
// state响应式处理
// 后面就可以通过this.$store.state.xxx来进行访问了
this._vm = new Vue({
data: {
$$state: options.state
}
});
this._mutations = options.mutations;
}
get state() {
return this._vm._data.$$state;
}
set state(v) {
console.error("Please use replaceState to reset state");
}
commit(type, payload) {
const entry = this._mutations[type];
if (!entry) {
console.error("unknown mutation type");
}
entry(this.state, payload);
}
}
const install = (_Vue) => {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
};
export default { Store, install };
通过this.state会触发get方法,设置的话就会触发set方法了
实现dispatch方法
拓展:
在action的参数中,我们可以通过结构来获取到getter、commit、state这些
所以就认为,传给action方法的就是Store实例this本身了
dispatch(type, payload) {
const entry = this._actions[type];
if (!entry) {
console.error("unkown mutation type");
}
entry(this, payload);
}
然后就测试
报错了
因为我们传入了this,这个this就会有执行的问题
我们在action中是
actions: {
// 参数是什么?哪里来的
add({commit}) {
setTimeout(() => {
commit("add");
}, 1000);
}
},
在seTimeout里面的执行了方法,这时候this其实就已经变了,早就不是我们的Store实例本身了,所以一般这个时候都会使用 bind来进程绑定this的了
然后就实现了
目前代码:
vuex.js
// 1、插件:挂载$store
// 2、实现Store
let Vue;
class Store {
constructor(options) {
// state响应式处理
// 后面就可以通过this.$store.state.xxx来进行访问了
this._vm = new Vue({
data: {
$$state: options.state
}
});
this._mutations = options.mutations;
this._actions = options.actions;
// bind绑定this
this.commit = this.commit.bind(this);
this.dispatch = this.dispatch.bind(this);
}
get state() {
return this._vm._data.$$state;
}
set state(v) {
console.error("Please use replaceState to reset state");
}
commit(type, payload) {
const entry = this._mutations[type];
if (!entry) {
console.error("unknown mutation type");
}
entry(this.state, payload);
}
dispatch(type, payload) {
const entry = this._actions[type];
if (!entry) {
console.error("unkown mutation type");
}
entry(this, payload);
}
}
const install = (_Vue) => {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
};
export default { Store, install };
实现getters
我们在store/index.js中
import Vue from 'vue'
import Vuex from './vuex'
// 挂载this.$state
Vue.use(Vuex)
export default new Vuex.Store({
// 通过this.$store.state.xxx 访问
state: {
counter: 0
},
mutations: {
// 这里的state从哪里来的
add(state) {
return state.counter++;
}
},
actions: {
// 参数是什么?哪里来的
add({commit}) {
setTimeout(() => {
commit("add");
}, 1000);
}
},
modules: {
},
getters: {
doubleCounter(state) {
return state.counter * 2;
}
}
})
定义了一个getters
vuex.js中具体实现
实现流程:
因为我们使用是这样的:
<h1>两倍:{{$store.getters.doubleCounter}}</h1>
也就是访问了store对象里面getters熟悉的一个key = doubleCounter的属性值
所以
- 在Store类中定义一个getters对象
- 然后对用户定义的getters进行遍历,然后动态的生成对应函数名的key值和value值
- 然后用户访问这个key值的时候,其实就是去执行了key这个名字的 getters中对应的函数了
完整代码如下:
// 1、插件:挂载$store
// 2、实现Store
let Vue;
class Store {
constructor(options) {
// state响应式处理
// 后面就可以通过this.$store.state.xxx来进行访问了
this._vm = new Vue({
data: {
$$state: options.state
}
});
this._mutations = options.mutations;
this._actions = options.actions;
// bind绑定this
this.commit = this.commit.bind(this);
this.dispatch = this.dispatch.bind(this);
this.getters = {};
options.getters && this.handleGetters(options.getters);
}
handleGetters(getters) {
Object.keys(getters).map(key => {
Object.defineProperty(this.getters, key, {
get: () => getters[key](this.state)
})
})
}
get state() {
return this._vm._data.$$state;
}
set state(v) {
console.error("Please use replaceState to reset state");
}
commit(type, payload) {
const entry = this._mutations[type];
if (!entry) {
console.error("unknown mutation type");
}
entry(this.state, payload);
}
dispatch(type, payload) {
const entry = this._actions[type];
if (!entry) {
console.error("unkown mutation type");
}
entry(this, payload);
}
}
const install = (_Vue) => {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
};
export default { Store, install };