大家都知道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' // 引入vuex Vue.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 = null class Store { constructor (options) { let { state, getters, actions, mutations } = options } } // Vue.use(Vuex) const install = _Vue => { // 避免vuex重复安装 if (Vue === _Vue) return Vue = _Vue Vue.mixin({ // 通过mixins让每个组件实例化的时候都会执行下面的beforeCreate beforeCreate () { // 只有跟节点才有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 = {} } = options this.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: { // 模块a a: { state: { count: 4000 }, actions: { change ({state}) { state.count += 21 } }, modules: { // 模块b b: { 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.state this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图 this._vm = new Vue({ data: { state } }) // 把模块之间的关系进行整理, 自己根据用户参数维护了一个对象 // root._children => a._children => b this.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) } // 设置getter if (rootModule._raw.getters) { myforEach(rootModule._raw.getters, (getterName, getterFn) => { Object.defineProperty(store.getters, getterName, { get: () => { return getterFn(rootModule.state) } }) }) } // 在跟模块设置actions if (rootModule._raw.actions) { myforEach(rootModule._raw.actions, (actionName, actionsFn) => { // 因为同是在根模块设置,子模块也有能相同的key // 所有把所有的都放到一个数组里面 // 就变成了例如 [change, change] , 第一个是跟模块的actions的change,第二个是a模块的actions的change let entry = store.actions[actionName] || (store.actions[actionName] = []) entry.push(() => { const commit = store.commit const state = rootModule.state actionsFn.call(store, {state, commit}) }) }) } // 在跟模块设置mutations, 同理上actions if (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) return Vue = _Vue Vue.mixin({ // 通过mixins让每个组件实例化的时候都会执行下面的beforeCreate beforeCreate () { // 只有跟节点才有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://github.com/naihe138/write-vuex
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]