vuex的实现原理是什么
关于vuex
就不再赘述,简单回顾一下:当应用碰到多个组件共享状态
时,简单的单向数据流
很容易被破坏:第一,多个视图依赖于同一状态;第二,来自不同视图的行为需要变更同一状态。若解决前者使用传参的方式,则不适用于多层嵌套的组件以及兄弟组件;若解决后者使用父子组件直接引用或事件变更和同步状态的多份拷贝,则不利于代码维护。
所以,最好的办法是:把组件的共享状态抽取出,以一个全局单例模式管理!这也正是vuex
背后的基本思想。
所以,vuex的大致框架如下:
classStore{constructor(){//state//getters//mutations//actions}//commit//dipatch}
接下来,就写写看。
一、创建vue项目
vuecreatevue2-vuex//创建vue2项目yarnaddvuex@next--save//安装vuexyarnserve//启动项目
二、实现原理
1、State
(1)使用
//store.js//仓库importVuefrom'vue'importVuexfrom'vuex'importextrafrom'./extra.js'Vue.use(Vuex)//引入vuex的方式,说明Store需要install方法exportdefaultnewVuex.Store({//仓库数据源state:{count:1,dowhat:'addCount'},}
//app.vue<template><pclass="testState"><p>{{mycount}}</p><p>{{dowhat}}:{{count}}</p></p></template><script>exportdefault{import{mapState}from'vuex'//推荐方式computed:mapState()({mycount:state=>state.count}),//推荐方式的简写方式computed:{//解构的是getters...mapState(['count','dowhat'])},}</script>
(2)注意
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性
中返回某个状态,这种模式导致组件依赖全局状态单例
。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态
Vuex 通过 store
选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)
)
(3)实现
所以除了Store
内部的五大属性以外,还需要考虑插件的一个install
方法,所以大致框架如下:
classStore{constructor(){//state//getters//mutations//actions//modules}//commit//dipatch}letVuex={Store,Install}exportdefaultVuex
所以,接下来就可以具体实现了,
classStore{constructor(options){//statethis.state=options.state}}letinstall=function(_Vue){_Vue.mixin({beforeCreate(){//在组件创建之前自动调用,每个组件都有这个钩子if(this.$options&&this.$options.store){//this.$options读取根组件this.$store=this.$options.store}else{this.$store=this.$parent&&this.$parent.$store}}})}
然而,上述的state的实现有一个缺点:当改变数据的时候,state
的数据不能动态的渲染。所以如何把state
里的数据成为响应式成为关键问题?实际上,类似vue
里的data
,也可以通过这种方式让其成为响应式。那么就得从install
方法中传入Vue
,所以改变后:
letVue=nullclassStore{constructor(options){//statethis.vm=new_Vue({data:{state:options.state//data中的数据才是响应式}})}getstate(){returnthis.vm.state}}letinstall=function(_Vue){//用于Vue.use(plugin)Vue=_Vue_Vue.mixin({onBeforeCreate(){//在组件创建之前自动调用,每个组件都有这个钩子if(this.$options&&this.$options.store){//this.$options读取根组件this.$store=this.$options.store}else{this.$store=this.$parent&&this.$parent.$store}}})}
2、getters
(1)使用
//store.jsexportdefaultnewVuex.Store({//计算属性getters:{//这里的函数不需要调用,可以直接使用,官方默认前面有getgetCount(state){//接受state作为其第一个参数returnstate.count*100;}},}
(2)注意
有时候我们需要从 store 中的 state 中派生出一些状态(比如增加,删除,过滤等等),Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,Getter 接受 state 作为其第一个参数,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果
(3)实现
//gettersletgetters=options.getters||{}this.getters={}Object.keys(getters).forEach(getterName=>{Object.defineproperty(this.getters,getterName,{get:()=>{returngetters[getterName](this.state)}})})
3、mutations
(1)使用
//store.jsexportdefaultnewVuex.Store({//相当于methodsmutations:{//mutations内部的函数,天生具备一个形参add(state,n){state.count+=n;},decrease(state,n){state.count-=n;}},}
methods:{submit(){console.log('success');},//解构仓库mutations里面的方法,要啥解构啥...mapMutations(['add','decrease']),//this.$store.commit('add'),...mapActions(['addAction','decreaseAction']),//this.addAction()调用actions里面的方法//this.$store.dispatch('add'),}
(2)注意
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler) 。这个回调函数就是进行状态更改的地方,并且它会接受 state 作为第一个参数,不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment
的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法
可以向 store.commit
传入额外的参数,即 mutation 的 载荷(payload) ,在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读
(3)实现
//mutationsletmutations=options.mutations||{}this.mutations={}Object.keys(mutations).forEach(mutationName=>{this.mutations[mutationName]=(arg)=>{//保证多个(第二个)参数的传入mutations[mutationName](this.state,arg)}})commit=(method,arg)=>{//使用箭头函数改变被调用的this的指向//console.log(this);this.mutations[method](arg)}
4、actions
(1)使用
//store.jsexportdefaultnewVuex.Store({actions:{addAction(context){//在这里调用add方法context.commit('add',10);},decreaseAction({commit}){commit('decreaseAction',5)}},}
(2)注意
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
Action 通过
store.dispatch
方法触发Action 通常是异步的,
store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且store.dispatch
仍旧返回 Promise一个
store.dispatch
在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行
(3)实现
//actionsletactions=options.actions||{}this.actions={}Object.keys(actions).forEach(actionName=>{this.actions[actionName]=(arg)=>{actions[actionName](this,arg)}})dispatch=(method,arg)=>{this.actions[method](arg)}
5、modules
(1)使用
//actionsletactions=options.actions||{}this.actions={}Object.keys(actions).forEach(actionName=>{this.actions[actionName]=(arg)=>{actions[actionName](this,arg)}})dispatch=(method,arg)=>{this.actions[method](arg)}
//store.jsmodules:{extra:extra}
(2)注意
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象
对于模块内部的 action,局部状态通过
context.state
暴露出来,根节点状态则为context.rootState
对于模块内部的 getter,根节点状态(rootState)会作为第三个参数暴露出来
三、整体代码
letVue=null//全局的_VueclassStore{constructor(options){//state//this.state=options.state写法不完美,当改变数据的时候,不能动态的渲染,所以需要把data中的数据做成响应式的this.vm=new_Vue({data:{state:options.state//data中的数据才是响应式}})//gettersletgetters=options.getters||{}this.getters={}Object.keys(getters).forEach(getterName=>{Object.defineProperty(this.getters,getterName,{get:()=>{returngetters[getterName](this.state)}})})//mutationsletmutations=options.mutations||{}this.mutations={}Object.keys(mutations).forEach(mutationName=>{this.mutations[mutationName]=(arg)=>{//保证多个(第二个)参数的传入mutations[mutationName](this.state,arg)}})//actionsletactions=options.actions||{}this.actions={}Object.keys(actions).forEach(actionName=>{this.actions[actionName]=(arg)=>{actions[actionName](this,arg)}})}dispatch=(method,arg)=>{this.actions[method](arg)}commit=(method,arg)=>{//console.log(this);this.mutations[method](arg)}getstate(){returnthis.vm.state}}letinstall=function(_Vue){Vue=_VueVue.mixin({beforeCreate(){//在组件创建之前自动调用,每个组件都有这个钩子if(this.$options&&this.$options.store){//this.$options读取到根组件this.$store=this.$options.store}else{////如果不是根组件的话,也把$store挂到上面,因为是树状组件this.$store=this.$parent&&this.$parent.$store}}})}letVuex={Store,install}exportdefaultVuex
到此,相信大家对“vuex的实现原理是什么”有了更深的了解,不妨来实际操作一番吧!这里是本站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!