本前提是已经熟练使用vuex
1. 将 Vuex 挂载到window下面
(function (global, factory) {
global.Vuex = factory()
})(this, (function() {}))
2.给每一个vue组件挂载一个 store.state取到
接下来我们看store是怎么混入到vue中的,首先就是 Vue.use()来安装这个插件,一般安装的插件都有个 install 方法,因为 Vue.use()也是调用者这个方法,并把Vue实例传入进来,然后通过 Vue.minxin()方法在 beforeCerete阶段将 store了就把父组件的$store赋值给子组件,最后return 出去
(function (global, factory) {
global.Vuex = factory()
})(this, (function() {
var Vue // 存一份 Vue实例
var install = function(_Vue) {
Vue = _Vue
} Vue.mixin({ beforeCreate: function() {
// 深度优先,判断父组件是否有 $store,如果有 就把父级的 赋值给 组件的 $store
if( this.$options && this.$options.store ) {
this.$store = this.$options.store
}else{
this.$store = this.$parent && this.$parent.$store
}
}
})
return {
install: install
}
}))
3.那这个store应该如何写呢
store是通过这种方式去 创建的var store = new Vuex.Store({options}),所以我们需要写一个Store的构造函数, 并return出去
var Store = function( options ) {}return {
install,
Store
}
第一步实现 state,我们知道 state也是双向绑定的,所以想到了 Object.defineProperty,并写了一个只读的方法,
var Store = function( options ) {
this._state = new Vue({
data: {
state: options.state
} })
Object.defineProperty( this, 'state', {
enumerable: true,
configurable: true,
get() {
return this._state.state
},
set() {
{
throw new Error(("[vuex] " + '不能通过 state 直接赋值,可以通过 commit 触发'))
}
}
})
}
return {
install,
Store
}
第二步是要实现 getters ,这个相当于 vue的computed,将state处理后返回,首先封装一个forEach方法,之后收集所有的getters, 也是通过 Object.defineProperty,如果模板中用到了这个getter,直接执行getter函数并把state当参数传入
function forEach (obj, callback) {
Object.keys(obj).forEach(function(key) {
callback && callback(key, obj[key])
})
}
var store = this // 这里存储一下 this
var getters = options.getters || {}
this.getters = {}
forEach(getters, function(key, value) {
Object.defineProperty(this.getters, key, {
enumerable: true,
configurable: true,
get: function() {
return value( store.state )
} }) }.bind(this))
第三步是实现一个 同步的提交 mutations ,提交mutation需要一个 commit方法,同getters,首先先把所有的mutation搜集订阅,然后通过commit方法区发布
var mutations = options.mutations || {}
this.mutations = {}
forEach(mutations, function(key, value) {
this.mutations[key] = function(payload) {
value(store.state, payload )
}}.bind(this))
// 同步提交
Store.prototype.commit = function(type, payload) {
this.mutations[type](payload)
}
第四部是要实现一个 actions 异步提交,需要一个dispatch方法,同样也是先收集 actions,然后写dispatch方法
var actions = options.actions || {}
this.actions = {}
forEach( actions, function(key, value) {
this.actions[key] = function(payload) {
value(store, payload)
}}.bind(this))
Store.prototype.dispatch = function(type, payload) {
this.actions[type](payload)
}
到这看上去已经很完美了,基本的主要功能都实现了,但是运行时候发现,如果在 dispatch的时候里面 放了 commit 就会报错,找不到 commit 触发的方法,因为 forEach运行的会后 他的this是window,所以我们得想办法改变this指向,所有我们现在原型方法上添加 commit 和 dispatch,这样执行commit的时候会先找到原型方法,不会执行实例方法,并且绑定commit ,执行的时候this 指向都是 store ,就可以找到 commit的
var commit = store.commit, dispatch = store.dispatch
this.commit = function(type, payload) {
commit.call(this, type, payload )
}.bind(this)
this.dispatch = function(type, payload) {
dispatch.call(this, type, payload)
}.bind(this)
下面是完整的代码
// 将 Vuex 挂载到window下面(function (global, factory) { global.Vuex = factory()})(this, (function() { // # 一、为 每一个组件挂载 $store // ** 1. vuex 会挂载一个 vm.$store 全局属性, 取值的时候可以 通过 $store.state ... // 接下来我们看store是怎么塞入到 vue中的 Vue.use(vuex) -- 用来写插件,Vue.use()用来安装这个插件 // ,第一个参数是 Vue的构造函数, 第二个是传入的options // 如果传入对象 默认调用这个库的install方法 https://cn.vuejs.org/v2/guide/plugins.html // https://github.com/vuejs/awesome-vue#components--libraries 丰富的插件社区 // ** 2. 先保留一份 Vue // ** 3. 初始化store 的时候 new Vux.store() 所有 Vuex应该还有一个 store的构造函数, // 而且我们想在所有组件中都可以访问到 $store, 就要给每一个组件都添加 $store属性 vue中提供了 一个方法 vue.mixin() // 它将影响每一个之后创建的 Vue 实例, 通过这个方法我们可以给所有的组件都添加上一个 $store, // 通过这个方法我们可以看出来,vue是先渲染父组件再渲染子组件, 深度优先 var Vuefunction forEach (obj, callback) { Object.keys(obj).forEach(function(key) { callback && callback(key, obj[key]) })} var install = function(_Vue) { Vue = _Vue Vue.mixin({ beforeCreate: function() { // 深度优先,判断父组件是否有 $store,如果有 就把父级的 赋值给 组件的 $store if( this.$options && this.$options.store ) { this.$store = this.$options.store }else{ this.$store = this.$parent && this.$parent.$store } } }) } var Store = function( options ) { // # 二、处理 store的 state, 我们不能直接修改 ,所有我们用es5写一个 get 和 set 方法封装 this._state // 理论上给 this.$store.state[xxx]属性赋值是不合法的,但是真实可以成功,而且得响应式的 this._state = new Vue({ data: { state: options.state } }) Object.defineProperty( this, 'state', { enumerable: true, configurable: true, get() { return this._state.state }, set() { { throw new Error(("[vuex] " + '不能通过 state 直接赋值,可以通过 commit 触发')) } } }) // # 三、处理 store的 getters, (相当于 vue的 computed的属性) var store = this // 这里存储一下 this var getters = options.getters || {} this.getters = {} forEach(getters, function(key, value) { Object.defineProperty(this.getters, key, { enumerable: true, configurable: true, get: function() { return value( store.state ) } }) }.bind(this)) // # 四、处理 store 的 mutations var mutations = options.mutations || {} this.mutations = {} forEach(mutations, function(key, value) { this.mutations[key] = function(payload) { value(store.state, payload ) } }.bind(this)) // # 五、处理 store 的 actions var actions = options.actions || {} this.actions = {} forEach( actions, function(key, value) { this.actions[key] = function(payload) { value(store, payload) } }.bind(this)) console.log(this) // actions commit 的时候 this是 window ,所以会找不到 触发的 commit函数名, 解决办法就是 先定义一个commit 和 dispatch var commit = store.commit, dispatch = store.dispatch this.commit = function(type, payload) { commit.call(this, type, payload ) }.bind(this) this.dispatch = function(type, payload) { dispatch.call(this, type, payload) }.bind(this) } // 同步提交 Store.prototype.commit = function(type, payload) { this.mutations[type](payload) } // 异步提交 Store.prototype.dispatch = function(type, payload) { this.actions[type](payload) } return { install: install, Store: Store }}))
git 地址
https://github.com/zhidanwang19910111/lite-vuex.git
系列文章
ES5实现一个 Vuex(2)