Vuex理解与实现

125 阅读2分钟

定义

Vuex的出现是主要是为了组件层级嵌套过深时的组件通信问题。采用中心化的思想,集中管理需要所有组件的状态的一个仓库,并定义一套规范保证状态以往可预测的方向发生改变。本质上是用一个对象存储组件的状态,利用Vuedata函数里的数据是响应式的,使用Vue.mixin()方法,在data数据进行初始化前,将store注入到每个Vue实例中,然后在初始化的时候实现了响应式,在视图与数据之间的连接。定义了两种行为,Actions 用来处理异步状态变更(内部还是调用 Mutations),Mutations 处理同步的状态变更,整个链路应该是一个闭环,单向的,完美契合 FLUX 的思想。

图片.png

Vuex注册

组件注册使用Vue.use(Vuex),需提供install方法

/**
* store.js - store 注册
*/
let Vue

export function install(_Vue) {
  // 拿到 Vue 的构造器,存起来
  Vue = _Vue
  // 通过 mixin 注入到每一个vue实例 👉 https://cn.vuejs.org/v2/guide/mixins.html
  Vue.mixin({ beforeCreate: vuexInit })
  
  function vuexInit () {
    const options = this.$options
    // 这样就可以通过 this.$store 访问到 Vuex 实例,拿到 store 了
    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
    }
  }
}

响应式

/**
* store.js - 利用 VUE 实现响应式
*/

// this.$store.state
export class Store {
  constructor(options = {}) {
    resetStoreVM(this, options.state)
  }
  
  get state () {
    return this._vm._data.$$state
  }
}

function resetStoreVM(store, state) {
  // 因为 vue 实例的 data 是响应式的,正好利用这一点,就可以实现 state 的响应式
  store._vm = new Vue({
    data: {
      $$state: state
    }
  })
}

衍生数据(getters)

/**
* store.js - 衍生数据(getters)
*/
export class Store {
  constructor(options = {}) {
    
    const state = options.state
    
    resetStoreVM(this, state)
    
    // 我们用 getters 来收集衍生数据 computed
    this.getters = {}
    
    _.forEach(this.getters, (name, getterFn) => {
      Object.defineProperty(this.getters, name, {
        get: () => getterFn(this.state)
      })
    })
  }
  
  get state () {
    return this._vm._data.$$state
  }
}

function resetStoreVM(store, state) {
  store._vm = new Vue({
    data: {
      $$state: state
    }
  })
}

Actions/Mutations

/**
* store.js - Actions/Mutations 行为改变数据
*/
export class Store {
  constructor(options = {}) {
    
    const state = options.state
    
    resetStoreVM(this, state)
    
    this.getters = {}
    
    _.forEach(options.getters, (name, getterFn) => {
      Object.defineProperty(this.getters, name, {
        get: () => getterFn(this.state)
      })
    })
    
    // 定义的行为,分别对应异步和同步行为处理
    this.actions = {}
    this.mutations = {}
    
    _.forEach(options.mutations, (name, mutation) => {
      this.mutations[name] = payload => {
        // 最终执行的就是 this._vm_data.$$state.xxx = xxx 这种操作
        mutation(this.state, payload)
      }
    })
    
    _.forEach(options.actions, (name, action) => {
      this.actions[name] = payload => {
        // action 专注于处理异步,这里传入 this,这样就可以在异步里面通过 commit 触发 mutation 同步数据变化了
        action(this, payload)
      }
    })
  }
  
  // 触发 mutation 的方式固定是 commit
  commit(type, payload) {
    this.mutations[type](payload)
  }
  
  // 触发 action 的方式固定是 dispatch
  dispatch(type, payload) {
    this.actions[type](payload)
  }
  
  get state () {
    return this._vm._data.$$state
  }
}

function resetStoreVM(store, state) {
  store._vm = new Vue({
    data: {
      $$state: state
    }
  })
}

拆分出多个module

// module 可以对状态模型进行分层,每个 module 又含有自己的 state、getters、actions 等

// 定义一个 module 基类
class Module {
	constructor(rawModule) {
    this.state = rawModule || {}
    this._rawModule = rawModule
    this._children = {}
  }
  
  getChild (key) {
    return this._children[key]
  }
  
   addChild (key, module) {
    this._children[key] = module
  }
}

// module-collection.js 把 module 收集起来
class ModuleCollection {
  constructor(options = {}) {
    this.register([], options)
  }
  
  register(path, rawModule) {
    const newModule = new Module(rawModule)
    if (path.length === 0 ) {
      // 如果是根模块 将这个模块挂在到根实例上
      this.root = newModule
    }
    else {
      const parent = path.slice(0, -1).reduce((module, key) => {
        return module.getChild(key)
      }, this.root)
      
      parent.addChild(path[path.length - 1], newModule)
    }
    
    // 如果有 modules,开始递归注册一波
    if (rawModule.modules) {
      _.forEach(rawModule.modules, (key, rawChildModule) => {
        this.register(path.concat(key), rawChildModule)
      })
    }
  }
}

// store.js 中
export class Store {
  constructor(options = {}) {
    // 其余代码...
    
    // 所有的 modules 注册进来
    this._modules = new ModuleCollection(options)
    
    // 但是这些 modules 中的 actions, mutations, getters 都没有注册,所以我们原来的方法要重新写一下
    // 递归的去注册一下就行了,这里抽离一个方法出来实现
    installModule(this, this.state, [], this._modules.root);
  }
}

function installModule(store, state, path, root) {
  // getters
  const getters = root._rawModule.getters
  if (getters) {
    _.forEach(getters, (name, getterFn) => {
      Object.defineProperty(store.getters, name, {
        get: () => getterFn(root.state)
      })
    })
  }
  
  // mutations
  const mutations = root._rawModule.mutations
  if (mutations) {
    _.forEach(mutations, (name, mutation) => {
      let _mutations = store.mutations[name] || (store.mutations[name] = [])
      _mutations.push(payload => {
        mutation(root.state, payload)
      })
      
      store.mutations[name] = _mutations
    })
  }
  
  // actions
  const actions = root._rawModule.actions
  if (actions) {
    _.forEach(actions, (name, action) => {
      let _actions = store.actions[name] || (store.actions[name] = [])
      _actions.push(payload => {
        action(store, payload)
      })
      
      store.actions[name] = _actions
    })
  }
  
  // 递归
  _.forEach(root._children, (name, childModule) => {
    installModule(this, this.state, path.concat(name), childModule)
  })
}

以上只是以最简化的代码实现了 vuex 核心的 state module actions mutations getters 机制,如果对源代码感兴趣,可以看看这篇文章