当前位置 : 首页/建站知识/深圳外贸网站建设/vuex的原理是什么?

vuex的原理是什么?

发布时间:2021年9月13日 14:21 作者:誉新源


在vuex中,我们将从一些概念开始。


这取决于vuex的工作流程。


修改state的唯一方法是提交mutations。


在异步情况下派发(dispatch)动作,其本质仍然是提交mutations。


在提交mutations之后,VueComponents可以动态呈现组件。


感觉有没有减少了什么,没错,当getters下实现时就会说。


一、vuex实现


下一步,我们将模拟vuex的源代码实现一个简单版本的vuex。


您可点击此处查看完整的源码,并随文章观看。


二、建立存储库


第一,我们需要创建一个store,该store有下列属性和方法。


将createStore方法定义为简单的创建一个Store实例并传递options。


扩展函数创建存储(options){returnnewStore(options)}


让我们看一下Store类的实现。

export class Store {
  constructor(options = {}) {
    const plugins = options.plugins || []
    this._subscribers = []
    this._actionSubscribers = []
    this._actions = Object.create(null)
    this._mutations = Object.create(null)
    this.getters = Object.create(null)

    // 收集 modules
    this._modules = new ModuleCollection(options)

    // 绑定 commit 和 dispatch 到自身
    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) {
      return commit.call(store, type, payload, options)
    }

    const state = this._modules.root.state

    // 安装 module
    installModule(this, state, [], this._modules.root)

    // 初始化 state
    resetStoreState(this, state)

    // 应用插件
    plugins.forEach(plugin => plugin(this))
  }
}

收集 modules

通过ModuleCollection收集 modules,生成一棵 module 树

// store.js
export class Store {
  constructor(options = {}) {
      this._modules = new ModuleCollection(options)
  }
}

// module-collection.js
export default class ModuleCollection {
  constructor(rawRootModule) {
    this.register([], rawRootModule)
  }
    
  register(path, rawModule) {}
}

ModuleCollection中调用了一个注册函数register来注册每个 module

其中path参数指的是当前 moduel 的路劲,可以用来判断层级关系,rawModule则是原始的 module 对象

  register(path, rawModule) {
    const newModule = new Module(rawModule)
    if (path.length === 0) {
      // root 定义为 rawModule
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    //  如果传入有 modules,递归地注册子模块
    if (rawModule.modules) {
      Object.keys(rawModule.modules).forEach(key => {
        const rawChildModule = rawModule.modules[key]
        this.register(path.concat(key), rawChildModule)
      })
    }
  }

register中,通过rawModule我们实例化了一个Module对象,它具有以下属性

  • _rawModule:传入的原对象

  • _children:子 moduels

  • state:传入的原对象的 state 值

  • getChild:获取子 moduel 的方法

  • addChild:新增子 module 的方法

export default class Module {
  constructor(rawModule) {
    this._rawModule = rawModule
    this._children = Object.create(null)
    const rawState = rawModule.state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
}

path.length === 0时,将该newModule作为root的值,也可以叫做父 module

设置完root后,会继续判断有没有modules,如果有的话,递归地注册子模块

    //  如果传入有 modules,递归地注册子模块
    if (rawModule.modules) {
      Object.keys(rawModule.modules).forEach(key => {
        const rawChildModule = rawModule.modules[key]
        this.register(path.concat(key), rawChildModule)
      })
    }

在这里,将每个 module 各自的 key 设置为 path 用来区分层次,加入到root

get(path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }

if (path.length === 0) {
  // ...
} else {
  const parent = this.get(path.slice(0, -1))
  parent.addChild(path[path.length - 1], newModule)
}

最后,我们就得到了这样的一个 module 树

 

 

绑定 commit 和 dispatch

继续回到 store 的构造函数代码

    // 绑定 commit 和 dispatch 到自身
    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) {
      return commit.call(store, type, payload, options)
    }

封装替换原型中的 dispatch 和 commit 方法,将 this 指向当前 store 对象。dispatch 和 commit 方法具体实现如下:

  commit(type, payload) {
    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      return
    }
    entry(payload)
    this._subscribers.slice().forEach(sub => sub(mutation, this.state))
  }

