从0实现简易版的vuex
想要更好的使用一个插件,可以尝试理解其实现的方式。
当然,了解一个优秀的插件,本身也会增强自己的能力。
本文,努力从零开始实现一个简易版的vuex
,期间会用到很多编程思想,希望自己越来越灵活使用。
TL;DR
state
是响应式的,巧用Vuegetters
用Object.defineProperty
实现和state的紧密相关mutations
是定义commit
方法,commit
的this需要固定为store实例actions
是定义dispatch
,逻辑神似mutations
- 翻到文末可以直接看
vuex.js
的简易版代码
vuex 的初版样子
先可以用vue create xx
创建一个项目,不带vuex
的。
先看看,如果有vuex
插件的main.js
。
网络异常,图片无法展示
|
!!!特别注意
{state:{},mutations:...}
,是用户传的参数- store 虽然可以
this.$store.state
,但这个 state 不完全是用户传的 state,而是处理过的 state,这两有本质区别 - 同样,用户传过来的其他属性,也会做处理,这样才有后期的
this.$store.getters.xx
等等 - 换言之,
store
就是对用户传的参数做各种处理,以方便用户操作她的数据。
从这推理出vuex
,应该具有的特征:
Vue.use
表明,vuex 肯定有install
方法new Vuex.Store
表明,vuex 导出对象里,有个Store
的类- 每个组件内部都可以
this.$store
表明,需要注入$store
如果对插件一脸懵的话,可以简单看下vue 插件的入门说明
第一版vuex.js
就出来了:
网络异常,图片无法展示
|
但这样,$store
和store实例
并没有挂钩,此时可以借助Vue.mixins的beforeCreate钩子
拿到当前的 Vue 实例,从而拿到实例的$options
。
export default { install(Vue) { Vue.mixin({ beforeCreate() { // 这里的this是vue的实例,其参数store就是store实例 (!Vue.prototype.$store) && (Vue.prototype.$store = this.$options.store;) } }); }, Store };
改进:不要轻易在原型上面添加属性,应该只在根实例有store
的时候才设置$store
,子实例会拿到根实例的$store
网络异常,图片无法展示
|
github源码 切换到c1
分支
处理用户传的 state
store 实例的state
可以出现在视图里,值变化的时候,视图也一并更新。 所以,state
是被劫持的,这里投机取巧的用下Vue
。
// vuex.js class Store { constructor(options) { this.options = options; this.state = new Vue({ data: options.state }); } }
<!-- App.vue --> <div id="app"> {{ $store.state.a }} <button @click="$store.state.a++"> 增加 </button> </div>
github源码 切换到c2
分支
!!!因为state是用Vue进行响应式,所有vuex重度依赖vue,不能脱离vue使用
处理用户传的 getters
- 用户传的
getters
是一个函数集合 - 但是实际使用中,属性值是函数的返回值
- 属性依旧是劫持的,这边因为是函数,所以不能再投机取巧了
// vuex.js constructor(options) { this.options = options; this.state = new Vue({ data: options.state }); if (options.getters) { this.getters = {}; Object.keys(options.getters).forEach(key => { // 这里必须是属性劫持 Object.defineProperty(this.getters, key, { get: () => { return options.getters[key](this.state); } }); }); } }
// main.js state: { a: 1, b: 2 }, getters: { a1(state) { return state.a + 1; } }
<!-- app.vue --> <div id="app"> {{ $store.state.a }} {{ $store.getters.a1 }} <button @click="$store.state.a++"> 增加 </button> </div>
网络异常,图片无法展示
|
github源码 切换到c3
分支
处理 mutations
mutations
,传的参数是一个函数集合的对象,使用的时候commit('函数名',payload)
代码翻译:
mutations:{ addA(state,payload){state.a+=payload} } // 使用的时候 this.$store.commit('addA',2)
由此推理出,vuex 其实写了一个commit
方法。这个就很简单了,直接溜上来。
// vuex.js class Store { constructor(options) { // ... if (options.mutations) { this.mutations = { ...options.mutations }; } } commit(mutationName, ...payload) { console.log(mutationName, ...payload); this.mutations[mutationName](this.state, ...payload); } }
网络异常,图片无法展示
|
// <button @click="$store.commit('addA', 2)"> 增加 </button> const store = new Vuex.Store({ state: { a: 1, b: 2 }, getters: { a1(state) { return state.a + 1; }, }, mutations: { addA(state, num) { state.a += num; }, }, });
github源码 切换到c4
分支
处理 actions
actions
和mutations
是很相似的。
actions:{ // 注意!!!,这里的第一个参数是store实例 addA({commit},payload){setTimeout(()=>{commit('addA',payload)},1000)} } // 使用的时候 this.$store.dispatch('addA',100)
这下更容易了,直接copy
commit(mutationName, ...payload) { this.mutations[mutationName](this.state, ...payload); } dispatch(actionName, ...payload) { // 注意这里是this,不是this.state this.actions[actionName](this, ...payload); }
网络异常,图片无法展示
|
// <button @click="$store.dispatch('addA', 2)"> 1s后增加100 </button> const store = new Vuex.Store({ // ... actions: { addA(store, num) { setTimeout(() => { store.commit("addA", num); }, 1000); } }, });
github源码 切换到c5
分支
优化
- commit做处理的时候,最好用下切片思维,这样方便修改逻辑
- commit里面的
this
,最好固定执行store实例,因为这样在action那边的时候,可以直接解构赋值 - action也一样
// vuex.js constructor(options){ // ... if (options.mutations) { this.mutations = {}; Object.keys(options.mutations).forEach(mutationName => { // 切片思维,这里上下都可以加逻辑 this.mutations[mutationName] = (...payload) => { options.mutations[mutationName](...payload); }; }); } } // 将this始终执行store实例 commit = (mutationName, ...payload) => { this.mutations[mutationName](this.state, ...payload); };
action操作一样,不在赘述代码。
actions: { addA({commit}, num) { // 这里可以解构了!!! setTimeout(() => { commit("addA", num); }, 1000); } },
github源码 切换到c6
分支。
还有模块空间的内容,考虑到篇幅较长,就不在本文继续了。
附注:vuex.js的所有代码
let Vue; class Store { constructor(options) { this.options = options; this.state = new Vue({ data: options.state }); if (options.getters) { this.getters = {}; Object.keys(options.getters).forEach(key => { Object.defineProperty(this.getters, key, { get: () => { return options.getters[key](this.state); } }); }); } if (options.mutations) { this.mutations = {}; Object.keys(options.mutations).forEach(mutationName => { this.mutations[mutationName] = (...payload) => { options.mutations[mutationName](...payload); }; }); } if (options.actions) { this.actions = {}; Object.keys(options.actions).forEach(actionName => { this.actions[actionName] = (...payload) => { options.actions[actionName](...payload); }; }); } } commit = (mutationName, ...payload) => { this.mutations[mutationName](this.state, ...payload); }; dispatch = (actionName, ...payload) => { this.actions[actionName](this, ...payload); }; } export default { install(_Vue) { Vue = _Vue; Vue.mixin({ beforeCreate() { // 这里的this是vue的实例,其参数store就是store实例 const hasStore = this.$options.store; // 根实例的store hasStore ? (this.$store = this.$options.store) : this.$parent && (this.$store = this.$parent.$store); } }); }, Store };