vuex4实现原理

vuex的实现原理

创建demo

  • app.vue





  • store/index.js
import {
  createStore
} from 'vuex'

export default createStore({
  state: {
    count: 0
  },
  getters: {
    double (state) {
      return state.count * 2
    }
  },
  mutations: {
    add (state, payload) {
      state.count += payload
    }
  },
  actions: {
    asyncAdd ({
      commit
    }, payload) {
      setTimeout(() => {
        commit('add', payload)
      }, 1000)
    }
  },
  modules: {
    aCount: {
      namespaced: true,
      state: {
        count: 0
      },
      mutations: {
        add (state, payload) {
          state.count += payload
        }
      },
      modules: {
        cCount: {
          // namespaced: true,
          state: {
            count: 0
          },
          mutations: {
            add (state, payload) {
              state.count += payload
            }
          }
        }
      }
    },
    bCount: {
      namespaced: true,
      state: {
        count: 0
      },
      mutations: {
        add (state, payload) {
          state.count += payload
        }
      }
    }
  }
})

// class Store {
//   dispatch () {
//     console.log(this)
//   }
// }

// const { dispatch } = new Store()
// dispatch()


  • 测试功能是否正常

初始化vuex

  • 在store/indexjs和app.vue中,全部换成自己的vuex
  • import { useStore } from “@/vuex”;
  • import {createStore} from ‘@/vuex’
  • 在src下添加vuex文件夹,新增index.js,导出两个方法
import { inject } from 'vue'
const storeKey = 'store'
class Store {
  constructor (options) {
    this.aa = 100
  }
  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}

export function createStore (options) {
  return new Store(options)
}

export function useStore (injectKey = null) {
  return inject(injectKey !== null ? injectKey : storeKey)
}

  • 测试功能是否正常
  • 在app.vue中测试{{$store.aa}} 是否输出100

初始化state

  • 把options中的state,挂载到_state上面,使用vue提供的数据响应方法,初始化state
  • 访问 $store._state.data.count,显示0,则说明成功
  • 使用代理,把state代理到_state.data上面
import { inject, reactive } from 'vue'

const storeKey = 'store'
class Store {
  constructor (options) {
    this._state = reactive({ data: options.state })
  }

  get state () {
    return this._state.data
  }

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}
export function createStore (options) {
  return new Store(options)
}

export function useStore (injectKey = null) {
  return inject(injectKey !== null ? injectKey : storeKey)
}

  • 测试app.vue中的 计数器:{{ count }} {{ $store.state.count }} 是否正常显示
    在这里插入图片描述

初始化getters

  • 获取options上的getters
  • 取store.getters.double,store.getters必须是一个对象
  • 创建一个新的store.getters
  • 给store.getters做代理,get的时候,取options上的getters方法,传入参数为store.state
import { inject, reactive } from 'vue'
import { forEachValue } from './utils'

const storeKey = 'store'
class Store {
  constructor (options) {
    const store = this
    store._state = reactive({ data: options.state })
    // getters
    const _getters = options.getters
    store.getters = {}
    forEachValue(_getters, function (fn, key) {
      // ƒ double(state) {
      //   return state.count * 2;
      // }
      // 'double'
      Object.defineProperty(store.getters, key, {
        enumerable: true,
        get: () => fn(store.state)
      })
    })
  }

  get state () {
    return this._state.data
  }

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}
export function createStore (options) {
  return new Store(options)
}

export function useStore (injectKey = null) {
  return inject(injectKey !== null ? injectKey : storeKey)
}
  • utils.js
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}
  • 测试app.vue ====> double:{{ double }} {{ $store.getters.double }}
    vuex4实现原理_第1张图片

初始化mutation和action

  • 在store上挂载_mutations 和_actions
  • 给_mutations 和_actions这两个对象赋值 key ==》 方法
  • store调用commit和dispatch时候,就是调用_mutations 和_actions上面挂载的方法。
import { inject, reactive } from 'vue'
import { forEachValue } from './utils'

const storeKey = 'store'
class Store {
  constructor (options) {
    const store = this
    store._state = reactive({ data: options.state })
    // getters
    const _getters = options.getters
    store.getters = {}
    forEachValue(_getters, function (fn, key) {
      // ƒ double(state) {
      //   return state.count * 2;
      // }
      // 'double'
      Object.defineProperty(store.getters, key, {
        enumerable: true,
        get: () => fn(store.state)
      })
    })
    // mutations和actions
    const _mutations = options.mutations
    const _actions = options.actions
    store._mutations = Object.create(null)
    store._actions = Object.create(null)
    forEachValue(_mutations, function (mutation, key) {
      // ƒ add(state, payload) {
      //   state.count += payload;
      // }
      // 'add'
      store._mutations[key] = payload => {
        mutation.call(store, store.state, payload)
      }
    })
    forEachValue(_actions, function (action, key) {
      // ƒ  asyncAdd({
      //   commit
      // }, payload) {
      //   setTimeout(() => {
      //     commit('add', payload);
      //   }, 1000);
      // }
      // 'asyncAdd'
      store._actions[key] = payload => {
        action.call(store, store, payload)
      }
    })
  }

