vuex 源码解析部分

179 阅读2分钟

import { forEachValue } from "../src/vuex/util"

源码的第一步

vuex 的安装 重点的逻辑点是什么 重点的代码是什么

// 重点的逻辑是:

// 1. vuex 任何插件都有install的方法, Vue.use() 就会默认调用这个安装方法

// 2. 从 使用上看 还有有 Store这个 因为 需要 new Vue ({

// store

// }) 这样保证每一个组件的注入store这个属性 ,所以还需要导出这个类

// 重点的代码是:

let Vue // 搞一个全局的Vue

const install = (_Vue) => {

Vue= _Vue // _Vue是 Vue的构造函数

// 需要根据组件中的store注入每一个组件(子组件) Vue.mixin

applyMixin(Vue)

}

function applyMixin (Vue) {

Vue.mixin({

beforeCreate: vuexInit

})

}

function vuexInit () {

// 给所有组件增加 $store属性 指向我们创建store 的实例

const option = this.$options

if (options.store) { // 说明这个是根实例

this.store=options.store = options.store

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

this.store=options.store = options.store

}

}

源码的第二步

vue 中 的状态实现 状态state

1。 首先这个 options 明白是什么

export class Store { // 容器的初始化

constructor (options) { // options就是你 new Vuex.$Store({state, mutations, actions 这些})

const state = options.state // 数据变化更新视图, Vue的核心逻辑是依赖收集

// 添加状态逻辑

// 这里的依据 是 Vue 中的 data 都是相应式的 所以把这个$$state 放在里边,也会是响应式的 但是 Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

this._vm = new Vue({

data: { // Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

state:state//会将state: state // 会将 state 对应的对象 都通过 defineProperty来进行数据劫持

},

})

}

get state () {

return this._vm_dat.$$state

}

}

2. 其次要明白 state 怎么变成响应式的数据

源码的第三步 简单版的实现 主要是 getters mutations 这个

gettes实现

export class Store { // 容器的初始化

constructor (options) { // options就是你 new Vuex.$Store({state, mutations, actions 这些})

const state = options.state // 数据变化更新视图, Vue的核心逻辑是依赖收集

// 添加状态逻辑

// 这里的依据 是 Vue 中的 data 都是相应式的 所以把这个$$state 放在里边,也会是响应式的 但是 Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

this._vm = new Vue({

data: { // Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

state:state//会将state: state // 会将 state 对应的对象 都通过 defineProperty来进行数据劫持

},

})

this.getters = {}

const computed = {}

// 通过遍历options即是传入的 getters 循环 拿到 里边的方法变成 this.getters的属性 然后 这个属性 通过 defineProperty 变为 一个响应式 并且把state传入

Object.keys(options.getters).forEach(key => {

Object.defineProperty(this.getters, key, {

get: () => options.getters[key](this.state)

})

})

// 为了更优化代码 抽离出这个工具函数

const forEachValue = (obj, callback) => {

Object.keys(obj).forEach(key => callback(obj[key], key))

}

// 所以 变为

forEachValue(options.getters,(fn,key) => {

Object.defineProperty(this.getters, key, {

get: () => fn(this.state)

})

})

}

get state () {

return this._vm_dat.$$state

}

}

// 目前的问题就是 getters 这些调用一次就会执行一次 没有缓存,这里用到 computed

export class Store { // 容器的初始化

constructor (options) { // options就是你 new Vuex.$Store({state, mutations, actions 这些})

const state = options.state // 数据变化更新视图, Vue的核心逻辑是依赖收集

// 添加状态逻辑

// 这里的依据 是 Vue 中的 data 都是相应式的 所以把这个$$state 放在里边,也会是响应式的 但是 Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

const computed = {}

this.getters = {}

// 所以 变为

forEachValue(options.getters,(fn,key) => {

computed[key] = () => { // 将用户的getters 放在 Vue的实例上

return fn(this.state)

}

Object.defineProperty(this.getters, key, {

get: () => this._vm[key]// 当我取值时候用的 是计算属性的逻辑

})

})

// 这里 特别注意顺序问题, 要先放在计算属性中,在创造实例

this._vm = new Vue({

data: { // Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

state:state//会将state: state // 会将 state 对应的对象 都通过 defineProperty来进行数据劫持

},

computed

})

}

get state () {

return this._vm_dat.$$state

}

}

mutations actions 实现

