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

Redux、React-redux、Redux中间件详解

时间:2023-03-28 14:11:53 HTML

Redux结构有没有想过自己实现一个Redux?其实并不难。Redux主要由store、reducer、action组成。接下来,我们将尝试逐步构建Redux。Redux对reducer的一步步实现根据上图的介绍,我们知道reducer是根据传入的类型,对相关的state进行处理,然后返回一个新的state。从这里我们得到以下代码://reducer.jsconstinit={num:0}exportconstreducer=(state=init,action)=>{switch(action.type){case'add':return{...state,num:state.num+1}case'low':return{...state,num:state.num-1}default:returninit}}store我们实现了上图中的第一步,是商店。.js文件。我们首先要明确store文件主要有三个重要的功能,分别是subscribe、dispatch、getState。接下来直接贴代码分析一下。//store.jsimport{reducer}from'./reducer.js'exportconstcreateStore=()=>{letcurrentState={}letcollect=[]dispatch({})functiongetState(){returncurrentState}functiondispatch(action){currentState=reducer(currentState,action)collect.forEach(tempFunc=>tempFunc())}functionsubscribe(tempFunc){if(fninstanceofFunction){collect.push(tempFunc)}return}return{getState,dispatch,subscribe}}我们可以看到createStore函数中除了三个基本函数外还有一行dispatch({})。这其实就是初始化redux。如果reducer中的初始化没有被触发,如果对相关的值进行加减操作都会得到NaN值。那么订阅函数主要是按照观察者模式来实现的。当用户在页面上订阅了subscribe函数,然后执行dispatch操作后,会触发当前页面的所有subscribe函数。这么说很麻烦,还是上代码吧。//index.jsimportReactfrom'react'import{createStore}from'../../store/store'import{reducer}from'../../store/reducer'conststore=createStore(reducer)exportclassRollextendsReact.Component{constructor(props){super(props)this.state={num:0}}componentWillMount(){store.subscribe(()=>this.setState({num:store.getState().nu??m}))}lowNum(){store.dispatch({type:'low'})console.log('store里面的价值'+store.getState().num)}addNum(){store.dispatch({type:'add'})console.log('store里面的价值'+store.getState().num)}render(){return(this.lowNum()}>low{this.state.num}