  get state () {
    return this._state.data
  }

  commit = (type, payload) => {
    const store = this
    store._mutations[type](payload)
  }

  dispatch = (type, payload) => {
    const store = this
    store._actions[type](payload)
  }

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}
export function createStore (options) {
  return new Store(options)
}

export function useStore (injectKey = null) {
  return inject(injectKey !== null ? injectKey : storeKey)
}

  • 测试点击事件,正常则最简单的vuex已经实现
    vuex4实现原理_第2张图片

vuex进阶

  • 上面只是实现了vuex的一部分功能,vuex最重要的module功能未实现,action会返回一个promise也没有是实现
  • 如果只是了解原理,上面的demo就够了

重构项目

  • 拆分目录如下
    vuex4实现原理_第3张图片
  • 拆分后的store.js
import { storeKey } from './injectKey'
export default class Store {
  constructor (options) {

  }
  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}

初始化moudles

  • store.js
  • 在store上面挂载_modules,让数据 state 变为树形结构

import { storeKey } from './injectKey'
import ModuleCollection from './module/module-collection'
export default class Store {
  constructor (options) {
    const store = this
    store._modules = new ModuleCollection(options)
  }

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}

  • module/module-collection
  • 创建树形结构,并在_children中挂载modules的子模块
import { forEachValue } from '../utils'
import Module from './module'
export default class ModuleCollection {
  constructor (rootModule) {
    this.root = null
    this.register(rootModule, [])
  }

  register (rawModule, path) {
    // this.root = {
    //   _raw: rawModule,
    //   state: rawModule.state,
    //   _children: {}
    // }
    const newMoudule = new Module(rawModule)
    if (path.length === 0) {
      this.root = newMoudule
    } else {
      // 往前截一位
      const parent = path.slice(0, -1).reduce((module, current) => {
        return module.getChild(current)
      }, this.root)
      parent.addChild(path[path.length - 1], newMoudule)
    }

    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        console.log(rawChildModule, key)
        // {namespaced: true, state: {…}, mutations: {…}, modules: {…}}modules: {cCount: {…}}mutations: {add: ƒ}namespaced: truestate: {count: 0}[[Prototype]]: Object 'aCount'
        this.register(rawChildModule, path.concat(key))
      })
    }
  }
}

  • module.js
  • 生成树的基本结构,并在原型上添加方法getChild ,addChild 来改变实例的_children属性
export default class Module {
  constructor (rawModule) {
    this._raw = rawModule
    this.state = rawModule.state
    this._children = {}
  }

  getChild (key) {
    return this._children[key]
  }

  addChild (key, module) {
    this._children[key] = module
  }
}

  • 最后挂载到store上的_modules如下图
    vuex4实现原理_第4张图片

初始化state

  • installModule.js
  • 接受四个参数 (store, rootState, path, module)
export function installModule (store, rootState, path, module) {
  const isRoot = !path.length

  if (!isRoot) {
    const parentState = path.slice(0, -1).reduce((state, key) => state[key], rootState)
    parentState[path[path.length - 1]] = module.state
  }
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child)
  })
}

  • module.js 添加forEachChild方法
forEachChild (fn) {
    forEachValue(this._children, fn)
  }
  • 这时store上的state
    vuex4实现原理_第5张图片

展示state的数据

  • 这时候,页面还是报错找不到count
  • 添加 resetStoreState(store,state)方法
import { reactive } from 'vue'
export function resetStoreState (store, state) {
  store._state = reactive({ data: state })
}

  • store.js

import { storeKey } from './injectKey'
import ModuleCollection from './module/module-collection'
import { installModule } from './installModule'

import { resetStoreState } from './resetStoreState'
export default class Store {
  constructor (options) {
    const store = this
    // 初始化installModule
    store._modules = new ModuleCollection(options)

    // 初始化state
    const state = store._modules.root.state
    installModule(store, state, [], store._modules.root)
    resetStoreState(store, state)
  }

  get state () {
    return this._state.data
  }

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}

  • 页面展示正常,点击事件可用,则说明正常运行
    vuex4实现原理_第6张图片

初始化getters

  • 和基础版一样,需要在store上面添加一个_wrappedGetters属性
  • 做代理访问store.getters
  • store.js
 constructor (options) {
    const store = this
    // 初始化installModule
    store._modules = new ModuleCollection(options)
    store._wrappedGetters = Object.create(null)

    // 初始化state
    const state = store._modules.root.state
    installModule(store, state, [], store._modules.root)
    resetStoreState(store, state)
    console.log(store)
  }
  • installModule.js 中初始化getters
