Vuex简介
在使用Vue.js进行开发大型,且数据复杂的应用时,都会遇到多个组件共享同一个状态,亦或是多个组件会去更新同一个状态,在应用代码量较少的时候,我们可以组件间通信去维护修改数据,或者是通过事件总线来进行数据的传递以及修改。但是当应用逐渐庞大以后,代码就会变得难以维护,从父组件开始通过prop传递多层嵌套的数据由于层级过深而显得异常脆弱,而事件总线也会因为组件的增多、代码量的增大而显得交互错综复杂,难以捋清其中的传递关系。
Vuex就是把数据层放到全局形成一个单一的Store,组件层变得更薄,专门用来进行数据的展示及操作。所有数据的变更都需要经过全局的Store来进行,形成一个单向数据流,使数据变化变得“可预测”。
Vuex是一个专门为Vue.js框架设计的(为什么呢?等会看源码,知道了)、用于对Vue.js应用程序进行状态管理的库,它借鉴了Flux、redux的基本思想,将共享的数据抽离到全局,以一个单例存放,同时利用Vue.js的响应式机制来进行高效的状态管理与更新。正是因为Vuex使用了Vue.js内部的“响应式机制”,所以Vuex是一个专门为Vue.js设计并与之高度契合的框架(优点是更加简洁高效,缺点是只能跟Vue.js搭配使用)
Vue组件之间通信方式
父传子(props down)
父组件向子组件传值,在父组件内,通过设置子组件的属性给子组件传值,再子组件内,通过props属性来接受子组件传来的属性
图二中
type:为 String。
子传父(events up)
子组件向父组件传值时,在子组件内通过触发内部的事件来给父组件传值,或者是更改父组件的状态
事件共享(event bus)
事件共享也叫事件巴士,通过一个事件中心,绑定不用的事件名称,其他组件可以出发某一个事件也改变绑定事件的值
$attrs/$listeners
$attrs与$listeners的主要应用是实现多层嵌套传递,父组件内,通过对子组件使用v-bind/v-on绑定属性/事件,子组件内则可以通过$attrs/$listeners接受(在props内的属性,不会出现在$attrs),并可以多级嵌套传递
Tip:另外补充一个
Vue组件属性的inheritAttrs。Vue官网对于inheritAttrs的属性解释:如果你不希望组件的根元素继承特性,你可以在组件的选项中设置inheritAttrs: false。该属性默认为true,具体效果看图
provide/inject
provide/inject主要使用在跨层级组件之间的传值。provide 是在父组件中定义,然后所有子组件都是可以通过 inject 注入该变量进行操作
这里变量内容不一致,应该是
grandpa.msg
ref,$parent/$children
ref 被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例
$children指向直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。
$parent 指向当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
Vuex的使用
概念分析
vuex单一状态(全局状态)管理,在SPA单页面组件的开发中,在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、修改,并且你的修改可以同步全局。
可以更形象的把他称为一个管理数据的仓库,把多个组件(不限层级)都需要的state,全部都放在仓库里进行保存和操作,如果你的项目状态比较复杂,模块比较多。Vuex还支持modules属性,来帮助你管理这些模块,下面是一个Vuex.Store的配置参数
-
state单一状态树,用来保存状态的对象,类似于组件中的data
-
getters从
state中延伸出一些方便使用的状态,类似于组件中的computed,官网是这么解释的:Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
-
mutations更改
Vuex的store中的状态的唯一方法。非常类似于事件,每个mutation都有一个字符串的 事件类型type和 一个 回调函数handler。这个回调函数就是我们实际进行状态更改的地方,并且它会接受state作为第一个参数。 -
actionsaction类似于mutation,比mutation更强大,但是不能直接修state,需要出发特定的mutation来修改state,通常用来异步的操作state
-
modules应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,
store对象就有可能变得相当臃肿。可以将store划分成模块,每一个模块类似于一个store
实例方法
-
commitstore.commit有两种参数方式store.commit('module?/mutation', { num: 1}) store.commit({ type: 'mutation', num: 1 }) -
dispatchstore.dispatch同样有两种参数方式store.dispatch('module?/action', { num: 1}) store.dispatch({ type: 'action', num: 1 }) -
watch响应式地监测一个getter方法的返回值,当值改变时调用回调函数。store.watch((state, getter) => state.app.num,()=>{ console.log('state.app.num changed'), {}}) -
registerModule注册一个动态模块store.registerModule(moduleObject) -
hotUpdate使用webpack的Hot Module Replacement,Vuex支持在开发过程中热重载mutation、module、action和getter。没有太好的例子,上官网的code// webpack api 注册所有模块 function loadModules() { const context = require.context("./modules", false, /([a-z_]+)\.js$/i) const modules = context .keys() .map((key) => ({ key, name: key.match(/([a-z_]+)\.js$/i)[1] })) .reduce( (modules, { key, name }) => ({ ...modules, [name]: context(key).default }), {} ) return { context, modules } } const { context, modules } = loadModules() const store = new Vuex.Store({ modules }) if (module.hot) { // 在任何模块发生改变时进行热重载。 module.hot.accept(context.id, () => { const { modules } = loadModules() store.hotUpdate({ modules }) }) } -
subscribe/subscribeAction订阅
store的mutation/action。handler会在每个mutation/action完成后调用,接收mutation/action和经过mutation/action后的状态作为参数。
辅助函数
辅助函数的诞生,更方便的来调用store里面的方法,同时使用辅助函数,可以更好的让我们知道在某个组件内调用了那些store,而不是用store的实例,来到处的this.$store.commit,this.$store.dispatch。在项目上了一个层级,以及后起维护的时候,就不知道哪里调用了store的方法,改变了state
-
mapState当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用
mapState辅助函数帮助我们生成计算属性 -
mapGettersmapGetters将store中的getter映射到局部计算属性 -
mapMutations使用
mapMutations辅助函数将组件中的methods映射为store.commit调用 -
mapActions使用
mapActions辅助函数将组件中的methods映射为store.dispatch调用
定义仓库
组件中使用辅助函数(例子)
阅读Vuex源码
上面说了Vuex的那么多概念和方法,到底Vuex的里面藏了什么。我们就来看看Vuex的五脏六腑吧(node_modules/vuex, version:3.4.0 为示例)
package目录
Vuex的五脏六腑
着重看一下vuex/dist/vuex.js
-
install方法Vue在使用Vue.use(plugin)时,会调用plugin的install方法,我们就从install开始function install (_Vue) { if (Vue && _Vue === Vue) { { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ); } return } Vue = _Vue; applyMixin(Vue); }install调用applyMixin并传入Vue -
applyMixin方法function applyMixin (Vue) { var version = Number(Vue.version.split('.')[0]); if (version >= 2) { Vue.mixin({ beforeCreate: vuexInit }); } else { // override init and inject vuex init procedure // for 1.x backwards compatibility. var _init = Vue.prototype._init; Vue.prototype._init = function (options) { if ( options === void 0 ) options = {}; options.init = options.init ? [vuexInit].concat(options.init) : vuexInit; _init.call(this, options); }; } /** * Vuex init hook, injected into each instances init hooks list. */ function vuexInit () { var 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版本选择初始化方法 调用vuexInit,然后获取options.store,如果options没有store将$store指向parent.store,如果有store根据类型执行 -
store里面有什么370~436行Store的构造函数函数,生成store对象448~631行 在Store的原型上添加实例方法708~758行 初始化module相关配置818~890行 注册getters,mutations,actions,modules937~1070行 编写辅助函数mapState等1077~1140行 模块命令空间的操作1143~1196行logger函数 (大佬就是大佬,还要备注一下借鉴了redux-logger,在1141行)
手动实现一个Vuex
我们按照Vuex的思路来实现一个简易个Vuex,读起来有些别扭~
编写install方法
声明store类,并写入实例方法
注册自定义store
在组件中使用
结束语
九月,透蓝的天空,悬着火球般的太阳,云彩好似被太阳烧化了,也消失得无影无踪。望着远处的房屋之上,缓缓升起的炊烟。身体颤了一颤,抡起键盘继续优化我的code
如果对你有帮助,请不要吝啬你的赞👍
文章最后送上demo飞机票