vuex实现及简略解析
大家都知道vuex
是vue
的一个状态管理器,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。先看看vuex
下面的工作流程图
通过官方文档提供的流程图我们知道,vuex
的工作流程,
- 1、数据从
state
中渲染到页面; - 2、在页面通过
dispatch
来触发action
; - 3、
action
通过调用commit
,来触发mutation
; - 4、
mutation
来更改数据,数据变更之后会触发dep
对象的notify
,通知所有Watcher
对象去修改对应视图(vue的双向数据绑定原理)。
使用vuex
理解vuex
的工作流程我们就看看vuex
在vue
中是怎么使用的。
首先用vue-cli
创建一个项目工程,如下图,选择vuex
,然后就是一路的回车键
安装好之后,就有一个带有vuex
的vue
项目了。
进入目录然后看到,src/store.js
,在里面加了一个状态{count: 100}
,如下
import Vue from 'vue'
import Vuex from 'vuex' // 引入vuexVue.use(Vuex) // 使用插件export default new Vuex.Store({state: {count: 100 // 加一个状态},getter: {},mutations: {},actions: {}
})
最后在App.vue文件里面使用上这个状态,如下
<template><div id="app">这里是stort------->{{this.$store.state.count}}</div>
</template><script>
export default {name: 'app'
}
</script><style>
</style>
项目跑起来就会看到页面上看到,页面上会有100了,如下图
到这里我们使用vuex
创建了一个store
,并且在我们的App组件视图中使用,但是我们会有一些列的疑问。
store
是如何被使用到各个组件上的??- 为什么
state
的数据是双向绑定的?? - 在组件中为什么用
this.$store.dispch
可以触发store
的actions
?? - 在组件中为什么用
this.$store.commit
可以触发store
的mutations
?? - ....等等等等
带着一堆问题,我们来自己实现一个vuex
,来理解vuex
的工作原理。
安装并使用store
在src
下新建一个vuex.js
文件,然后代码如下
'use strict'let Vue = nullclass Store {constructor (options) {let { state, getters, actions, mutations } = options}
}
// Vue.use(Vuex)
const install = _Vue => {// 避免vuex重复安装if (Vue === _Vue) returnVue = _VueVue.mixin({// 通过mixins让每个组件实例化的时候都会执行下面的beforeCreatebeforeCreate () {// 只有跟节点才有store配置,所以这里只走一次if (this.$options && this.$options.store) {this.$store = this.$options.store} else if (this.$parent && this.$parent.$store) { // 子组件深度优先 父 --> 子---> 孙子this.$store = this.$parent.$store}}})
}export default { install, Store }
然后修改store.js
中的引入vuex模块改成自己的vuex.js
import Vuex from './vuex' // 自己创建的vuex文件
在我们的代码中export default { install, Store }
导出了一个对象,分别是install
和Store
install
的作用是,当Vue.use(Vuex)
就会自动调用install
方法,在install
方法里面,我们用mixin
混入了一个beforeCreate
的生命周期的钩子函数,使得当每个组件实例化的时候都会调用这个函数。
在beforeCreate
中,第一次根组件通过store
属性挂载$store
,后面子组件调用beforeCreate
挂载的$store
都会向上找到父级的$store
,这样子通过层层向上寻找,让每个组件都挂上了一个$store
属性,而这个属性的值就是我们的new Store({...})
的实例。如下图
通过层层向上寻找,让每个组件都挂上了一个
$store
属性
设置state响应数据
通过上面,我们已经从每个组件都通过this.$store
来访问到我们的store的实例,下面我们就编写state
数据,让其变成双向绑定的数据。下面我们改写store
类
class Store {constructor (options) {let { state, getters, actions, mutations } = options // 拿到传进来的参数this.getters = {}this.mutations = {}this.actions = {}// vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图this._vm = new Vue({data: {state}})}// 访问state对象时候,就直接返回响应式的数据get state() { // Object.defineProperty get 同理return this._vm.state}
}
传进来的state
对象,通过new Vue({data: {state}})
的方式,让数据变成响应式的。当访问state
对象时候,就直接返回响应式的数据,这样子在App.vue
中就可以通过this.$store.state.count
拿到state
的数据啦,并且是响应式的呢。
编写mutations、actions、getters
上面我们已经设置好state
为响应式的数据,这里我们在store.js
里面写上mutations、actions、getters
,如下
import Vue from 'vue'
import Vuex from './vuex' // 引入我们的自己编写的文件Vue.use(Vuex) // 安装store
// 实例化store,参数数对象
export default new Vuex.Store({state: {count : 1000},getters : {newCount (state) {return state.count + 100}},mutations: {change (state) {console.log(state.count)state.count += 10}},actions: {change ({commit}) {// 模拟异步setTimeout(() => {commit('change')}, 1000)}}
})
配置选项都写好之后,就看到getters
对象里面有个newCount
函数,mutations
和actions
对象里面都有个change函数
,配置好store
之后我们在App.vue
就可以写上,dispatch
和commit
,分别可以触发actions
和mutations
,代码如下
<template><div id="app">这里是store的state------->{{this.$store.state.count}} <br/>这里是store的getter------->{{this.$store.getters.newCount}} <br/><button @click="change">点击触发dispach--> actions</button><button @click="change1">点击触发commit---> mutations</button></div>
</template><script>
export default {name: 'app',methods: {change () {this.$store.dispatch('change') // 触发actions对应的change},change1 () {this.$store.commit('change') // 触发mutations对应的change}},mounted () {console.log(this.$store)}
}
</script>
数据都配置好之后,我们开始编写store类,在此之前我们先编写一个循环对象工具函数。
const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key]))
// 作用:
// 例如{a: '123'}, 把对象的key和value作为参数
// 然后就是函数运行callback(a, '123')
工具函数都准备好了,之后,下面直接县编写getters
、mutations
和actions
的实现
class Store {constructor (options) {let { state = {}, getters = {}, actions = {}, mutations = {} } = optionsthis.getters = {}this.mutations = {}this.actions = {}// vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图this._vm = new Vue({data: {state}})// 循环getters的对象myforEach(getters, (getterName, getterFn) => {// 对this.getters对象进行包装,和vue的computed是差不多的// 例如 this.getters['newCount'] = fn(state)// 执行 this.getters['newCount']()就会返回计算的数据啦Object.defineProperty(this.getters, getterName, {get: () => getterFn(state)})})// 这里是mutations各个key和值都写到,this.mutations对象上面// 执行的时候就是例如:this.mutations['change']()myforEach(mutations, (mutationName, mutationsFn) => {// this.mutations.change = () => { change(state) }this.mutations[mutationName] = () => {mutationsFn.call(this, state)}})// 原理同上myforEach(actions, (actionName, actionFn) => {// this.mutations.change = () => { change(state) }this.actions[actionName] = () => {actionFn.call(this, this)}})const {commit , dispatch} = this // 先存一份,避免this.commit会覆盖原型上的this.commit// 解构 把this绑定好// 通过结构的方式也要先调用这类,然后在下面在调用原型的对应函数this.commit = type => {commit.call(this, type)}this.dispatch = type => {dispatch.call(this, type)}}get state() { // Object.defineProperty 同理return this._vm.state}// commi调用commit (type) {this.mutations[type]()}// dispatch调用dispatch (type) {this.actions[type]()}
}
通过上面的,我们可以看出,其实mutations
和actions
都是把传入的参数,赋值到store
实例上的this.mutations
和this.actions
对象里面。
当组件中this.$store.commit('change')
的时候 其实是调用this.mutations.change(state)
,就达到了改变数据的效果,actions
同理。
getters是通过对Object.defineProperty(this.getters, getterName, {})
对this.getters进行包装当组件中this.$store.getters.newCount
其实是调用getters
对象里面的newCount(state)
,然后返回计算结果。就可以显示到界面上了。
大家看看完成后的效果图。
到这里大家应该懂了vuex
的内部代码的工作流程了,vuex
的一半核心应该在这里了。为什么说一半,因为还有一个核心概念module
,也就是vuex
的数据的模块化。
vuex数据模块化
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
例如下面的store.js
// 实例化store,参数数对象
export default new Vuex.Store({modules: {// 模块aa: {state: {count: 4000},actions: {change ({state}) {state.count += 21}},modules: {// 模块bb: {state: {count: 5000}}}}},state: {count : 1000},getters : {newCount (state) {return state.count + 100}},mutations: {change (state) {console.log(state.count)state.count += 10}},actions: {change ({commit}) {// 模拟异步setTimeout(() => {commit('change')}, 1000)}}
})
然后就可以在界面上就可以写上this.$store.state.a.count(显示a模块count)
,this.$store.state.a.b.count(显示a模块下,b模块的count)
,这里还有一个要注意的,其实在组件中调用this.$store.dispatch('change')
会同时触发,根的actions
和a模块
的actions
里面的change
函数。
下面我们就直接去实现models
的代码,也就是整个vuex
的实现代码,
'use strict'let Vue = null
const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key]))class Store {constructor (options) {let state = options.statethis.getters = {}this.mutations = {}this.actions = {}// vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图this._vm = new Vue({data: {state}})// 把模块之间的关系进行整理, 自己根据用户参数维护了一个对象// root._children => a._children => bthis.modules = new ModulesCollections(options)// 无论子模块还是 孙子模块 ,所有的mutations 都是根上的// 安装模块installModules(this, state, [], this.modules.root)// 解构 把this绑定好const {commit , dispatch} = this// 通过结构的方式也要先调用这类,然后在下面在调用原型的对应函数this.commit = type => {commit.call(this, type)}this.dispatch = type => {dispatch.call(this, type)}}get state() { // Object.defineProperty 同理return this._vm.state}commit (type) {// 因为是数组,所以要遍历执行this.mutations[type].forEach(fn => fn())}dispatch (type) {// 因为是数组,所以要遍历执行this.actions[type].forEach(fn => fn())}
}class ModulesCollections {constructor (options) { // vuex []// 注册模块this.register([], options)}register (path, rawModule) {// path 是空数组, rawModule 就是个对象let newModule = {_raw: rawModule, // 对象_children: {}, // 把子模块挂载到这里state: rawModule.state}if (path.length === 0) { // 第一次this.root = newModule} else {// [a, b] ==> [a]let parent = path.slice(0, -1).reduce((root, current) => {return root._children[current]}, this.root)parent._children[path[path.length - 1]] = newModule}if (rawModule.modules) {// 遍历注册子模块myforEach(rawModule.modules, (childName, module) => {this.register(path.concat(childName), module)})}}
}// rootModule {_raw, _children, state }
function installModules (store, rootState, path, rootModule) {// rootState.a = {count:200}// rootState.a.b = {count: 3000}if (path.length > 0) {// 根据path找到对应的父级模块// 例如 [a] --> path.slice(0, -1) --> [] 此时a模块的父级模块是跟模块// 例如 [a,b] --> path.slice(0, -1) --> [a] 此时b模块的父级模块是a模块let parent = path.slice(0, -1).reduce((root, current) => {return root[current]}, rootState)// 通过Vue.set设置数据双向绑定Vue.set(parent, path[path.length - 1], rootModule.state)}// 设置getterif (rootModule._raw.getters) {myforEach(rootModule._raw.getters, (getterName, getterFn) => {Object.defineProperty(store.getters, getterName, {get: () => {return getterFn(rootModule.state)}})})}// 在跟模块设置actionsif (rootModule._raw.actions) {myforEach(rootModule._raw.actions, (actionName, actionsFn) => {// 因为同是在根模块设置,子模块也有能相同的key// 所有把所有的都放到一个数组里面// 就变成了例如 [change, change] , 第一个是跟模块的actions的change,第二个是a模块的actions的changelet entry = store.actions[actionName] || (store.actions[actionName] = [])entry.push(() => {const commit = store.commitconst state = rootModule.stateactionsFn.call(store, {state, commit})})})}// 在跟模块设置mutations, 同理上actionsif (rootModule._raw.mutations) {myforEach(rootModule._raw.mutations, (mutationName, mutationFn) => {let entry = store.mutations[mutationName] || (store.mutations[mutationName] = [])entry.push(() => {mutationFn.call(store, rootModule.state)})})}// 递归遍历子节点的设置myforEach(rootModule._children, (childName, module) => {installModules(store, rootState, path.concat(childName), module)})
}const install = _Vue => {// 避免vuex重复安装if (Vue === _Vue) returnVue = _VueVue.mixin({// 通过mixins让每个组件实例化的时候都会执行下面的beforeCreatebeforeCreate () {// 只有跟节点才有store配置if (this.$options && this.$options.store) {this.$store = this.$options.store} else if (this.$parent && this.$parent.$store) { // 子组件深度优先 父 --> 子---> 孙子this.$store = this.$parent.$store}}})
}export default { install, Store }
看到代码以及注释,主要流程就是根据递归的方式,处理数据,然后根据传进来的配置,进行操作数据。
至此,我们把vuex
的代码实现了一遍,在我们App.vue
的代码里添加
<template><div id="app">这里是store的state------->{{this.$store.state.count}} <br/>这里是store的getter------->{{this.$store.getters.newCount}} <br/>这里是store的state.a------->{{this.$store.state.a.count}} <br/><button @click="change">点击触发dispach--> actions</button><button @click="change1">点击触发commit---> mutations</button></div>
</template>
最后查看结果。
完结撒花~~~
博客文章地址:https://blog.naice.me/article...
源码地址:https://github.com/naihe138/w...
vuex实现及简略解析相关推荐
- Intel CPU命名规则的简略解析
Intel的CPU命名规则一直不是特别清楚,而网上的很多解读是不准确,甚至是错误的,应该以官方文档为准.所以我在查阅官方资料的基础上,以一种简明扼要的方式记录下来.值得说明的是,这个解析只是简略的,一 ...
- Python实训day12am【网络爬虫大作业简略解析:动态生成html页面、数据写入Excel】
Python实训-15天-博客汇总表 目录 1.HTML页面设计 2.生成每个城市的HTML页面 2.1.HTML页面代码(weatherTemplate.html) 2.2.实例代码-动态生成htm ...
- Python实训day11pm【大作业简略解析】
Python实训-15天-博客汇总表 目录 1.课堂笔记 2.群消息 3.代码 3.1.demo1 3.2.demo2 1.课堂笔记 大作业题目思路引导: 题目1:定时爬取每个地级市的实时天气状况.存 ...
- Symbol方法简略解析,与Symbol.for(‘xxx‘)
当我们需要一个独一无二的值时,可以使用Symbol.Symbol是ES6引入的一种新的基本数据类型,它可以被用作对象属性的键,以保证对象的属性名不会与其他属性名冲突.每个Symbol值都是唯一的,即使 ...
- vuex 配置使用解析 mutations响应式规则 Vue.delete/set 目录划分
文章目录 用处 解析 结构 单一状态树 state getters使用 mutations mutations 提交风格 mutations 响应规则 mutations 常量类型 名称使用常量 mu ...
- vuex 编译项目_俺咋能看懂公司前端项目?
●●● 大家好, 我是一名刚步入社会的有志青年开发者. 在校学了三年的后端开发,没想到刚步入公司干起了前端工作,华丽的转变让我有点猝不及防,谁让我辣么优秀! 趁着头发茂密,让我们步入正题! 很有幸进 ...
- 学习 vuex 源码整体架构,打造属于自己的状态管理库
前言 这是学习源码整体架构第五篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现.本篇文章学习的是实际仓库的代码. 其余四篇分别是: ...
- 前端 100 问,大厂面试题精华解析
引言 半年时间,几千人参与,精选大厂前端面试高频 100 题,这就是「壹题」. 在 2019 年 1 月 21 日这天,「壹题」项目正式开始,在这之后每个工作日都会出一道高频面试题,主要涵盖阿里.腾讯 ...
- vuex系列--浅析Vuex 的设计思想
一.前言 在聊之前,大家要始终记得一句话:一切前端概念,都是纸老虎. 不管是Vue,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要.什么是共享状态?比如一个组件需要使用 ...
- vuex 源码整体架构学习
前言 这是学习源码整体架构第五篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现.本篇文章学习的是实际仓库的代码. 其余四篇分别是: ...
最新文章
- k8s暴露nginx NodePort端口命令:expose暴露端口使用示例
- QT的QSqlTableModel类的使用
- PageOffice实现最简单的Java导出Word中的数据
- web学习1--web项目的WEB-INF目录
- 记录qt窗口在拖动过程中出现的问题
- 关于秋收秋季的丰收插画素材,收获满足
- Supervisor 配置文件
- CRC冗余校验码的介绍和实现
- vs2008 MFC类继承结构
- [转]『TensorFlow』读书笔记_TFRecord学习
- 如果同时需要两张表,但其中一个表中没有另一个表中的字段,该如何正确使用
- 平面设计中的网格系统pdf_一本好书 | 排版圣经:设计中的网格系统
- 示例项目-京东商城图书分类排行榜抓取
- 深度学习Course4第三周Detection Algorithms习题整理
- Unity曲面UI插件Curved UI
- 旺旺qq html,如何在网页HTML中嵌入QQ、MSN、旺旺、Gtalk快速对话框代码
- 赛科尔亚洲招聘Axapta顾问
- ConcurrentHashMap中有十个提升性能的细节,你都知道吗?
- k--最近邻算法(KNN)
- python 自动化运维——实战 (一)
热门文章
- 阶段1 语言基础+高级_1-3-Java语言高级_08-JDK8新特性_第1节 常用函数接口_6_函数式接口作为方法的返回值类...
- C++ 安全单例模式总结
- NLTK学习笔记(六):利用机器学习进行文本分类
- 输入1-53周,输出1-53周的开始时间和结束时间
- (转)谷歌安卓官方教程中文版
- 指针二维二维数组和二重指针 的疑问 二维数组名为什么不能直接赋值给二重指针...
- web form常用控件
- ubuntu14上安装ros教程
- 使用C#,轻松发邮件之QQ邮箱
- Pausing and Resuming an Activity