前言刚开始看redux的时候,reducer、store、dispatch、middleware这些名词很难看懂,但是接触到vuex就更容易理解了之后。本章从头开始实现一个简单版本的状态管理器。方便大家以后了解vuex和redux的状态管理库的源码。什么是状态管理器?一个状态管理器的核心思想是收集之前委托给各个组件的修改数据层的controller代码,统一管理。组件需要修改。对于数据层,需要触发一个特定的预定义调度器,然后调度器将动作应用到模型上,实现对数据层的修改。然后将对数据层的更改应用到视图,形成单向数据流。简单的状态管理器本文将一步步写一个Store类来实现状态的同步更新、异步更新、中间件等方法。首先,我们编写一个Store类。classStore{constructor({state,mutations,actions}){this._state=statethis._mutations={}this._actions={}this._callbacks=[]}}state存储所有需要的数据,mutation是唯一的状态的方法。Action类似于mutation,区别在于action通过提交mutation来改变状态,action可以包含任何异步操作。这与vuex相同。当我们改变状态时需要通知订阅者。这可以通过实施发布-订阅模型来完成。callbacks用于存放订阅者的回调函数。让我们一一实现这些方法。一个Mutation改变Store中状态的唯一方法是提交一个mutation,每个mutation都有一个字符串事件类型(type)和一个回调函数(handler)。这个回调函数是我们真正改变状态的地方,它会接受状态作为第一个参数,然后对状态进行一些改变:conststate={name:'webb',age:20,data:{}}constmutations={changeAge(state,data){state.age=data},changeData(state,data){state.data=data}}接下来,我们将状态作为第一个参数传递给变异函数initMutation(state,突变,存储){constkeys=Object.keys(mutations)keys.forEach(key=>{registerMutation(key,mutations[key],state,store)})}functionregisterMutation(key,handle,state,store){//store._mutations[key]函数实际上是在mutation提交的时候执行的。此函数接受一个数据参数//并实现将状态作为第一个参数传递给回调函数。store._mutations[key]=function(data){handle.call(store,state,data)}}转换Store类classStore{constructor({state,mutations,actions}){this._state=statethis._mutations={}this._actions={}this._callbacks=[]}}Store.prototype._init=function(state,mutations,actions){initMutation(this,mutations,state)}Store.prototype.commit=function(type,payload){constmutation=this._mutations[type]mutation(payload)}//获取最新的stateStore.prototype.getState=function(){returnthis._state}conststore=newStore({state,mutations})通过提交突变来更新状态console.log(store.getState())//{name:'webb',age:20,data:{}}store.commit('changeAge',21)console.log(store.getState())//{name:'webb',age:21,data:{}}至此我们已经意识到,当mutation被提交时,state的值会被修改。现在有一个问题摆在我们面前,如果通过this._state.xx=xx可以直接修改state的值,我们应该避免直接修改state的值。那么我们可以在修改状态的时候做一层拦截。如果它没有被mutation修改,就会抛出一个错误。现在我们尝试使用es6代理来解决这个问题。classStore{constructor({state,mutations,actions}){this._committing=false//用于判断是否是commitmutation触发更新this._mutations={}this._init(state,mutations,actions)}}店铺。prototype._init=function(state,mutations,actions){this._state=initProxy(this,state)}Store.prototype.commit=function(type,payload){constmutation=this._mutations[type]this._committing=复制代码truemutation(payload)this._committing=false}//state操作添加拦截,如果不是commitmutation,会抛出错误functioninitProxy(store,state){returnnewProxy(state,handle(store))}functionhandle(store){return{get:function(target,key){returnReflect.get(target,key)},set:function(target,key,value){if(!store._committing){}thrownewError('Thestatecanonlybechangedthroughmutation')}returnReflect.set(target,key,value)}}}Subscribe上面我们已经完成了状态数据的修改。接下来,我们实现当状态数据更新时,通知相关的状态用户。//收集订阅者Store.prototype.subscribe=function(callback){this._callbacks.push(callback)}//修改状态后,触发订阅者的回调函数,将旧状态和新状态作为参数传递给Store.prototype.commit=function(mutationName,payload){constmutation=this._mutations[mutationName]conststate=deepClone(this.getStatus())this._committing=truemutation(payload)this._committing=falsethis._callbacks。forEach(callback=>{callback(state,this._state)})}conststore=newStore({state,mutations})store.subscribe(function(oldState,newState){console.log('old',oldState)console.log('new',newState)})store.commit('changeAge',21)//old:{name:'webb',age:20,data:{}}//new:{name:'webb',age:21,data:{}}在上面的代码中,我们采用了发布-订阅模型,通过subscribe函数来订阅状态变化。执行完变异后,调用订阅者的回调函数,并将之前的状态和最新的状态作为参数返回。actionsvuex文档中提到的一个重要原则是要记住mutation必须是一个同步函数。为什么?请参考以下示例:mutations:{someMutation(state){api.callAsyncMethod(()=>{state.count++})}}现在假设我们正在调试一个应用程序并在devtool中观察mutation日志。每个突变都会被记录下来,devtools需要捕捉前一个状态和下一个状态的快照。然而,上面示例中变异中异步函数中的回调使得这成为不可能:因为当变异触发时回调函数还没有被调用,devtools不知道回调函数何时被实际调用——本质上任何状态变化在回调函数是不可追踪的,所以为了处理异步操作,我们需要实现动作。constmutations={changeData(state,data){state.data=data}}constactions={asyncgetData({commit}){constdata=awaitaxios.get('http://ip-api.com/json')commit('changeData',data.data.status)}}functioninitAction(store,actions){constkeys=Object.keys(actions)keys.forEach(key=>{registerAction(key,store,actions[key])})}functionregisterAction(key,store,handle){store._actions[key]=function(data){//将提交和状态作为参数传递给操作的回调函数。当异步任务执行时,你可以提交一个突变来更新状态letres=handle.call(store,{commit:store.commit.bind(store),state:store._state},data)returnres}}//action由dispatch触发,并将更新后的状态作为promise的结果返回Store.prototype.dispatch=function(actionName,payload){returnnewPromise((resolve,reject)=>{constaction=this._actions[actionName]constself=this//action异步操作返回一个promise,当promise有结果时,返回最新状态。action(payload).then(()=>{resolve(this._state)})})}store.dispatch('getData').then(state=>{console.log('dispatchsuccess',state)})此时我们已经实现了一个基本的状态管理器。Middleware中间件现在有一个需求,每次修改状态的时候,记录修改前的状态,为什么修改,修改后的状态。这里我们模仿koa的洋葱模型中间件来实现。//首先定义一个中间件类Middleware{constructor(){this.middlewares=[]this.index=0}use(middleware){this.middlewares.push(middleware)}exec(){this.next()}next(){if(this.index