this.addNum()}>add
)}}添加了订阅功能的效果图:没有添加订阅功能的效果图:如果不添加,其实就是状态inthestore更新了,但是store的状态没有同步到页面,导致无法触发页面的更新。react-redux的实现在react项目中一般不会直接使用redux,而是使用react-redux作为两者之间的桥梁。示例首先,让我们看一下react-redux的简单用法。//Provider伪代码ReactDOM.render()//connent伪代码ChildComponent=connect(mapStateToProps,mapDispatchToProps)(ChildComponent)ProviderProvider相当于一个容器组件,容器有多个组件层可以嵌套在里面。实际上,Provider不会对内部组件做任何处理。只需要让组件正常显示即可。它接受一个store参数,这个store参数会把外界的store参数传给context,然后让这个组件成为组件树的根节点,那么它的子组件就可以拿到context。//provider.jsimportReactfrom'react'importPropTypesfrom'prop-types'exportclassProviderextendsReact.Component{//声明上下文对象属性staticchildContextTypes={store:PropTypes.object,children:PropTypes.object}//返回Context对象中的属性actual是接收一个组件作为参数,最后返回一个新的组件,也就是我们常说的HOC(Higher-OrderComponent)。除了接收一个组件外,它还接收两个参数,一个是mapStateToProps,一个是mapDispatchToProps,这些是传入组件的props,需要通过高层组件connect传回给原来的组件。我们对这个过程有了大致的了解,可以简单的实现一下:importReactfrom'react'importPropTypesfrom'prop-types'exportfunctionconnect(mapStateToProps,mapDispatchToProps){//1.传入state和dispatchobjectsreturnfunction(WrappedCompment){//2.接收传入的组件classConnectextendsReact.Component{constructor(){super()this.state={//3.将所有props整合到一个对象中,方便写mapStateAndDispatchProps:{}}}staticcontextTypes={//4.获取contextstore中的store:PropTypes.object}componentDidMount(){const{store}=this.context//5.用于更新和合并几个传入的对象this.mergeAndUpdateProps()store。subscribe(()=>{this.mergeAndUpdateProps()})}mergeAndUpdateProps(){const{store}=this.context让tempState=mapStateToProps?mapStateToProps(store.getState(),this.props):{}让tempDispatch=mapDispatchToProps?mapDispatchToProps(store.dispatch,this.props):{}this.setState({mapStateAndDispatchProps:{...tempState,...tempDispatch,...this.props}})}render(){//把所有传入的props进入上一个组件return}}//返回新组件returnConnect}}实现效果并连接到Roll组件进行测试://Roll.jsimportReactfrom'react'import{connect}from'../../store/connect'constmapStateToProps=state=>{return{num:state.num}}constmapDispatchToProps=dispatch=>{return{addNum:()=>{dispatch({type:'add'})},lowNum:()=>{dispatch({type:'low'})}}}classRollextendsReact.Component{consstructor(props){super(props)}render(){return(this.props.lowNum()}>low{this.props.num}
this.props.addNum()}>add
)}}exportdefaultconnect(mapStateToProps,mapDispatchToProps)(Roll)最终结果:redux中间件(middleware)大家可能都用过redux的一些中间件软件,比如redux-thunk、redux-saga、redux-logger等,但是这些中间件是如何实现的呢?我们走到一起为什么首先需要中间件?假设有一个场景,我们需要打印每次dispatch的记录,很容易想到执行dispatch后就可以打印:functiondispatchAndPrint(store,dispatch){dispatch({type:'add'})安慰。log('newState:',store.getState())}但是现在还有一个需求,我们需要继续捕获dispatch中的错误,那我们怎么写呢:functiondispatchAndCatch(store,dispatch){try{dispatch({type:'add'})}catch(e){console.error('dispatcherror:',err)throwe}}然后如果需求越来越多,我们其实会写越来越多的dispatch,在事实上,我们可以提取这个调度步骤:store.dispatch=functiondispatchAndCatch(store,dispatch){try{next({type:'add'})}catch(e){console.error('dispatcherror:',err)throwe}}applyMiddleware当我们使用redux中的中间件,我们将使用applyMiddleware。applyMiddleware的作用其实和我们上面写的例子差不多。你可以理解为applyMiddleware先获取一个dispatch,然后在middleware中修改dispatch。具体dispatch会怎么转化,就看我们的中间件了。为此,我们可以实现一个简单版本的applyMiddleware函数。constapplyMiddleware=function(store,middleware){letnext=store.dispatch;store.dispatch=middleware(store)(next);}applyMiddleware(dispatchAndPrint)我们在使用applyMiddleware的时候其实是调用了多个中间件链式调用当然不是说一次只能使用一个中间件,那么如何实现如果使用多个中间件?我们可以将上一个中间件返回的dispatch作为下一个中间件的next函数传递,我们可以curry这两个函数:constdispatchAndPrint=store=>next=>action=>{console.log('newState:',store.getState())returnnext(action)}constdispatchAndCatch=store=>next=>action=>{try{next(action)}catch(e){console.error('dispatcherror:',err)throwe}}WriteapplyMiddleware:functionapplyMiddleware(store,middlewares){//浅拷贝防止后面逆向影响原来的中间件middlewares=middlewares.slice()//放在最前面的中间件应该在前面执行,这里如果不翻转数组,先放的函数会在最内层,会导致middlewares.reverse()最后执行。让dispatch=store.dispatchmiddlewares.map((middleware)=>{dispatch=middleware(store)(dispatch)})return{...store,dispatch}}这里我们对applyMiddleware函数进行说明。其实middlewares就是一个中间件数组。我们把middlewares数组倒过来是因为每次我们的middleware函数只是返回一个新的dispatch函数给下一个middleware,最后得到的是最后一个middleware返回的函数包装了dispatch。如果反转,则先执行最后一个中间件,然后不断向前推进执行第一个中间件走进applyMiddleware的源码当然,如果我们看applyMiddleware的源码,并不会像我们一样直接把中间件数组倒过来,而是如下写法:functionapplyMiddleware(...middlewares){return(createStore)=>(reducer,preloadedState,enhancer)=>{varstore=createStore(reducer,preloadedState,enhancer);vardispatch=store.dispatch;变量链=[];varmiddlewareAPI={getState:store.getState,dispatch:(action)=>dispatch(action)};chain=middlewares.map(middleware=>middleware(middlewareAPI));dispatch=compose(...chain)(store.dispatch);return{...store,dispatch}}}compose函数的实现:functioncompose(...funcs){if(funcs.length===0){returnarg}if(funcs.length===1){//只需要执行一个函数,执行函数,返回结果即可returnfuncs[0]}//当执行多个函数时,使用reduce递归处理这些函数returnfuncs.reduce((a,b)=>(...args:any)=>a(b(...args)))我们可以看到在applyMiddleware的源码中,compose函数其实是用来将前面中间件的返回值传递给next中间件作为参数,从而达到连接中间件的效果。如果中间件的顺序是a,b,c,compose函数组合的结果是c(b(a(...args))),执行顺序是a->b->c。总结可能后面看到redux-thunk的源码的时候,可能会疑惑为什么这个库这么简单,只有几行代码,但是大可不必惊讶,因为即使是redux也不是很复杂,但是背后的JS编程思想值得学习,比如函数柯里化、函数式编程、装饰器等等。信息:8k字|redux/react-redux/redux中间件设计与实现redux中间件原理解析Redux入门教程(二):中间件与异步操作javascript函数curry代码组合(compose)