当前位置: 首页 > Web前端 > HTML5

探索Redux4.0版本的迭代基础上谈展望(对比React语境)

时间:2023-04-05 01:09:44 HTML5

Redux前几天(2018.04.18)发布了新版本,6个commit被合并到master。从诞生到现在的4.0版本,Redux在使用层面一直保持着平稳的过渡。同时,不久前,React也从15版本升级到16版本,开发者无需做太多改动即可“无痛升级”。但是在版本迭代的背后有很多有趣的设计值得了解。Redux的这次升级也是如此。本文将从本次版本升级入手,从源码变化入手进行分析。通过下面的内容,相信读者可以对JavaScript的基础层面有更深入的了解。本文支持前端初学者的学习,更适合有阅读Redux源码经验的朋友。核心源码不再重复分析,更多关注升级和改动。变化概览本次升级有22处变化,大部分体现在TypeScript、CommonJS和ES构建的使用,状态错误抛出等方面。对于工程和配置的变化,我们不会再多说了。主要从代码细节入手,从基础入手,重点关注以下改动:中间件APIdispatch参数处理;应用中间件更改;bindActionCreators透明地处理这个;调度时,冻结状态;,我们直接进入主题。applyMiddleware参数处理这个变化是由Asvarox提出的。熟悉Redux源码中applyMiddleware.js设计的读者,一定对middlewareAPI不陌生:对于每一个中间件,它都可以感知store的一部分,即middlewareAPI。这里有一个简短的扩展:constmiddlewareAPI={getState:store.getState,dispatch:(action)=>dispatch(action)};chain=middlewares.map(middleware=>middleware(middlewareAPI));dispatch=compose(...chain)(store.dispatch)创建中间件存储:letnewStore=applyMiddleware(mid1,mid2,mid3,...)(createStore)(reducer,null);我们看到applyMiddleware是一个三层的curry函数。它会依次获取三个参数,第一个是middlewares数组,[mid1,mid2,mid3,...],第二个是Redux原生的createStore,最后一个是reducer;applyMiddleware使用createStore和reducer创建store,然后store的getState方法和dispatch方法直接和间接赋值给middlewareAPI变量。middlewares数组允许每个中间件通过map方法以参数middlewareAPI单独执行。执行后得到链数组,[f1,f2,...,fx,...,fn],然后组成链中的所有匿名函数,[f1,f2,...,fx,...,fn]组装成一个新的函数,也就是一个新的dispatch,当newdispatch执行时,[f1,f2,...,fx,...,fn]会从右到左依次执行。以上解释修改自:纯渲染专栏。好了,简单介绍完中间件机制,我们来看看这个变化。故事源于Asvarox设计了一个自定义的中间件,这个中间件接收到的dispatch需要两个参数。他的“杰作”是这样的:constmiddleware=({dispatch})=>next=>(actionCreator,args)=>dispatch(actionCreator(...args));对比传统中间件的写法:constmiddleware=store=>next=>action=>{...}我们可以很明显的看出他的写法有什么问题:基于原来的Redux源码,后面的argsactionCreator参数将丢失。所以他提出的修改是:constmiddlewareAPI={getState:store.getState,-dispatch:(action)=>dispatch(action)+dispatch:(...args)=>dispatch(...args)}ifIf好奇他为什么要这样设计自己的中间件,可以参考issue#2501。我个人认为对于需求,可以通过其他方式避免他的“奇葩”方法;但是对于Redux库来说,扩展middlewareAPI.dispatch参数确实更合适。让我们停止这种变化,停止挖掘死胡同。应该学习:基于ES6的不确定参数和传播运算符的魔法。虽然我一直在说,一直在提,但是我们在实际开发程序的时候还是要注意,养成良好的习惯。基于此,同样的变化也体现在:exportdefaultfunctionapplyMiddleware(...middlewares){-return(createStore)=>(reducer,preloadedState,enhancer)=>{-conststore=createStore(reducer,preloadedState,enhancer)+return(createStore)=>(...args)=>{+conststore=createStore(...args)letdispatch=store.dispatchletchain=[]此更改由jimbolla建议。bindActionCreators为此透明地处理了Redux中的bindActionCreators,达到了dispatchwrappingaction的目的。这样bindActionCreators创建的方法就可以直接调用dispatch(action)(隐式调用)。可能很多开发者不经常使用,这里稍微扩展一下。在action.js文件中,我们定义了两个actioncreators:直接使用:import{bindActionCreators}from'redux';import*asoldActionCreatorfrom'./action.js'classC1extendsComponent{constructor(props){super(props);const{dispatch}=道具;this.boundActionCreators=bindActionCreators(oldActionCreator,dispatch);}componentDidMount(){//由react-redux注入的dispatch:let{dispatch}=this.props;letaction=TodoActionCreators.addTodo('使用Redux');派遣(行动);}render(){//...让{dispatch}=this.props;让newAction=bindActionCreators(oldActionCreator,dispatch)返回}}这样子组件Child中,直接调用newAction.action1就相当于调用了dispatch(action1)。这样做的好处是没有store和dispatch组件,可以进行Action分发。一般这个API用的不多,至少作者用的不是很频繁。所以上面简单介绍一下。有经验的开发者不难猜到bindActionCreators的源码做了什么,连同这个变化:functionbindActionCreator(actionCreator,dispatch){-return(...args)=>dispatch(actionCreator(...args))+returnfunction(){returndispatch(actionCreator.apply(this,arguments))}}我们来看这个变化,对actionCreator使用apply方法,显式绑定this。那么这样做的意义何在?让我举个例子,假设我们将this绑定到原始的actionCreator并使用bindActionCreators方法:constuniqueThis={};functionactionCreator(){return{type:'UNKNOWN_ACTION',this:this,args:[...参数]}};constaction=actionCreator.apply(uniqueThis,argArray);constboundActionCreator=bindActionCreators(actionCreator,store.dispatch);constboundAction=boundActionCreator.apply(uniqueThis,argArray);我们应该期望boundAction与action一致;而boundAction.this与uniqueThis一致,都等于action.this。在这样的期待下,这样的改变无疑是必要的。冻结状态DanAbramov认为在reducer中使用getState()和subscribe()方法是一种反模式。调用store.getState会使reducer变得不纯。事实上,原始版本在reducer执行期间禁用了dispatch方法。源码如下:functiondispatch(action){//...if(isDispatching){thrownewError('Reducersmaynotdispatchactions.')}try{isDispatching=truecurrentState=currentReducer(currentState,action)}finally{isDispatching=false}varlisteners=currentListeners=nextListenersfor(vari=0;i