参考链接:
Vuex实现原理解析
Vuex框架原理与源码分析
vue-mixins
在之前,我们实现了一个简单版本的MVVM
框架,现在我们来模拟实现一下vue
中的vuex
回顾Vuex
的使用
在这里,以官网的例子进行说明
use(Vuex)
& 声明 store
import Vue from 'Vue'
import Vuex from 'Vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
在上面代码中,Vue.use(Vuex)
,这一句代码至关重要。那这句代码为什么重要呢?它到底做了些什么?
在说明这些问题之前,我们必须知道
Vue中的use(xx)
方法会默认调用xx.install()
方法。 也就是说,在Vue.use(Vuex)
的时候,会执行Vuex.install()
方法。
为了验证上面的观点,我们来看下Vue.use
方法实现
function (plugin: Function | Object) {
/* istanbul ignore if */
if (plugin.installed) {
return
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
// 实际执行插件的install方法
plugin.install.apply(plugin, args)
} else {
plugin.apply(null, args)
}
plugin.installed = true
return this // 返回调用对象
}
我们再来看一下Vuex
的install
实现
/*暴露给外部的插件install方法,供Vue.use调用安装插件*/
export function install (_Vue) {
if (Vue) {
/*避免重复安装(Vue.use内部也会检测一次是否重复安装同一个插件)*/
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
/*保存Vue,同时用于检测是否重复安装*/
Vue = _Vue
/*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
applyMixin(Vue)
}
或许,有些地方还看不太懂,比如参数_Vue的作用,但没关系,我们接着往下走...
我们知道vuex
是全局共用一个store
,只要一处store
的数据发生了变化,全局都会发生变化。那么具体是如何实现的呢?
实现全局共享Store
如何实现全局共用一个store
?
解决这个问题,我们不难有这样一个思路,就是让所有的vue
实例,都一个指向store
对象的指针。
既然有了思路,那么具体该如何实现呢?我们知道,在使用vue
开发时,vue
组件是分层级的,类似于树
结构。
根据这个思路,我们不难想到递归。
使用递归可以实现我们的目的,但在这里,我们仿照Vuex
源码的方式,使用Vue.mixin
的方式。使用mixin
的方式,比使用递归效果更好,姿势也更华丽...
关于mixin
可以看官网关于mixin的介绍
根据官网的例子介绍,全局混入的方式会影响每个单独创建的Vue
实例 (包括第三方组件)。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
看到这里,我们可以想到利用mixin
的方式,为所有的Vue
实例绑定同一个store
对象
前面我们提到install
方法,这个方法是使用Vuex
必须要执行的,我们可以在这里做一些手脚,加入一些自己的逻辑。
要为所有的Vue
实例绑定store
对象,我们要思考以下几个问题:
store
从哪里来?- 如果确保所有的
store
是同一个对象? - 什么时候进行绑定?
为了回答上面三个问题,我们再来Vuex
的使用
store.js
import Vue from 'Vue'
import Vuex from 'Vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
main.js
import Vue from 'vue'
import App from './App'
import './common/css/iconfont.css'
import router from './router/index'
new Vue({
el: '#app',
router,
store, // 在这里,我们传入了store
render: h => h(App)
})
现在,回答一下我们的问题store
在我们创建vue
根实例时传入。我们只要让所有的vue
实例的全部都指向根实例的store
就好了。还有,前面我们提到过,vue
中的组件时表现为树形结构的,我们可以options.parent
访问到当前实例的父亲。
现在只剩下最后一个问题,什么时候绑定store
?
对于数据的绑定,我们推崇越早越好,因此我们利用beforeCreate生命钩子函数实现我们的目的
具体代码如下:
let Vue;
let install = (_Vue) => { //到这里,参数_Vue的作用也就明确了,因为我们需要用到Vue
Vue = _vue;
Vue.mixin({
beforeCreate() {
const options = this.$options
if (options.store) {
/*存在store其实代表的就是Root节点,直接执行store(function时)或者使用store(非function)*/
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
/*子组件直接从父组件中获取$store,这样就保证了所有组件都公用了全局的同一份store*/
this.$store = options.parent.$store
}
}
})
}
到目前为止,我们就实现了为所有的Vue
实例绑定同一个store
对象。
未完待续...