首先我们先从一个面试题入手。
1. props
2. 自定义事件
3. eventbus
4. vuex
5. 还有常见的边界情况\$parent、\$children、\$root、\$refs、provide/inject
6. 此外还有一些非props特性\$attrs、\$listeners
今天我们来看看Vuex内部的奥秘! 如果要看别的属性原理请移步到Vue组件通信原理剖析(一)事件总线的基石 on和emit和Vue组件通信原理剖析(三)provide/inject原理分析
vuex
Vuex集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以可预测的方式发生变化。
我们先看看如何使用vuex,
-
第一步:定义一个Store
// store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default = new Vuex.Store({ state: { counter: 0 }, getters: { doubleCounter(state) { return state.counter * 2 } }, mutations: { add(state) { state.counter ++ } }, actions: { add({commit}) { setTimeout(() => { commit('add') }, 1000); } } })
-
第二步,挂载app
// main.js import vue from 'vue' import App form './App.vue' import store from './store' new Vue({ store, render: h => h(App) }).$mount('#app')
-
第三步:状态调用
// test.vue <p @click="$store.commit('add')">counter: {{ $store.state.counter }}</p> <p @click="$store.dispatch('add')">async counter: {{ $store.state.counter }}</p> <p>double counter: {{ $store.getters.doubleCounter }}</p>
从上面的例子,我们可以看出,vuex需要具备这么几个特点:
- 使用Vuex只需执行
Vue.use(Vuex)
,保证vuex是以插件的形式被vue加载。 - state的数据具有响应式,A组件中修改了,B组件中可用修改后的值。
- getters可以对state的数据做动态派生。
- mutations中的方法是同步修改。
- actions中的方法是异步修改。
那我们今天就去源码里探索以下,vuex是怎么实现的,又是怎么解决以上的问题的!
问题1:vuex的插件加载机制
所谓插件机制,就是需要实现Install方法,并且通过mixin
形式混入到Vue的生命周期中,我们先来看看Vuex的定义
-
需要对外暴露一个对象,这样就可以满足
new Vuex.Store()
// src/index.js import { Store, install } from './store' import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers' export default { Store, install, version: '__VERSION__', mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }
-
其次是定义store,并且实现vue的Install方法
// src/store.js let Vue // bind on install export class Store { ...... } // 实现的Install方法 export 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) }
问题2:state的数据响应式
看懂了Vuex的入口定义,下面我们就针对store的定义来一探究竟,先看看state的实现
// src/store.js
export class Store {
constructor(options = {}) {
......
// strict mode
this.strict = strict
const state = this._modules.root.state
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 看上面的注释可以得知,resetStoreVM就是初始化store中负责响应式的vm的方法,而且还注册所有的gettersz作为vm的计算属性
resetStoreVM(this, state)
}
}
我们来看看resetStoreVM的具体实现
// src/store.js
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
// 这里是实现getters的派生
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
// 这是是通过new一个Vue实例,并将state作为实例的datas属性,那他自然而然就具有了响应式
const silent = Vue.config.silent
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
问题3:getters实现state中的数据的派生
关于getters的实现,我们在上面也做了相应的解释,实际上就是将getters的方法包装一层后,收集到computed对象中,并使用Object.defineProperty注册store.getters,使得每次取值时,从store._vm中取。
关键的步骤就是创建一个Vue的实例
store._vm = new Vue({
data: {
$$state: state // 这是store中的所有state
},
computed // 这是store中的所有getters
})
问题4:mutations中同步commit
// src/store.js
// store的构造函数
constructor(options = {}) {
// 首先在构造方法中,把store中的commit和dispatch绑定到自己的实例上,
// 为什么要这么做呢?
// 是因为在commit或者dispatch时,尤其是dispatch,执行function时会调用实例this,而方法体内的this是具有作用域属性的,所以如果要保证每次this都代表store实例,就需要重新绑定一下。
const store = this
const { dispatch, commit } = this
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)
}
}
// commit 的实现
commit (_type, _payload, _options) {
// check object-style commit
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 通过传入的类型,查找到mutations中的对应的入口函数
const entry = this._mutations[type]
......
// 这里是执行的主方法,通过遍历入口函数,并传参执行
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
......
}
问题5:actions中的异步dispatch
上面说了在构造store时绑定dispatch的原因,下面我们就继续看看dispatch的具体实现。
// src/store.js
// dispatch 的实现
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
// 同样的道理,通过type获取actions中的入口函数
const entry = this._actions[type]
······
// 由于action是异步函数的集合,这里就用到了Promise.all,来合并多个promise方法并执行
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
return res
})
}
到这里,我们就把整个store中状态存储和状态变更的流程系统的串联了一遍,让我们对Vuex内部的机智有个简单的认识,最后我们根据我们对Vuex的理解来实现一个简单的Vuex。
// store.js
let Vue
// 定义store类
class Store{
constructor(options = {}) {
this.$options = options
this._mutations = options.mutations
this._actions = options.actions
this._wrappedGetters = options.getters
// 定义computed
const computed = {}
this.getters = {}
const store = this
Object.keys(this._wrappedGetters).forEach(key => {
// 获取用户定义的getters
const fn = store._wrappedGetters[key]
// 转换为computed可以使用无参数形式
computed[key] = function() {
return fn(store.state)
}
// 为getters定义只读属性
Object.defineProperty(store.getters, key {
get:() => store._vm[key]
})
})
// state的响应式实现
this._vm = new Vue({
data: {
// 加两个$,Vue不做代理
$$state: options.state
},
computed // 添加计算属性
})
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
// 存取器,获取store.state ,只通过get形式获取,而不是直接this.xxx, 达到对state
get state() {
return this._vm._data.$$state
}
set state(v) {
// 如果用户不通过commit方式来改变state,就可以在这里做一控制
}
// commit的实现
commit(type, payload) {
const entry = this._mutations[type]
if (entry) {
entry(this.state, payload)
}
}
// dispatch的实现
dispatch(type, payload) {
const entry = this._actions[type]
if (entry) {
entry(this, payload)
}
}
}
// 实现install
function install(_Vue) {
Vue = _Vue
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$Store = this.$options.store // 这样就可以使用 this.$store
}
}
})
}
// 导出Vuex对象
export default {
Store,
install
}
全部文章链接
Vue组件通信原理剖析(一)事件总线的基石 on和emit
Vue组件通信原理剖析(二)全局状态管理Vuex
Vue组件通信原理剖析(三)provide/inject原理分析
最后喜欢我的小伙伴也可以通过关注公众号“剑指大前端”,或者扫描下方二维码联系到我,进行经验交流和分享,同时我也会定期分享一些大前端干货,让我们的开发从此不迷路。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!