Vuex源码不多,1000多行,排除各种报错,兼容,语法糖和拓展功能,其实就干了两件事:
1、把外部传入的store对象组织成了一棵树型的数据结构
2、把state和getters的数据变成响应式,保证界面中可以双向绑定
按照这个需求,结合vuex的实现思路自己写了个vuexx,个人一直不太接受vuex3里的面向对象组织形式,深入源码可以看到state和action、mutation等的组织方式是不太一样的,比如state.module.type 和 dispatch(‘module/type’),太蛋疼了。这块按照个人的理解重新实现了。
App.vue
<template>
<div>
vuexx
<div>root: {{count}}</div>
<div>rootPlus: {{countPlus}}</div>
<div>A: {{countA}}</div>
<div>A: {{countAPlus}}</div>
<button @click="onClick">add</button>
</div>
</template>
<script>
import store from './store'
export default {
name: 'App',
computed: {
count () {
return store.state.count
},
countPlus () {
return store.getters.countPlus
},
countA () {
return store.modules.A.state.count
},
countAPlus () {
return store.modules.A.getters.countPlus
}
},
methods: {
onClick() {
store.dispatch('add', 1)
store.modules.A.dispatch('add', 1)
}
}
}
</script>
<style>
</style>
store.js
import Vue from 'vue'
import Vuexx from './lib'
Vue.use(Vuexx)
export default new Vuexx.Store({
state: {
count: 1
},
getters: {
countPlus: state => state.count + 1
},
mutations: {
add (state, payload) {
state.count += payload
}
},
actions: {
add (context, payload) {
context.commit('add', payload)
}
},
modules: {
A: {
state: {
count: 10
},
getters: {
countPlus: state => state.count + 1
},
mutations: {
add (state, payload) {
state.count += payload
}
},
actions: {
add (context, payload) {
context.commit('add', payload)
}
},
}
}
})
vuexx
let Vue
function install (_Vue) {
if (Vue && Vue === _Vue) {
return
}
Vue = _Vue
applyMixin(Vue)
}
function applyMixin (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// 把$store挂载到各个vue实例中,意义不大,此处不实现了
} else {
// depracated
}
}
function isPromise (val) {
return val && typeof val.then === 'function'
}
function partial (fn, arg) {
return function () {
return fn(arg)
}
}
class Store {
constructor(options, parent) {
this.committing = false
this.state = options.state
this.getters = options.getters
this.mutations = options.mutations
this.actions = options.actions
this.parent = parent
if (options.modules) {
this.modules = {}
Object.keys(options.modules).forEach(moduleName => {
this.modules[moduleName] = new Store(options.modules[moduleName], this)
})
}
this.installModule()
this.resetStoreVM()
}
installModule() {
this.registerMutations()
this.registerActions()
this.regsiterGetters()
}
resetStoreVM () {
const oldVM = this._vm
const computed = {}
if (this.getters) {
Object.keys(this.getters).forEach(key => {
computed[key] = partial(this.getters[key], this)
Object.defineProperty(this.getters, key, {
get: () => {
return this._vm[key]
}
})
})
}
const silent = Vue.config.silent
Vue.config.silent = true
this._vm = new Vue({
data: {
$$state: this.state
},
computed: computed
})
Vue.config.silent = silent
Object.defineProperty(this, 'state', {
get: () => {
return this._vm._data.$$state
}
})
if (oldVM) {
Vue.nextTick(() => {
return oldVM.$destroy()
})
}
}
regsiterGetters () {
if (this.getters) {
const originGetters = this.getters
this.getters = {}
Object.keys(originGetters).forEach(key => {
this.getters[key] = () => {
return originGetters[key](this.state, this.getters)
}
})
}
}
registerMutations () {
if (this.mutations) {
const orginMutations = this.mutations
this.mutations = {}
Object.keys(orginMutations).forEach(key => {
this.mutations[key] = (state, payload) => {
this.withCommit(() => {
orginMutations[key].call(this, state, payload)
})
}
})
}
}
registerActions () {
if (this.actions) {
const originActions = this.actions
this.actions = {}
const context = this
Object.keys(originActions).forEach(key => {
this.actions[key] = (payload) => {
let res = originActions[key].call(this, context, payload)
if (!isPromise(res)) {
res = Promise.resolve(res)
}
return res
}
})
}
}
withCommit (fn) {
const committing = this.committing
this.committing = true
fn()
this.committing = committing
}
commit (type, payload) {
const mutation = this.mutations[type]
mutation.call(this, this.state, payload)
}
dispatch(type, payload) {
const action = this.actions[type]
if (action) {
const res = action.call(this, payload)
return new Promise((resolve, reject) => {
res.then(res => {
resolve(res)
}, error => {
reject(error)
})
})
}
}
}
export default {
install,
Store,
}