当前位置: 首页 > 后端技术 > Node.js

深入redux技术栈

时间:2023-04-03 13:32:35 Node.js

本文是继上一篇《React进阶漫谈》后的第二篇文章。本文主要分析redux的思想和应用,也参考了网上的大量资料,不过代码也是自己尝试和实践过的,在此分享,供大家一起学习(最后地址:个人blog/segmentFault)注意:本文中所有的示例代码都被合成了一个小demo放在了这里。如果您觉得这个demo对您的学习有一点帮助,请给个star支持。redux介绍本文默认大家对react和flux架构有一定的了解,使用过或者了解过redux,所以不会从最基础的开始,直接对redux进行总结。如果你从未使用过redux,最好先看看这里。要理解redux,首先要总结一下redux的一些设计原则:在Redux中,只用一个单一的对象树结构来存储整个应用的状态,也就是整个应用都会用到的数据,我们称之为store(存储).除了存储的数据,store还可以存储整个应用的状态(包括router的状态,后面会介绍)。因此,通过Store,可以实现整个应用的即时保存功能(创建快照)。另外,这种设计也为服务端渲染提供了可能。状态为只读,符合flux的设计理念。我们不能在components中改变store的状态(其实redux会根据reducer生成store),只能通过dispatch触发action迭代当前state。这里我们也没有直接修改应用的状态,而是返回了一个全新的状态。状态修改由纯函数组成。Redux中的reducer的原型会像下面这样,你可以把它看作是前一个状态+action=新状态的公式:(previousState,action)=>newState对于每个reducer它们都是纯函数,也就是说他们没有任何副作用。这种设计的好处不仅仅在于使用reducer修改状态变得简单,纯可测试。此外,redux可以保存每个返回状态,从而轻松生成时间旅行并跟踪每个动作引起的变化结果。如果我们在react中使用redux,那么我们同时需要react-redux和redux。redux架构和源码分析这部分主要说一下自己的理解。可能有点抽象,也可能不是完全正确,可以直接跳过。createStoredux中的核心方法是createStore。React的核心功能都在createStore及其最终生成的store中涵盖了。createStore方法本身支持三个参数:reducer、initialState和enhancer。Enhancer可以用作增强的包装函数。我们不是很常用。这个函数内部维护了一个currentState,这个currentState可以通过getState函数(内置)返回。另外,它实际上实现了一种发布-订阅的模式,通过store.subscribe来订阅事件。这项工作是由react-redux隐式完成的,这是在有dispatch更新整个状态树时触发所有的监听器。另外,内置的dispatch函数在经过一系列的检查后触发reducer,然后改变state,然后依次调用listener完成整个statetree的更新。middleWare用过redux的朋友其实对redux-thunk这样的中间件并不陌生。事实上,在很多情况下,它是不可或缺的。Redux对中间件也有很好的支持。我觉得这个概念类似于nodejs的中间件机制。有些是类似的:action依次经过每个middleWare再传递给下一个,每个middleWare还可以进行其他的操作比如中断或者改变action,直到最终的处理功能交给reducer。redux的applyMiddleware函数非常精炼:exportdefaultfunctionapplyMiddleware(...middlewares){return(createStore)=>(reducer,preloadedState,enhancer)=>{varstore=createStore(reducer,preloadedState,enhancer)vardispatch=店铺。dispatchvarchain=[]varmiddlewareAPI={getState:store.getState,dispatch:(action)=>dispatch(action)//注意这里的dispatch不是原来的store.dispatch,而是实际变了}chain=middlewares.map(middleware=>middleware(middlewareAPI))dispatch=compose(...chain)(store.dispatch)return{...store,dispatch}}}核心是dispatch=compose(...chain)(store.dispatch),这句话是对每个中间件的链式调用,其中compose的源码:exportdefaultfunctioncompose(...funcs){if(funcs.length===0){returnarg=>arg}if(funcs.length===1){returnfuncs[0]}constlast=funcs[funcs.长度-1]constrest=funcs。slice(0,-1)return(...args)=>rest.reduceRight((composed,f)=>f(composed),last(...args))}将上一个函数的执行结果调用到下一个函数。其实写一个中间件的过程也很简单。比如redux-trunk其实有这样的内容:,getState,extraArgument);}返回下一个(动作);};}constthunk=createThunkMiddleware();thunk.withExtraArgument=createThunkMiddleware;exportdefaultthunk;redux和routing当然,我们首先声明react工具集的react-router不一定非要和redux一起使用,而是redux还有一个react-router-redux可以配合react-router和redux一起使用,效果很好。因为我们这部分没有介绍如何使用react-router,所以react-router的使用请参考中文文档。react-router的特性允许开发者通过JSX标签来声明路由,这使得我们的路由写起来非常友好,声明式路由的表达能力是比较强的。嵌套路由和路由匹配:参数可以在指定路径中传递:另外,如果参数是可选的,我们可以用括号(:可选参数)。支持多种路由切换方式:我们知道目前的路由切换方式无非就是使用hashchange和pushState。前者有更好的浏览器兼容性,但看起来不像是真实的url,而后者为我们提供了优雅的url体验,但服务器需要解决任意路径刷新的问题(服务器应该自动重定向到首页).为什么需要react-router-redux简单来说,react-router-redux可以让我们把路由当做状态的一部分,使用redux改变路由:直接调用dispatch:this.props.push(“/detail/”);,这样路由也被看作是一个全局状态,路由状态也是应用状态的一部分,可能更利于前端状态管理。react-router-redux需要和react-router一起使用,不能单独使用。在原项目中加入react-router-redux并不复杂:import{createStore,combineReducers,compose,applyMiddleware}from'redux';import{routerReducer,routerMiddleware}from'react-router-redux';import{hashHistory}从'react-router';从'redux-thunk'导入ThunkMiddleware;从'./reducers'导入rootReducer;从'.importDevTools。/DevTools';constfinalCreateStore=compose(applyMiddleware(ThunkMiddleware,routerMiddleware(hashHistory)),DevTools.instrument())(createStore);console.log("rootReducer",rootReducer);constreducer=combineReducers({rootReducer,路由:routerReducer,});导出默认函数configureStore(initialState){conststore=finalCreateStore(reducer,initialState);returnstore;}另外,上面提到的demoreact-router-redux-demo使用了react-router和react-router-redux,当然redux的一些其他的好东西也用到了,比如redux-devtools,有的朋友有兴趣的可以点这里reduxandcomponents这部分是关于一个组件编写规范,而不是一些库或架构,这些规范帮助我们在复杂的项目中组织页面而不杂乱。从布局的角度来看,redux强调三种不同的布局组件,Layouts,Views,Components:mostouterrouter的组件参数不承担直接与redux交互的功能。比如我项目中的Layouts组件:constFrame=(props)=>

{props.children}
;Views组件,我觉得这个组件是Components或者Components组的高层组件,这一层可以和redux交互,处理数据,我们可以集成一个整体的功能component组下放一个Views(注:由于我给的demo很简单,所以Views层和Components层并没有那么分开)Components组件,也就是最终的渲染组件。一般来说,这一层组件的数据是通过props传入的,并不直接与redux的单向数据流交互。它可以是一个木偶式的无状态组件,也可以是一个具有少量交互和自身状态的组件。此级别的组件可以大量重复使用。总而言之,遵守这套规范并不是强制性的,但是一旦项目稍微复杂一点,这样做的好处就可以充分体现出来。redux和formredux的单向数据流相对于双向数据绑定在处理表单等问题上有点力不从心,不过好在已经开源了几个不错的插件:redux-form-utils,不错,这个插件的star数很少,但是比较简单,而且源码也比较短,只有200多行,所以这是一个值得源码学习的插件代码(它的源码结构也很简单,就是先设置一个高层组件,这个高层组件可以给我们自己定义的表单组件传入新的props,自定义组件,后面就是定义一些actions和reducers,负责在内容变化时通知statetree),但是缺点是这个插件没有做表单验证的工作,所以如果我们需要表单验证,还是需要自己做一些工作。另外,这个插件源码使用了::ES6语法,其实就是一个语法糖,可以在es6的类内部使用babel-preset-stage-0::::this.[functionName]相当于this.[functionName].bind(this,args?)redux形式。该插件功能复杂,代码完整,体积非常庞大。可以参考文档使用,但是理解源码只是一件比较麻烦的事情。但是这个插件需要在redux应用的状态下挂载一个节点。这个节点不需要开发者自己控制。他唯一需要做的就是编写一个提交函数。我也稍微修改了我demo中的一个例子,用起来感觉更舒服了。Redux性能优化我们要实现redux性能优化,首先要知道redux的性能可能受到哪些影响,否则没有目标就没有办法优化性能。因为本人不是redux的老手,所以无法涵盖所有??的性能优化点。我总结两点:有时候,我们需要的数据格式会有自己的冗余,可以提取一些公共的部分来减小体积,比如我们需要的数据格式可能是这样的:}{name:"Mark",title:"国家一级运动员"}]这个时候我们其实可以优化成这样:[{"国家一级运动员":"Nike","Mark""国家first-levelathlete"Levelreferee":"Jenny","Nike"}]这时候我们可以直接使用后者作为store的格式,我们使用reselect库将其转换成我们想要的格式。关于如何使用上面的链接进行reselect更详细的例子,这里就不过多介绍了,其实对于redux来说,每次store发生变化,所有的connects都会重新计算,在大型应用中,浪费时间可以想象,为了减少性能浪费,我们可以在connect缓存中做selector,上面提到的reselect库自带缓存特性,我们可以通过比较参数来判断是否使用缓存。这里使用了纯函数的特性。reselect的缓存功能可以由用户自定义。有关详细信息,请参阅上面链接到github的自述文件。