vuex 源码分析_vuex源码解读&简易实现

本文深入分析Vuex的源码,从Vue.use(Vuex)开始,讲解Store的初始化过程,包括如何实现state和getters的响应式,以及commit如何触发mutations。最后,给出了一个简易的Vuex实现。
摘要由CSDN通过智能技术生成

源码解读

开始之前,先贴个大纲:

首先,我们从使用方法入手,一步步来看

// store.js

import Vue from 'vue';

import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({

state: {

msg: ''

},

getters: {

msgPlus: state => state.msg + ' plus'

},

mutations: {

setMsg (store, data) {

store.msg = data;

}

},

actions: {

asyncChangeMsg ({commit}, data) {

setTimeout(() => {

commit('setMsg', data);

}, 2000)

}

}

})

// main.js

import Vue from 'vue';

import App from 'App.vue';

import store from './store';

new Vue({

render(h)=> h(App),

store,

}).$mount('#app')

// App.vue

msg: {{$store.state.msg}}

msgPlus: {{$store.getters.msgPlus}}

export default {

created () {

this.$store.dispatch('asyncChangeMsg', 22)

}

}

接下来

从store.js文件入手。Vue.use(Vuex): 据官网介绍,此方法用来安装插件,插件必须提供install方法,且会将Vue作为参数传入;

于是,查看导出vuex的地方,找到./node_modules/vuex/dist/vuex.esm.js,就是源码部分了。注:后缀esm(EcmaScript Module)表示es6模块版;

此文件底部,查看导出部分,确存在install方法

var index_esm = {

Store: Store,

install: install,

version: '3.1.2',

mapState: mapState,

mapMutations: mapMutations,

mapGetters: mapGetters,

mapActions: mapActions,

createNamespacedHelpers: createNamespacedHelpers

};

export default index_esm;

查看install实现,传入的参数是真正的Vue

var Vue; // bind on install

function install (_Vue) {

// 防止重复

if (Vue && _Vue === Vue) {

if (process.env.NODE_ENV !== 'production') {

console.error(

'[vuex] already installed. Vue.use(Vuex) should be called only once.'

);

}

return

}

Vue = _Vue;

applyMixin(Vue);

}

上述代码段先是防止重复调用Vue.use方法,随后调用applyMixin方法

function applyMixin (Vue) {

var version = Number(Vue.version.split('.')[0]);

if (version >= 2) {

// 在每个Vue实例的beforeCreated生命周期时,执行vuexInit方法

Vue.mixin({ beforeCreate: vuexInit });

} else {

// ...

}

}

上述代码段,根据不同vue版本分别处理,这里暂且只看2.x及以上版本。

Vue.mixin方法:据官网介绍,全局混入,影响注册后创建的每个Vue实例。此处作用是,在每个Vue实例的beforeCreated生命周期时,执行vuexInit方法。

下边看vuexInit方法做了什么?

function vuexInit () {

// this表示该Vue实例,$options是实例初始化选项

var options = this.$options;

// store injection

// 从options中取store,若存在,则赋值到this.$store上;否则,从父级取得$store进行赋值

// this.$options.parent与this.$parent等价

if (options.store) {

this.$store = typeof options.store === 'function'

? options.store()

: options.store;

} else if (options.parent && options.parent.$store) {

this.$store = options.parent.$store;

}

}

上述代码段,从main.js入口实例Vue时,初始化传入的store开始,一级级传到子组件,于是每个组件上都有了$store属性。

于是,我们可以在每个组件上,通过this.$store进行操作,例如App.js中的this.$store.state.msg/this.$store.dispatch等

了解了Vue.use(Vuex)之后,再次回到store.js文件,接下来看实例化Store对象过程中发生了什么?注意一点:

// 成员访问的优先级高于new,于是先执行Vuex.Store,而后执行实例化

new Vuex.Store({})

首先贴出Store方法的定义:

var Store = function Store (options) {

var this$1 = this;

if ( options === void 0 ) options = {};

if (!Vue && typeof window !== 'undefined' && window.Vue) {

install(window.Vue);

}

if (process.env.NODE_ENV !== 'production') {

assert(Vue, "must call Vue.use(Vuex) before creating a store instance.");

assert(typeof Promise !== 'undefined', "vuex requires a Promise polyfill in this browser.");

assert(this instanceof Store, "store must be called with the new operator.");

}

var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];

