资讯专栏INFORMATION COLUMN

vuex源码分析(一)

keithyau / 2627人阅读

摘要:的源码分析系列大概会分为三篇博客来讲,为什么分三篇呢,因为写一篇太多了。这段代码检测是否存在,如果不存在那么就调用方法这行代码用于确保在我们实例化之前,已经存在。每一个集合也是的实例。

vuex的源码分析系列大概会分为三篇博客来讲,为什么分三篇呢,因为写一篇太多了。您看着费劲,我写着也累

这是vuex的目录图

分析vuex的源码,我们先从index入口文件进行分析,入口文件在src/store.js文件中

export default {
 // 主要代码,状态存储类
  Store,
  // 插件安装
  install,
  // 版本
  version: "__VERSION__",
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

一个一个来看Store是vuex提供的状态存储类,通常我们使用Vuex就是通过创建Store的实例,
install方法是配合Vue.use方法进行使用,install方法是用来编写Vue插件的通用公式,先来看一下代码

export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== "production") {
      console.error(
        "[vuex] already installed. Vue.use(Vuex) should be called only once."
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

这个方法的作用是什么呢:当window上有Vue对象的时候,就会手动编写install方法,并且传入Vue的使用。
在install中法中有applyMixin这个函数,这个函数来自云src/mixin.js,下面是mixin.js的代码

export default function (Vue) {
// 判断VUe的版本
  const version = Number(Vue.version.split(".")[0])
// 如果vue的版本大于2,那么beforeCreate之前vuex进行初始化
  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // 兼容vue 1的版本
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */

// 给vue的实例注册一个$store的属性,类似咱们使用vue.$route
  function vuexInit () {
    const options = this.$options
    // store injection
    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
    }
  }
}

这段代码的总体意思就是就是在vue的声明周期中进行vuex的初始化,并且对vue的各种版本进行了兼容。vuexInit就是对vuex的初始化。为什么我们能在使用vue.$store这种方法呢,因为在vuexInit中为vue的实例添加了$store属性
Store构造函数
先看constructor构造函数

constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    // 判断window.vue是否存在,如果不存在那么就安装
    if (!Vue && typeof window !== "undefined" && window.Vue) {
      install(window.Vue)
    }
// 这个是在开发过程中的一些环节判断,vuex要求在创建vuex 
// store实例之前必须先使用这个方法Vue.use(Vuex),并且判断promise是否可以使用
    if (process.env.NODE_ENV !== "production") {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== "undefined", `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `Store must be called with the new operator.`)
    }
    // 提取参数
    const {
      plugins = [],
      strict = false
    } = options

    let {
      state = {}
    } = options
    if (typeof state === "function") {
      state = state() || {}
    }

    // store internal state
    // 初始化store内部状态,Obejct.create(null)是ES5的一种方法,Object.create(
    // object)object是你要继承的对象,如果是null,那么就是创建一个纯净的对象
    this._committing = false 
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    // 定义dispatch方法
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    // 定义commit方法
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    // 严格模式
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 初始化根模块,递归注册所有的子模块,收集getters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 重置vm状态,同时将getters转换为computed计算属性
    resetStoreVM(this, state)

    // apply plugins
    // 执行每个插件里边的函数
    plugins.forEach(plugin => plugin(this))

    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }

因为vuex是由es6进行编写的,我真觉得es6的模块简直是神器,我以前就纳闷了像Echarts这种8万行的库,他们是怎么写出来的,上次还看到一个应聘百度的问Echarts的源码没有。可怕!后来才知道模块化。如今es6引入了import这种模块化语句,让我们编写庞大的代码变得更加简单了。

if (!Vue && typeof window !== "undefined" && window.Vue) {
      install(window.Vue)
    }

这段代码检测window.Vue是否存在,如果不存在那么就调用install方法

assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)

这行代码用于确保在我们实例化 Store之前,vue已经存在。

assert(typeof Promise !== "undefined", `vuex requires a Promise polyfill in this browser.`)

这一行代码用于检测是否支持Promise,因为vuex中使用了Promise,Promise是es6的语法,但是有的浏览器并不支持es6所以我们需要在package.json中加入babel-polyfill用来支持es6
下面来看接下来的代码

const {
      plugins = [],
      strict = false
    } = options

这利用了es6的解构赋值拿到options中的plugins。es6的解构赋值在我的《前端面试之ES6》中讲过,大致就是[a,b,c] = [1,2,3] 等同于a=1,b=2,c=3。后面一句的代码类似取到state的值。

this._committing = false 
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()

这里主要用于创建一些内部的属性,为什么要加_这是用于辨别属性,_就表示内部属性,我们在外部调用这些属性的时候,就需要小心。

this._committing 标志一个提交状态
this._actions 用来存储用户定义的所有actions
this._actionSubscribers 
this._mutations 用来存储用户定义所有的 mutatins
this._wrappedGetters 用来存储用户定义的所有 getters 
this._modules 
this._subscribers 用来存储所有对 mutation 变化的订阅者
this._watcherVM 是一个 Vue 对象的实例,主要是利用 Vue 实例方法 $watch 来观测变化的
commit和dispatch方法在过后再讲

installModule(this, state, [], options)
是将我们的options传入的各种属性模块注册和安装
resetStoreVM(this, state)
resetStoreVM 方法是初始化 store._vm,观测 state 和 getters 的变化;最后是应用传入的插件
下面是installModule的实现

function installModule (store, rootState, path, module, hot) {

  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

这个函数的主要作用是什么呢:初始化组件树根组件、注册所有子组件,并将所有的getters存储到this._wrapperdGetters属性中
这个函数接受5个函数,store, rootState, path, module, hot。我们来看看他们分别代表什么:
store: 表示当前Store实例
rootState: 表示根state,
path: 其他的参数的含义都比较好理解,那么这个path是如何理解的呢。我们可以将一个store实例看成module的集合。每一个集合也是store的实例。那么path就可以想象成一种层级关系,当你有了rootState和path后,就可以在Path路径中找到local State。然后每次getters或者setters改变的就是localState
module:表示当前安装的模块
hot:当动态改变modules或者热更新的时候为true

 const isRoot = !path.length
 const namespace = store._modules.getNamespace(path)

用于通过Path的长度来判断是否为根,下面那一句是用与注册命名空间

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

这句是将mutation注册到模块上,后面几句是同样的效果,就不做一一的解释了

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })

这儿是通过遍历Modules,递归调用installModule安装模块。
if (!isRoot && !hot) {

const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
  Vue.set(parentState, moduleName, module.state)
})

}
这段用于判断如果不是根并且Hot为false的情况,将要执行的俄操作,这儿有一个函数getNestedState (state, path),函数的内容如下:

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

这个函数的意思拿到该module父级的state,拿到其所在的moduleName,然后调用store._withCommit()方法

store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
})

把当前模块的state添加到parentState,我们使用了_withCommit()方法,_withCommit的方法我们留到commit方法再讲。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89428.html

相关文章

  • vuex源码分析

    摘要:的源码分析系列大概会分为三篇博客来讲,为什么分三篇呢,因为写一篇太多了。这段代码检测是否存在,如果不存在那么就调用方法这行代码用于确保在我们实例化之前,已经存在。每一个集合也是的实例。 vuex的源码分析系列大概会分为三篇博客来讲,为什么分三篇呢,因为写一篇太多了。您看着费劲,我写着也累showImg(https://segmentfault.com/img/bVXKqD?w=357&...

    BDEEFE 评论0 收藏0
  • [源码学习] Vuex

    摘要:为了更清楚的理解它的原理和实现,还是从源码开始读起吧。结构梳理先抛开,的主要源码一共有三个文件,,初始化相关用到了和我们使用创建的实例并传递给的根组件。这个方法的第一个参数是构造器。的中,在保证单次调用的情况下,调用对构造器进入了注入。 原文链接 Vuex 作为 Vue 官方的状态管理架构,借鉴了 Flux 的设计思想,在大型应用中可以理清应用状态管理的逻辑。为了更清楚的理解它的原理和...

    FreeZinG 评论0 收藏0
  • Vuex源码学习()功能梳理

    摘要:我们通常称之为状态管理模式,用于解决组件间通信的以及多组件共享状态等问题。创建指定命名空间的辅助函数,总结的功能首先分为两大类自己的实例使用为组件中使用便利而提供的辅助函数自己内部对数据状态有两种功能修改数据状态异步同步。 what is Vuex ? 这句话我想每个搜索过Vuex官网文档的人都看到过, 在学习源码前,当然要有一些前提条件了。 了解Vuex的作用,以及他的使用场景。 ...

    livem 评论0 收藏0
  • vuex源码阅读分析

    摘要:继续看后面的首先遍历的,通过和首先获取然后调用方法,我们来看看是不是感觉这个函数有些熟悉,表示当前实例,表示类型,表示执行的回调函数,表示本地化后的一个变量。必须是一个函数,如果返回的值发生了变化,那么就调用回调函数。 这几天忙啊,有绝地求生要上分,英雄联盟新赛季需要上分,就懒着什么也没写,很惭愧。这个vuex,vue-router,vue的源码我半个月前就看的差不多了,但是懒,哈哈。...

    gplane 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<