9012年了,牛客的面经看的笔者由衷得以为,这年头,没看过源码,估计都不敢说本身是前端攻城狮了吧,再不折腾一下,估计连切图都要轮不上了。虽说安心作个切图仔仍是挺快乐的,不过说实在的,没有梦想的切图仔,和咸鱼有什么分别。javascript
今天笔者要讨论的就是如何实现一个缩小版的Vuex
,本篇先你们熟悉一下他的内部结构大概是什么样的以及实现一个本身的state
、getters
、mutations
、actions
,modules
部分笔者将在下一篇文章中进行介绍。前端
用了这么久Vuex
了,咱们不妨大胆的想象几个问题:vue
Vue
实例上都能拿到$store
,它的实现原理是什么。Vuex
怎么实现和Vue
同样,数据改变,界面自动更新。getters
与mutations
、actions
有什么不一样,怎么实现。mutations
与actions
有什么不一样,应用场景的区别,为何。接下来,咱们将从这几个问题开始,揭开Vuex
的神秘面纱java
Vuex试探之旅,你值得拥(fang)有(qi)。git
友情提醒,下面的代码不能直接用,只是笔者截取了片断用于理解,文末会贴上完整代码。es6
由于本篇不涉及modules
,因此文中getters
、mutations
、actions
实现将会和源码有一些的出入,不过,原理是同样的。github
若是你们有本身注册导入过Vuex
,那么相信你们对这样的过程天然是了然于心:vuex
import Vuex from 'vue'
Vue.use(Vuex)
const store=new Vuex.Store({...})
new Vue
的地方放上store
咱们从第二个步骤开始研究。异步
至于那些问
import
怎么用的大哥我只能默默地说一句,9012年了,没接触过es6
你是怎么学完Vue.js
的。函数
顾名思义,use
就是用的意思,这其实就涉及到Vue
的这一个插件安装机制了,它会默认去找须要安装的模块的install
方法,而后把Vue
以及其余参数传入你的install
方法,而后呢,咱们就能够在这一步上动点手脚了。
话很少说,看(代)码:
let _Vue;
const install = (vm, options) => {
_Vue = vm;
_Vue.mixin({
beforeCreate() {
if (this.$parent) {
this.$store = this.$parent.$store
} else {
this.$store = this.$options && this.$options.store
}
}
})
}
export default {install}
复制代码
首先咱们会先把Vue
保存一下,留待他用(管他待会用不用,先留着)。而后接下来就是咱们上面提出的第一个问题怎么实现的答案了,使用Vue
内部的mixin
方法,给每一个实例混入一个钩子函数。
什么是混入,这里伪装你们都知不道,稍微介绍一下。以上面代码为例,差很少就是给每一个实例都添加一个
beforeCreate
方法,它不会覆盖原有的钩子函数,而是会一块儿执行。
而后咱们能够先判断是不是子组件,若是是就拿到它父组件的$store
赋值给当前子组件的store
上,若是不是子组件,就能够从$options
上拿到$store
,这样一来,全部Vue
实例就都有了$store
属性了。
其实说白了,
state
不就是一个储存一些属性的对象而已,换了张脸我仍是认识你王麻子。
上(代)码:
class Store {
constructor(options) {
/**保存一份到自己实例 */
this._options = options;
}
get state() {
return this._options.state
}
}
复制代码
劫持一下获取方式,实际上就是在访问你传进来的对象。固然,若是只是这么写仍是有点问题的,它无法根据数据改变来自动让界面更新。
其实这个问题真挺好解决的,笔者在问题中都已经提醒你了,不信你回去再看看
咱们借用Vue
实例具备数据界面绑定的特性,因而咱们给state
稍微包装一下就能够实现咱们要的效果了。
/**借用Vue的双向绑定机制让Vuex中data变化实时更新界面 */
this.vm = new _Vue({
data: {
state: options.state
}
})
复制代码
而后再修改一下对应的get
方法
get state() {
return this.vm.state
}
复制代码
其实从用法上看,getters
用法和咱们的computed
真挺像的。从表现形式上看,它其实根本上就是个函数,只不过人家内部代替你执行了。
/**简化代码,封装遍历方法 */
const forEach = (obj, callback) => {
Object.keys(obj).forEach((key) => {
callback(key, obj[key])
})
}
/**保存getters */
this.getters = {};
let getters = this._options.getters || {}
/**遍历保存传入的getters,监听状态改变从新执行该函数 */
forEach(getters, (getterName, fn) => {
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
复制代码
这里咱们遍历了用户传入的getters
对象,而后把拿到的属性名写入到实例的getters
对象上,并绑定了对应的get
方法。这个地方咱们用上了咱们熟悉的属性劫持来控制怎么给用户返回咱们想给他的值。
同时,由于getter
中的第一个参数是state
对象,因此咱们在执行get
方法的时候会把state
做为参数传入执行,并返回函数执行结果给用户。
彷佛实现起来并不怎么难看懂,估计部分小伙伴会想,这货该不会在讹我吧,怎么可能这么简单。
其实吧,笔者以为mutations
这个东东有点像咱们用过的methods
对象,一样是一个对象上绑定了不少函数,只不过用法上面看上去彷佛不太同样,咱们通常都是使用commit
方法来调用一个mutation
函数,而后传入两个参数state、payload
。
看到这个payload
,就会有小伙伴问了,啥是payload
啊?官方文档称之为载荷,名字挺高大上的哈,其实就是接收用户传入的参数。
来看看笔者的实现吧。
/**保存mutations */\
constructor(options) {
this.mutations = {};
let mutations = this._options.mutations || {};
forEach(mutations, (mutationName, fn) => {
this.mutations[mutationName] = (payload) => {
return fn(this.state, payload)
}
})
}
...
commit = (type, payload) => {
this.mutations[type](payload);
}
复制代码
先把用户传入的mutations
对象的属性和方法保存到Vuex
实例上,而后让用户调用commit
方法的时候来指向对应的函数,并把state
和payload
传入。
乍一看,彷佛和getters
实现差不太多,只不过这里没用到属性劫持了,而是给你包装了一下调用方式。(反正笔者是这么理解的)
对于这个家伙的用途,相信部分小伙伴也能轻松的说出,是的,官方推荐通常的应用场景就是处理一些异步事件,而mutations
通常用于处理同步事件。下面贴上官方的一张概念图你就能理解了。
这时候又有小伙伴要说了,我用
mutations
同样的能够实现啊。是的,用mutations
固然也行,不过只是不符合Vuex
的设计理念而已。
若是对于一些异步事件使用mutations
会出现devtools
没法捕获到这个事件的记录,因此咱们最好仍是走走寻常路,跟着官方走吧。
/**保存actions */
constructor(options) {
this.actions = {};
let actions = this._options.actions || {};
forEach(actions, (actionName, fn) => {
this.actions[actionName] = (payload) => {
return fn(this, payload)
}
})
}
...
dispatch = (type, payload) => {
this.actions[type](payload)
}
复制代码
它实现起来仍是挺像mutations
的(毕竟两个好基友嘛),他们从代码层次上看差不太多,只是在传参方面和调用方法方面有点差别,一个用commit
,参数是state,payload
;一个是dispatch
,传参是一个Vuex
实例(实际上并非的,由于涉及到modules
,下文将会讲到)。
具体代码实现和mutations
差很少,笔者这就很少啰嗦了,不过在这里笔者仍是要提醒一下,由于通常来讲咱们在使用actions
的时候都是用的解构,获取commit
,以及一些其余参数,因此咱们在这里须要注意下this
指向的问题,笔者这里用的是箭头函数来解决了这个问题。
let _Vue;
/**简化代码,封装遍历方法 */
const forEach = (obj, callback) => {
Object.keys(obj).forEach((key) => {
callback(key, obj[key])
})
}
class Store {
constructor(options) {
/**借用Vue的双向绑定机制让Vuex中data变化实时更新界面 */
this.vm = new _Vue({
data: {
state: options.state
}
})
/**保存一份到自己实例 */
this._options = options;
/**保存getters */
this.getters = {};
let getters = this._options.getters || {}
/**遍历保存传入的getters,监听状态改变从新执行该函数 */
forEach(getters, (getterName, fn) => {
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
/**保存mutations */
this.mutations = {};
let mutations = this._options.mutations || {};
forEach(mutations, (mutationName, fn) => {
this.mutations[mutationName] = (payload) => {
return fn(this.state, payload)
}
})
/**保存actions */
this.actions = {};
let actions = this._options.actions || {};
forEach(actions, (actionName, fn) => {
this.actions[actionName] = (payload) => {
return fn(this, payload)
}
})
}
get state() {
return this.vm.state
}
commit = (type, payload) => {
this.mutations[type](payload);
}
dispatch = (type, payload) => {
this.actions[type](payload)
}
}
const install = (vm, options) => {
_Vue = vm;
_Vue.mixin({
beforeCreate() {
if (this.$parent) {
this.$store = this.$parent.$store
} else {
this.$store = this.$options && this.$options.store
}
}
})
}
export default {
install,
Store
};
复制代码
修行不易,前端的世界老是突飞猛进,要跟上它的步伐仍是要多折腾折腾。
今天笔者就暂时说到这了,小伙伴以为有帮助的话就给笔者点个赞呗。
贴上笔者我的网站地址: 烟雨的我的博客
源码github地址:my_vuex