var strict = options.strict; if ( strict === void 0 ) strict = false;

// store internal state

this._committing = false;

this._actions = Object.create(null);

this._actionSubscribers = [];

this._mutations = Object.create(null);

this._wrappedGetters = Object.create(null);

this._modules = new ModuleCollection(options);

this._modulesNamespaceMap = Object.create(null);

this._subscribers = [];

this._watcherVM = new Vue();

this._makeLocalGettersCache = Object.create(null);

// bind commit and dispatch to self

var store = this;

var ref = this;

var dispatch = ref.dispatch;

var commit = ref.commit;

this.dispatch = function boundDispatch (type, payload) {

return dispatch.call(store, type, payload)

};

this.commit = function boundCommit (type, payload, options) {

return commit.call(store, type, payload, options)

};

// strict mode

this.strict = strict;

var state = this._modules.root.state;

// 模块相关

installModule(this, state, [], this._modules.root);

resetStoreVM(this, state);

// apply plugins

plugins.forEach(function (plugin) { return plugin(this$1); });

var useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;

if (useDevtools) {

devtoolPlugin(this);

}

};

installModule方法为模块相关,暂且不看,先来看看比较重要的resetStoreVM方法:

function resetStoreVM (store, state, hot) {

var oldVm = store._vm;

// bind store public getters

store.getters = {};

// reset local getters cache

store._makeLocalGettersCache = Object.create(null);

// _wrappedGetters会收集所有getters方法 -- mynote

var wrappedGetters = store._wrappedGetters;

// 将getters的方法包装一层后,收集到computed对象中 -- mynote

// 使用Object.defineProperty注册store.getters,使得每次取值时,从store._vm中取。原因是下方进行new Vue传入computed后,computed的值会放到实例上边 -- mynote

var computed = {};

forEachValue(wrappedGetters, function (fn, key) {

computed[key] = partial(fn, store);

Object.defineProperty(store.getters, key, {

get: function () { return store._vm[key]; },

enumerable: true // for local getters

});

});

var silent = Vue.config.silent;

Vue.config.silent = true;

// 将state值 和 getters值分别传入一个new Vue中初始化成data和computed,使其具有响应式特性 -- mynote

store._vm = new Vue({

data: {

$$state: state

},

computed: computed

});

// ...

}

参考mynote部分,总的来说,此方法的作用是,借用Vue,使得state值和getters值具有响应式特征

再来看看,commit是如何触发mutations的?

Store.prototype.commit = function commit (_type, _payload, _options) {

var this$1 = this;

// check object-style commit

var ref = unifyObjectStyle(_type, _payload, _options);

var type = ref.type;

var payload = ref.payload;

var options = ref.options;

var mutation = { type: type, payload: payload };

var entry = this._mutations[type];

if (!entry) {

if (process.env.NODE_ENV !== 'production') {

console.error(("[vuex] unknown mutation type: " + type));

}

return

}

// 批量触发mutation

this._withCommit(function () {

entry.forEach(function commitIterator (handler) {

handler(payload);

});

});

// 通知订阅者

this._subscribers.forEach(function (sub) { return sub(mutation, this$1.state); });

if (

process.env.NODE_ENV !== 'production' &&

options && options.silent

) {

console.warn(

"[vuex] mutation type: " + type + ". Silent option has been removed. " +

'Use the filter functionality in the vue-devtools'

);

}

};

简易实现

// src下,新建vuex.js文件,之前引入vuex的地方改为引用此文件,其他地方无需修改

let Vue;

class Store {

constructor (options) {

this.vm = new Vue({

data: {state: options.state}

})

this.state = this.vm.state;

this.mutations = options.mutations;

this.actions = options.actions;

}

commit (eventName, data) {

this.mutations[eventName](this.state, data)

}

dispatch (eventName, data) {

this.actions[eventName](this, data)

}

}

const install = (_Vue) => {

Vue = _Vue;

Vue.mixin({

beforeCreate () {

if (this.$options && this.$options.store) {

this.$store = this.$options.store;

} else {

this.$store = this.$parent && this.$parent.$store;

}

}

})

}

export default {

Store,

install

}

完整项目可参考。

趁着空闲,记录下学习的知识,有待进一步完善...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值