vuex到底是什么?

  使用vue也有一段时间了,但是对vue的理解似乎还是停留在初始状态,究其原因,不得不说是自己没有深入进去,理解本质,导致开发效率低,永远停留在表面, 更坏的结果就是refresh、restart。

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

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

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

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

  下面,我们来解析源码

 

  

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

  其中.github这种.开头的文件一般都是配置型的文件,比如.github、.babelrc、.eslintrc等等,这些文件往往是不需要在正式环境中使用的。 所以在看源码时是可以忽略的。

  而build文件往往是用于创建一个项目时所需要的配置,比如vue-cli构建的项目中的各种构造(build)这个项目所需要配置的文件,并不是vuex的核心文件。

  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: ,
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(, -))
const moduleName = path[path.length - ]
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

  

vuex到底是什么?的更多相关文章

  1. vuex到底是个啥

    vuex总结 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vue 的 ...

  2. 挑战全网最幽默的Vuex系列教程:第一讲 Vuex到底是什么鬼

    先说两句 官方已经有教程了,为什么还要写这个教程呢?说实话,还真不是我闲着蛋疼,官方的教程真的是太官方了,对于刚入门 Vuex 的童鞋来说,想必看官方的教程,很多地方就如同看圣经一样,比如「欧玛尼玛尼 ...

  3. 4.VUEX到底是什么

    关于vuex类的新闻最近很多,看到眼热就去查了下资料,然后扯出来一堆flux.redux.state.state之类的概念,以及大型工程必要性之类的.看官方手册也是昏昏然. 然而,我还是弄懂了!我准备 ...

  4. 理解vuex的状态管理模式架构

    理解vuex的状态管理模式架构 一: 什么是vuex?官方解释如下:vuex是一个专为vue.js应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证以一种可预测的 ...

  5. Vue(三):vuex是什么

    vuex官网介绍 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vue ...

  6. Vuex 入门指南

    1.Vuex是什么? 我们还是像以往一样先看一看官方文档对此的解读(Vuex 是什么? · GitBook) Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的 ...

  7. 浅谈vuex

    很多技术,刚接触的时候:这是啥?用的时候:哟嚯,是挺好用的!加以研究:卧槽,就是这么个逼玩意儿! 最近接手了一个别人写了1/5的vue项目(页面画了1/3,接口啥都没对); 对于表格中的数据项操作以及 ...

  8. 一篇搞定Vuex

    1.简介 首先,你必须明显明白vuex到底是干啥的,主要解决开发中的哪些问题? Vuex是一个专门为Vue.js应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证 ...

  9. 五分钟搞懂Vuex

    这段时间一直在用vue写项目,vuex在项目中也会依葫芦画瓢使用,但是总有一种朦朦胧胧的感觉.于是决定彻底搞懂它. 看了一下午的官方文档,以及资料,才发现vuex so easy! 作为一个圈子中的人 ...

随机推荐

  1. 9.29学习的js基础

    js基础 1.三种js引入方式    a).<input type="button" value="点击事件" onClick="documen ...

  2. JavaScript - this详解 (二)

    用栗子说this Bug年年有,今年特别多 对于JavaScript这么灵活的语言来说,少了this怎么活! function 函数 this 对于没有实例化的function,我们称之为函数,即没有 ...

  3. Python 单元测试 增强系统健壮性

    问题背景交代 注意,JulyNovel只爬取免费小说,所有vip章节全部导航至起点网站,遵循robots协议,所有数据仅供学习用途,侵删 通过编写单元测试,提高JulyNovel系统可靠性 首先我们知 ...

  4. SPOJ distinct subtrings

    题目链接:戳我 后缀自动机模板? 求不同的子串数量. 直接\(\sum t[i].len-t[t[i].ff].len\)即可 代码如下: #include<iostream> #incl ...

  5. 点击隐藏显示和点击body空白处隐藏

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. “全栈2019”Java第六十四章:接口与静态方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  7. CAS客户端整合(一) Discuz!

    有好几个系统需要接入CAS,所以登录模块统统需要重构 版本 CAS服务端是Java的 Cas-server-4.0 CAS的php客户端 是 phpCAS-1.2.0 论坛版本是 Discuz!X3. ...

  8. svn提交新文件夹同时不需要更新全部上级目录

    关于svn的指定目录指定位置更新:当在提交了新建的目录后可以使用 a)  在需要更新的上级目录上单击右键 在延伸菜单中选择 b)  弹出对话框中选择,check repository c)  新添加的 ...

  9. Ionic2文档整理

    来自:Rainey's Blog 原文地址:http://rainey.space/2016/04/06/Ionic2_Chinese_Document/ Github:https://github. ...

  10. python模块之——tqdm(进度条)

    from tqdm import tqdm for i in tqdm(range(10000)): """一些操作""" pass 效果: ...