vuex究竟是什么?

  vuex究竟是什么? html

  使用vue也有一段时间了,可是对vue的理解彷佛仍是停留在初始状态,究其缘由,不得不说是本身没有深刻进去,理解本质,致使开发效率低,永远停留在表面, 更坏的结果就是refresh、restart。 vue

  首先说说什么是vue。 我对vue的理解是一个简单、易上手的开源框架。能够帮助咱们快速构建单页面应用。因为MVVM的特色,在使用起来数据和视图的相互驱动使得页面的效果很好,避免了多余了http请求和繁琐的事件函数绑定,使咱们在开发起来搞笑、畅快。 react

  可是只是使用vue,仍是存在问题的。好比在同一个页面间的组件之间状态的传递就会出现问题。 好比, 在一个购物页面,咱们点击按钮增长物品的数量,而后在下方显示总价,而且为了组件的重用,按钮所在的组件和总价所在的组件是不一样的, 那么若是但愿二者进行传递数据,该怎么处理呢? 咱们知道prop是用来从父组件向子组件进行传递的,因此不可行。在官网上也介绍了总线的使用,可是使用起来很是麻烦,因此vuex就排上了用场。 使用vuex,能够帮助咱们在当前页面顺利的传递、分享状态,可是若是是在不一样的页面,一旦刷新,页面的state数据就会丢失了,从这里,咱们须要知道的时vuex只是状态管理的工具,而不是数据存储的工具,若是但愿使用数据存储就必需要使用web Storage了。 官网所言以下:git

  Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。es6

  也就是说vuex仅仅是一个管理状态的工具,保证状态能够以一种能够预测的方式发生变化,并无任何永久存储的功能。github

  下面,咱们来解析源码web

 

 

 

 

  

  

  进入vue的github,首先,咱们看到的就是这样的一个结构。vuex

  其中.github这种.开头的文件通常都是配置型的文件,好比.github、.babelrc、.eslintrc等等,这些文件每每是不须要在正式环境中使用的。 因此在看源码时是能够忽略的。vue-cli

  而build文件每每是用于建立一个项目时所须要的配置,好比vue-cli构建的项目中的各类构造(build)这个项目所须要配置的文件,并非vuex的核心文件。typescript

  dist文件是最终生成的文件,好比咱们使用vue-cli时,在npm run build以后生成的能够在实际项目中使用的文件就是dist文件,内容以下所示:

  其中的vuex.js就是核心文件了,而 vuex.min.js 是压缩后的文件,在生产中使用。esm.js文件多是在报错的时候见得最多了,他会主动跟踪咱们的文件的错误,而vm也是很是经常使用的,vm能够理解为追踪,vue message等意思。 、

  而logger之类的是日志记录。

 

  接下来就是docs文件了,这个文件主要就是一些vuex的文档,内容以下:

  其中assets中链接了vuex的官方网站。en是英文文档,fr是法国文档,ja是日本文档,kr是韩国文档,old中记录的时1.0的文档,ru是俄文文档,zh-cn就是中文文档,example就是官网的一些例子的使用。src就是vuex的源码文件夹,最后说。 test也不是重点,其中包含了e2e和jshint的代码检测工具。 types是typescript的写法介绍。  最后的一些文件都是可有可无的了。  

  也就是说,读一个库的文件,最重要的是读取其src文件。以下所示:

  

   咱们先来对这个文件作一个总体介绍:

  • module提供了module对象和module对象树的建立功能。
  • plugins用于开发辅助插件,如state记录日志功能,显然这些都不是核心文件。
  • helpers.js提供了mutation、action、getters的查找api。 
  • index.esm.js显然仍是方便开发使用的追踪错误的文件。
  • index.js即该核心文件的入口文件,看一个项目最开始就要看它的入口文件,因此一会咱们最早从 index.js 开始分析。
  • mixin.js提供了store在vue实例上的装载注入
  • util.js提供了find、assert等工具方法,在一个项目中util.js几乎是必不可少的。

  

  

