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

React系列之Redux架构模式

时间:2023-04-03 14:51:59 Node.js

原文地址:https://gmiam.com/post/react-...没想到这篇文章这么晚才发表。最近发生了太多事情,导致没有了心态,最后离开了现在的公司,没想到会这么狼狈。一个人的光辉可以很大也可以很小。如果不能好好规划,就只能默默接受,世上没有类似的同理心。只有感受真实,才能真正认清一切,我们进入正题,看看Flux架构相对繁琐。与此同时,社区中涌现出众多第三方框架模型,Redux脱颖而出。React以组件的形式维护着一个UI树,但是没有状态数据。为了做更多的处理,Redux将状态数据抽象成一棵树来维护自己。它与React没有直接关系。它可以与其他框架结合使用,也可以与React结合使用。Redux的代码量很短,核心只提供了5个API:createStorecombineReducersbindActionCreatorsapplyMiddlewarecompose让我们直观感受一下Reduximport{createStore}from'redux';functioncounter(state=0,action){switch(action.type){case'INCREMENT':返回状态+1;case'DECREMENT':返回状态-1;默认值:返回状态;}}letstore=createStore(counter);store.subscribe(()=>console.log(store.getState()));store.dispatch({type:'INCREMENT'});//1store.dispatch({type:'INCREMENT'});//2store.dispatch({type:'DECREMENT'});//1外观可以看出入口是createStore,接收一个函数(这里叫reducer),这个函数接收两个参数状态和动作,然后调度一个对象(这里称为action,包含一个type属性来表示行为),reducer函数会被触发执行来操作状态,同时也会触发subscribe订阅回调。回调可以通过store.getState()获取当前状态数据。这里很简单,那么如果我们需要处理的数据和状态越来越多,reducer的功能就会越来越大,难以维护,所以Redux提供了combineReducers来应对这种情况,将这个大reducer分解成smallreducers,每个smallreducer维护自己的state数据,从而为下一个variantreducers/todos.jsexport分解出一个statetreedefaultfunctiontodos(state=[],action){switch(action.type){case'ADD_TODO':returnstate.concat([action.text])default:returnstate}}reducers/counter.jsexport默认函数counter(state=0,action){switch(action.type){case'INCREMENT':returnstate+1case'DECREMENT':返回状态-1默认值:返回状态counter})App.jsimport{createStore}from'redux'importreducerfrom'./reducers/index'letstore=createStore(reducer)console.log(store.getState())//{//计数ter:0,//todos:[]//}store.dispatch({type:'ADD_TODO',text:'UseRedux'})console.log(store.getState())//{//计数器:0,//todos:['UseRedux']//}可以看到我们使用combineReducers把reducer做了拆分,combineReducers部分精简源码exportdefaultfunctioncombineReducers(reducers){varreducerKeys=Object.keys(reducers)varfinalReducers={}for(vari=0;i{setTimeout(()=>{dispatch(increment());},1000)}}conststore=createStore(rootReducer复制代码,applyMiddleware(thunk))store.dispatch(increment())//同步store.dispatch(incrementAsync())//异步同步方法的触发和之前一样。这里的异步支持依赖于Redux的applyMiddleware中间件模式和thunk中间件来增强支持。查看applyMiddleware和createStore部分源码exportdefaultfunctionapplyMiddleware(...middlewares){return(createStore)=>(reducer,preloadedState,enhancer)=>{varstore=createStore(reducer,preloadedState,enhancer)vardispatch=store.dispatchvarchain=[]varmiddlewareAPI={getState:store.getState,dispatch:(action)=>dispatch(action)}chain=middlewares.map(中间件=>中间件(middlewareAPI))dispatch=compose(...chain)(store.dispatch)return{...store,dispatch}}}exportdefaultfunctioncreateStore(reducer,preloadedState,enhancer){if(typeofpreloadedState==='function'&&typeofenhancer==='undefined'){enhancer=preloadedStatepreloadedState=undefined}if(typeofenhancer!=='undefined'){returnenhancer(createStore)(reducer,preloadedState)}....return{调度,subscribe,getState,replaceReducer,[$$observable]:observable}}createStore里所说的增强就是applyMiddleware一些中间件,conststore=createStore(rootReducer,applyMiddleware(thunk))和下面写法是等有效的conststore=applyMiddleware(砰)(创造eStore)(rootReducer)看上面applyMiddleware的源码可以知道,原来的store会先用createStore创建,然后getState和dispatch传递给中间件。中间件处理完后,会返回展开后的store。看一下thunk中间件的源码){return({dispatch,getState})=>next=>action=>{if(typeofaction==='function'){returnaction(dispatch,getState,extraArgument);}返回下一个(动作);};}constthunk=createThunkMiddleware();thunk.withExtraArgument=createThunkMiddleware;导出默认thunk;很简单的传入dispatch,getStatereturnsnext=>action=>{...},然后传入store.dispatchreturnaction=>{...}即扩展的dispatch。这个新的调度也接受动作。如果是对象,直接用原来的store.dispatch触发。如果是函数,将dispatch传入函数体,把控制权交给函数。请注意,它将在稍后执行。收到的dispatch已经是可以处理函数的extendeddispatch了。再看composeAPI,applyMiddleware可以接受一系列的中间件,内部调用compose做处理compose(...chain)等价于(...args)=>f(g(h(...args)))也就是说传入一组函数,它会倒序执行,将前一个的执行结果传递给下一个,达到渐进增强的效果。说到Redux和它的API就差不多介绍完了。至于bindActionCreators,介绍了这么多,大家可以看看Redux本身能运行,那怎么和React结合呢?然后你需要react-redux这个中间桥梁。react-redux提供了两个APIProviderstoreconnect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])Provider是一个React组件,它接收一个store属性,store挂在React的Context上,这样它的子组件就可以获取不显示传输存储请参见示例import{Provider}from'react-redux'conststore=createStore(reducer)render(,document.getElementById('root'))那么问题来了,拿到store之后,如何与React和Redux进行交互和通信,这时connectAPI就派上用场了我们继续看一个例子import{bindActionCreators}from'redux'constApp=({todos,动作})=>(

)constmapStateToProps=state=>({todos:state.todos})constmapDispatchToProps=dispatch=>({actions:bindActionCreators(TodoActions,dispatch)})exportdefaultconnect(mapStateToProps,mapDispatchToProps)(App)连接源代码执行大概是这样的||context.store}render(){constmappedState=mapStateToProps(store.getState(),this.props)constmappedDispatch=mapDispatchToProps(store.dispatch,this.props)constmergedProps={mappedState,mappedDispatch}this.renderedElement=createElement(WrappedComponent,mergedProps)returnthis.renderedElement}}}}这里,我们做了适当的简化,从中可以看出connect返回一个Connect组件去store,然后通过store.getState()和store.dispatch给我们的mapStateToProps和mapDispatchToProps函数,通过props将相应的数据和方法返回给React组件,让React组件获取相应的数据显示,也可以通过dispatch触发Reduxstore的数据变化,Connect组件对比数据,看是否需要重新渲染。connect的实际代码比这复杂很多,内部做了详细的浅层数据对比,以提高性能。对于react-redux,有一个潜在的规则就是显示组件和容器组件分离,也就是说只有容器组件处理与Redux的数据和状态通信,显示组件只做正常的UI渲染。您可以从这里了解更多信息http://redux.js.org/docs/basi...看上面的constmapDispatchToProps=dispatch=>({actions:bindActionCreators(TodoActions,dispatch)})将转换的每个方法传入的函数或对象转换成下面的函数bindActionCreator(actionCreator,dispatch){return(...args)=>dispatch(actionCreator(...args))}这样,当React组件调用相应的action时,actionCreator产生的数据是可以调度的,可以看https://github.com/reactjs/re...这个例子是为了加深对目录结构的理解和分工。当然,如果你有兴趣多了解一些例子就更好了。本文终于到此结束。最后,大家加油!