function getNestedState(state,path){
  return path.reduce((state,key)=>state[key],state)
}
// getters
  module.forEachGetter((getter,key)=>{
    store._wrappedGetters[key] = ()=>{
      return getter(getNestedState(store.state,path))
    }
  })
  • module.js 添加方法forEachGetter
  • 让getter挂载到对应的module上
forEachGetter (fn) {
    if (this._raw.getters) {
      forEachValue(this._raw.getters, fn)
    }
  }

参照getters,初始化mutation和action

  • 对应的代码入下
  • store.js

import { storeKey } from './injectKey'
import ModuleCollection from './module/module-collection'
import { installModule } from './installModule'

import { resetStoreState } from './resetStoreState'
export default class Store {
  constructor (options) {
    const store = this
    // 初始化installModule
    store._modules = new ModuleCollection(options)
    store._wrappedGetters = Object.create(null)
    store._mutations = Object.create(null)
    store._actions = Object.create(null)
    // 初始化state
    const state = store._modules.root.state
    installModule(store, state, [], store._modules.root)
    resetStoreState(store, state)
    console.log(store)
  }
  
  commit = (type, payload) => {
    const entry = this._mutations[type] || []
    entry.forEach(handler => handler(payload))
  }

  dispatch = (type, payload) => {
    const entry = this._actions[type] || []
    return Promise.all(entry.map(handler => handler(payload)))
  }

  get state () {
    return this._state.data
  }

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}
  • installModule.js
import { isPromise } from './utils'
export function installModule (store, rootState, path, module) {
  const isRoot = !path.length

  if (!isRoot) {
    const parentState = path.slice(0, -1).reduce((state, key) => state[key], rootState)
    parentState[path[path.length - 1]] = module.state
  }

  // getters
  module.forEachGetter((getter, key) => {
    store._wrappedGetters[key] = () => {
      return getter(getNestedState(store.state, path))
    }
  })
  // mutation
  module.forEachMoutation((mutation, key) => {
    const entry = store._mutations[key] || (store._mutations[key] = [])
    entry.push((payload) => {
      mutation.call(store, getNestedState(store.state, path), payload)
    })
  })
  // actions
  module.forEachAction((action, key) => {
    const entry = store._actions[key] || (store._actions[key] = [])
    entry.push((payload) => {
      const res = action.call(store, store, payload)
      if (!isPromise(res)) {
        return Promise.resolve(res)
      }
      return res
    })
  })
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child)
  })
}

function getNestedState (state, path) {
  return path.reduce((state, key) => state[key], state)
}

  • module.js
import { forEachValue } from '../utils'
export default class Module {
  constructor (rawModule) {
    this._raw = rawModule
    this.state = rawModule.state
    this._children = {}
  }

  getChild (key) {
    return this._children[key]
  }

  addChild (key, module) {
    this._children[key] = module
  }

  forEachChild (fn) {
    forEachValue(this._children, fn)
  }

  forEachGetter (fn) {
    if (this._raw.getters) {
      forEachValue(this._raw.getters, fn)
    }
  }

  forEachMoutation (fn) {
    if (this._raw.mutations) {
      forEachValue(this._raw.mutations, fn)
    }
  }

  forEachAction (fn) {
    if (this._raw.actions) {
      forEachValue(this._raw.actions, fn)
    }
  }
}

  • resetStoreState.js
import { reactive } from 'vue'
import { forEachValue } from './utils'
export function resetStoreState (store, state) {
  store._state = reactive({ data: state })
  const wrappedGetters = store._wrappedGetters
  store.getters = {}
  forEachValue(wrappedGetters, function (getter, key) {
    Object.defineProperty(store.getters, key, {
      get: () => getter(),
      enumerable: true
    })
  })
}

  • 代码验证 store,页面正常,点击按钮无error,可进行下一步
    vuex4实现原理_第7张图片

namespaced的实现

  • 上面的代码,点击后,执行了多个add的方法
    vuex4实现原理_第8张图片
  • 这时候需要namespaced对方法的key进行按照path分类
  • module-collection.js 添加 getNamespaced方法
getNamespaced (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }
  • module.js上面添加 this.namespaced = rawModule.namespaced
  • 在installModule.js中,给每次便利循环的方法添加namespaced
// getters
  module.forEachGetter((getter,key)=>{
    store._wrappedGetters[namespaced + key] = ()=>{
      return getter(getNestedState(store.state,path))
    }

  })


  // mutation
  module.forEachMoutation((mutation,key)=>{
   const entry = store._mutations[namespaced+key] ||  (store._mutations[namespaced + key] = [])
   entry.push((payload)=>{
    mutation.call(store,getNestedState(store.state,path),payload)
   })
  })
  // actions
  module.forEachAction((action,key)=>{
    const entry = store._actions[namespaced + key] ||  (store._actions[namespaced + key] = [])
    entry.push((payload)=>{
    let res = action.call(store,store,payload)
    if(!isPromise(res)){
      return Promise.resolve(res)
    }
    return res
    })
  })

你可能感兴趣的:(vue,js插件,javascript,vue.js,前端)