index.js源码解读

  以前说了,阅读一个项目就要从他的入口文件开始,由于这才是最为核心的地方。 

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'

export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

  index.js很是简短,一共就12行代码,可是经过这12行代码咱们就能够知道vuex须要的是什么、提供的是什么了。而最最重要的时vuex提供了什么! 

  从export咱们能够一目了然的看出vuex暴露了哪些api, 其中Store是最重要的vuex提供的状态存储类,咱们使用vuex就是先要建立这个Store类,好比下面的代码:

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    totalPrice:  0,
    items: [],
    },
  mutations: {
     // ... 
   }
})

   这里咱们先引入vuex,而后Vue.use(Vuex),表示使用Vuex插件,而use能够引用就是由于咱们暴露了install接口,这样在咱们在执行Vue.use(Vuex)的时候就自动执行了install方法将Vuex做为引用。

   而后导出了version即vuex的版本, 还有咱们常用的mapState辅助函数、mapMutations辅助函数、mapGetters辅助函数、mapActions辅助函数、createNamespacedHelpers建立命名空间帮助函数。

  而createNamespaceHelpers主要是为了建立不一样模块的命令空间,解决命名冲突的问题。 

  正是由于vuex暴露了这些接口,因此咱们在写代码时,才会出现以下所示的使用方法

 import {mapState, mapMutations, mapActions} from 'vuex'
  
  export default {
    methods: {
      ...mapMutations([
        'UPDATE_CONTENT', "UPDATE_KINDS", "PUSH_TO_ITEMS", "UPDATE_CURINDEX"
      ]),
      ...mapActions([
        'updateKinds', 'updateContent', 'updateAllContent', 'updateMall', 'getCurContent', 'updateAllContentSync'
      ]),

  OK!到这就介绍了index.js,能够发现的时,这个入口文件的做用就是从不一样的文件引入接口,而后再统一的从入口文件这暴露出去。就像我写的vue-toast同样,最后必定要暴露出module.exports = Toast; 这样,在Vue.use(vue-toast)以后才能开始使用这个暴露的接口。 

  从这个入口文件能够看出,他须要的时 store.js 和 helpers.js ,那么剩下的文件呢? 显示是被 store.js 和 helpers.js引入使用了。 因此沿着入口文件中import的文件,咱们紧接着来看一看 store.js 文件。

 

 

store.js 源码解读

  这个文件当属vux中最为重要的文件了。 阅读源码的好处在于更好的使用库而且理解其中的使用方法来提高本身,因此,对于这篇400余行的代码,咱们采起精度的方式。下面开始 !

  对于这种模块化的文件咱们最早要看的就是他须要的是什么,提供的是什么。 很容易能够看出,store.js 的意义就在于道出了一个Store对象 和 一个install对象。以前咱们也提到过,是index.js中引入并导出的。 

  为了导出 Store对象和install对象, store.js引入了下面的一些模块:

import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import { forEachValue, isObject, isPromise, assert } from './util'

  至于这些模块具体是什么做用,咱们先说一下后面在具体了解。 applyMixin的做用是在 vue 实例初始化时提供一个 $store 方法供vue调用deveollPlugin 的主要做用是利用vue的开发者工具来展现vuex中的数据状态,方便开发者的调试 ModuleCollection的做用是支持 vuex 经过分模块的传入(collection就是收集的意思,将全部的vuex模块收集起来),能够是咱们的开发更为高效,由于状态一多,分模块才更容易管理, 这才稍早的版本中是不存在的。而forEachValu、isObject等就是一些通用的方法,在下面解读的时候咱们具体来说。

let Vue // bind on install

 定义局部 Vue 变量,用于判断是否已经装载和减小全局做用域查找。

 环境判断:

 接下来就是使用es6的语法,声明了一个store类:

export class Store {
  constructor (options = {}) {
    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.`)
    }

 

在开头讲了, assert是util.js中的,咱们看看assert的源码:

export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

 

即给定一个条件,若是不知足,就抛出一个错误,这里使用了es6的模板字符串方法。

因此这里是在判断处于开发环境下,而且Vue是存在的,不然就会报错,提示没有Vue.use(Vuex)。 接着判断 Promise 是否可用,由于vuex是基于promise的,若是不可用,咱们须要使用polyfill的方式实现promise。 这行代码的目的是为了确保 Promsie 可使用的,由于 Vuex 的源码是依赖 Promise 的。Promise 是 es6 提供新的 API,因为如今的浏览器并非都支持 es6 语法的,因此一般咱们会用 babel 编译咱们的代码,若是想使用 Promise 这个 特性,咱们须要在 package.json 中添加对 babel-polyfill 的依赖并在代码的入口加上 import 'babel-polyfill' 这段代码。 

接着就是 Store必须是用new操做符来建立的,因此这里的this时Store类型。

 

数据初始化:

构造函数接下来的代码是这样的:

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

    let {
      state = {}
    } = options
    if (typeof state === 'function') {
      state = state()
    }

 

这里利用了对象的解构赋值,即options是咱们经过Vue.use(vuex, {}) 中的后者可能传递的对象, 将options中的plugins赋值给当前的plugins, 将strict赋值给当前的strict,而 plugins = []和strict=false是默认值, 即若是options不存在时咱们默认使用的值。 

 

接下来定义了store的一些内部属性(咱们习惯用_来表示内部属性):

    this._committing = false
    this._actions = Object.create(null)
    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()

 

其中,_committing标志一个提交状态,做用是 state只能在 mutation 的回调函数中修改,而不能再其余地方修改, false就是不能修改。  this.actions是一个对象,它定义了用户的全部actions。 一样对于_mutations、_wrappedGetters。 subscribers定义了全部检测 mutations 变化的订阅者。 而 _watcherVM是vue对象的一个实例,使用$watch来检测变化的。

 

    // bind commit and dispatch to self
    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, options) {
      return commit.call(store, type, payload, options)
    }

 

这里的this绑定到了store实例上,而后定义了store的两个重要的方法 dispatch和commit。 接受的参数分别是 type ,即commit的类型以及dispatch的类型, 以及一个payload, 从源码就能够看出咱们进行commit和dipatch的时候必定要传递一个payload。 这是很重要的。最后说明在 store 上调用。由于都是store的方法。

 

    // strict mode
    this.strict = strict

 

线下环境建议开启严格模式,线上环境建议关闭严格模式。

 

 // init root module.
// this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state)

    // apply plugins
    plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))

 这几行代码的做用是递归地注册子模块。 而他的实现原理是什么呢? 下面看一看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 namespacedType = namespace + key
    registerAction(store, namespacedType, action, 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)
  })
}

 

即先判断是不是根目录, 若是path.length为0则为根目录,接着得到命名空间, 若是命名空间是存在的,咱们就将模块注册到map中。

这里再判断若是不是根目录,而且不是hot,咱们就经过getNestedState获得parentState以及moduleName,而且将module的state注册到parentState的moduleName中。由此来实现state注册。而getNestedState实现也很是简单:

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

 

 即根据path的长度来决定返回的state。  而后就是环境的管理,

 

命名空间和根目录条件判断完毕后,接下来定义local变量和module.context的值,执行makeLocalContext方法,为该module设置局部的 dispatch、commit方法以及getters和state(因为namespace的存在须要作兼容处理)。

 

 

 接下来咱们就能够开始循环注册 mutations 和 actions了。

 

这样就可使用了。 最后的介绍是关于一些基本方法的介绍。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

http://tech.meituan.com/vuex-code-analysis.html

https://github.com/DDFE/DDFE-blog/issues/8

https://github.com/vuejs/vuex/blob/dev/src/store.js

相关文章
相关标签/搜索