刚开始我们就提到,修改 state 需要提交 mutation,commit就实现了这一过程

dispathcommit的作用相同,不同的是dispath是派发action来提交mutation修改state,我们通常在action中执行异步函数

  dispatch(type, payload) {
    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      return
    }
    try {
      this._actionSubscribers
        .slice()
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (error) {
      console.error(e)
    }

    const result = entry(payload)

    return new Promise((resolve, reject) => {
      result
        .then(res => {
          try {
            this._actionSubscribers
              .filter(sub => sub.after)
              .forEach(sub => sub.after(action, this.state))
          } catch (error) {
            console.error(e)
          }
          resolve(res)
        })
        .catch(error => {
          try {
            this._actionSubscribers
              .filter(sub => sub.error)
              .forEach(sub => sub.error(action, this.state, error))
          } catch (e) {
            console.error(e)
          }
          reject(error)
        })
    })
  }

commitdispath中,都执行了各自的订阅函数集合_subscribers_actionSubscribers

_subscribers的订阅函数中传入了当前的 mutation 对象和当前的 state,这是提供给插件的参数

_actionSubscribers还将函数分为了before,after,error类型

module 安装

module 的安装是为了封装 mutations、actions、getters 函数,传入需要的参数

封装 mutation

  if (module._rawModule.mutations) {
    Object.keys(module._rawModule.mutations).forEach(key => {
      const mutation = module._rawModule.mutations[key]
      store._mutations[key] = payload =>
        // 惰性获取 state
        mutation.call(store, getNestedState(store.state, path), payload)
    })
  }

封装 action

  if (module._rawModule.actions) {
    Object.keys(module._rawModule.actions).forEach(key => {
      const action = module._rawModule.actions[key]
      store._actions[key] = payload => {
        let res = action.call(
          store,
          {
            dispatch: store.dispatch,
            commit: store.commit,
            getters: store.getters,
            state: getNestedState(store.state, path),
          },
          payload
        )
        if (!(res instanceof Promise)) {
          res = Promise.resolve(res)
        }
        return res
      }
    })
  }

封装 getter

  if (module._rawModule.getters) {
    Object.keys(module._rawModule.getters).forEach(key => {
      const getter = module._rawModule.getters[key]
      store.getters[key] = () =>
        // 惰性获取 state
        getter(getNestedState(store.state, path), store.getters)
    })
  }

最后递归安装子 module

  Object.keys(module._children).forEach(key =>
    installModule(store, rootState, path.concat(key), module._children[key])
  )

需要注意的一点是,获取 state 的时候需要惰性的获取,因为在使用 vuex 的过程中,state 发生会改变,如果封装函数的时候固定 state,会有不符合预期的行为

初始化 state

然后是resetStoreState函数

export function resetStoreState(store, state) {
  store._state = reactive({
    data: state,
  })
}

state变为响应式的,这样就可以在 vuex 修改了 state 之后,更新视图了

关于 vue3 的数据响应式原理可以看我的这篇文章 vue3 数据响应式原理分析

看到这你可能会有点迷惑,怎么实例上的是_state而不是state呢?其实还有store中还有一个 getter 取 state 的值

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

  set state(v) {}

这里我们也可以发现,直接设置 state 的值是无效的

应用插件

我们先来实现一个打印修改 state 前后变化的插件:logger

export const logger = store => {
  let prevState = deepClone(store.state)

  store.subscribe((mutation, state) => {
    const nextState = deepClone(state)

    const formattedTime = getFormattedTime()
    const message = `${mutation.type}${formattedTime}`
    console.log('%c prev state', 'color: #9E9E9E; font-weight: bold', prevState)
    console.log('%c mutation', 'color: #03A9F4; font-weight: bold', message)
    console.log('%c next state', 'color: #4CAF50; font-weight: bold', nextState)

    prevState = nextState
  })
}

很简单,监听一下mutation就可以啦

store中会注入每个插件

 plugins.forEach(plugin => plugin(this)

实验一下效果,如果你 clone 了源码,那么你可以在安装了依赖之后,执行

yarn dev

打开控制台,来查看效果