export class Store { // 容器的初始化

constructor (options) { // options就是你 new Vuex.$Store({state, mutations, actions 这些})

const state = options.state // 数据变化更新视图, Vue的核心逻辑是依赖收集

// 添加状态逻辑

// 这里的依据 是 Vue 中的 data 都是相应式的 所以把这个$$state 放在里边,也会是响应式的 但是 Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

const computed = {}

this.getters = {}

// 所以 变为

forEachValue(options.getters,(fn,key) => {

computed[key] = () => { // 将用户的getters 放在 Vue的实例上

return fn(this.state)

}

Object.defineProperty(this.getters, key, {

get: () => this._vm[key]// 当我取值时候用的 是计算属性的逻辑

})

})

// 这里 特别注意顺序问题, 要先放在计算属性中,在创造实例

this._vm = new Vue({

data: { // Vue 属性如果是$开头, 默认不会将这个属性挂载到 vm上

state:state//会将state: state // 会将 state 对应的对象 都通过 defineProperty来进行数据劫持

},

computed

})

this.mutations = {}

this.actions = {}

forEachValue (options.mutations, (fn, key) => {

this.mutations[key] = (payload) => fn(this.state, payload)

})

// 在严格模式下 mutations 和 actions是有区别的

forEachValue (options.actions, (fn, key) => {

this.actions[key] = (payload) => fn(this, payload) // this 其实就是这个store实例 是因为这个可以解构出 {commit}

})

}

commit = (type, payload) => { //箭头函数保证this 指向当前store的实例 type对应的就是 mutations 里边的 函数

// 调用commit 就是 去找刚刚绑定好的 mutation

this.mutations[type](payload)

}

dispatch = (type, payload) => {

this.actions[type](payload)

}

get state () {

return this._vm_dat.$$state

}

}

源码的第四步 模块收集

重点的逻辑点是 要把数据格式化成 一个 树形的数据 然后对这个树形的数据进行解析

树形结构 是这样的

// this.root = {

// _raw: '根模块',

// _children: {

// a: {

// _raw: 'a模块',

// _children: {

// c ...

// },

// state: 'a的状态'

// },

// a: {

// _raw: 'a模块',

// _children: {

// c ...

// },

// state: 'a的状态'

// }

// },

// state: '根模块自己的状态'

// }

class ModuleCollectioin {

constructor (options) {

this.register([], options) // 这里的数组是栈

}

register(path, rootModule) {

// let newModule = {

// _raw: rootModule,

// state: rootModule.state,

// _children: {}

// }

let newModule = new Module(rootModule)

if (path.length === 0) { // s说明是根

this.root = newModule

} else { // rootModule.modules 这个条件说明 有儿子

// 这里就需要寻找 需要拼的路径 [root, a, c]

let parent = path.slice(0, -1).reduce((memo,current) => {

// return memo._children[current]

return memo.getChild(current)

}, this.root)

// parent._children[path[path.length-1]] = newModule

parent.addChild(path[path.length -1], newModule)

}

if (rootModule.modules) {

// 这里就需要递归寻找

forEachValue(rootModule.modules, (module, moudleName => {

this.register(path.concat(moudleName), module)

}))

}

}

}

源码第五步 需要模块的数据出来了 需要对模块进行 遍历安装

这里就是遍历 我们得倒的模块树的 actions muatioins getters 定义在我们 最外层store上

然后将所有子模块的状态安装到父级模块上

const installModule = (store, rootState, path, module) => {

if (path.length > 0) {

let parent = path.slice(0, -1).reduce((memo, current) => {

return memo[current]

}, rootState)

Vue.set(parent, path[path.length - 1], module.state )

}

module.forEachMutations ((mutation, key ) => {

store._mutations[key] = store._mutations[key] || []

store.mutations[key].push((payload) => {

mutation.call(store, module.state, payload)

})

})

module.forEachActions ((actions, key) => {

store._action[key] = store._action[key] || []

store._actions[key].push((payload) => {

actions.call(store, store, payload)

})

})

module.forEachGetter((getter, key) => {

// 模块中 getter 的名字重复会被覆盖

store._wrappedGetter[key] = function () {

return getter(module.state)

}

})

moudle.forEachChild((child, key) => {

// 递归加载模块

installModule(store, rootState, path.concat(key), child)

})

}

// 将状态 和 getters 都定义在当前 vm上 监听???

resetStoreVM(this, state)

function resetStoreVM(store, state) {

const computed = {}

store.getters = {}

forEachValue(store._wrappedGetters, (fn, key) => {

computed[key] = () => {

return fn()

}

Object.defineProperty(store.getters, key, {

get : () => store._vm[key]

})

})

store._vm = new Vue({

data: {

}, computed }) } // 最后就是命名空间参考 代码