奇怪,怎么把函数的柯里化和Redux中间件,两个不相干的东西,连在一起,如果你和我有同样的疑问,说明你不会完全理解Redux中间件的原理。先说说什么是函数柯里化?下面说一下Redux的中间件和applyMiddleware的源码。查看演示以查看源代码。欢迎使用star高阶函数。提到函数的柯里化,首先要说的就是高阶函数。高阶函数满足以下两个要求:满足其中一个条件的函数:函数可以作为参数函数可以作为返回值看到这个,大家应该秒懂了吧,像setTimeout,map,filter,reduce等我们平时用到的都是高阶函数,当然我们也有今天要讲的函数柯里化也是高阶函数的应用函数柯里化什么是函数柯里化?看过《JSElevation》这本书的人应该知道,有一章是专门讲JS高级技术的,其中对函数柯里化的描述如下:用于创建一个已经设置了一个或多个参数的函数。函数柯里化的基本用法与函数绑定相同:使用闭包返回一个函数。两者的区别在于调用函数时,返回的函数还需要设置一些传入参数。听起来有点混乱,对吧?我们来看一个例子constadd=(num1,num2)=>{returnnum1+num2}constsum=add(1,2)add是一个返回两个参数之和的函数,如果要柯里化add,如下constcurryAdd=(num1)=>{return(num2)=>{returnnum1+num2}}constsum=curryAdd(1)(2)更通用的写法如下:constcurry=(fn,...initArgs)=>{letfinalArgs=[...initArgs]return(...otherArgs)=>{finalArgs=[...finalArgs,...otherArgs]if(otherArgs.length===0){returnfn.apply(this,finalArgs)}else{returncurry.call(this,fn,...finalArgs)}}}我们正在转换我们的add以便它可以接收任意数量的参数constadd=(...args)=>args.reduce((a,b)=>a+b)使用我们上面写的curry将add转换成curryconstcurryAdd=curry(add)curryAdd(1)curryAdd(2,5)curryAdd(3,10)curryAdd(4)constsum=curryAdd()//25注意最后一定要调用curryAdd()返回运算结果。你也可以修改咖喱。当传入的参数个数达到fn指定的参数个数时,返回运算结果。简而言之,函数的柯里化就是将多参数函数转换为单参数函数。这里的单参数不仅仅指一个参数。我的理解是参数分了。PS:敏感的同学应该看到这个和ES5的bind函数的实现和我先实现的bind函数很相似。Function.prototype.bind=function(context,...initArgs){constfn=thisletargs=[...initArgs]returnfunction(...otherArgs){args=[...args,...otherArgs]returnfn.call(context,...args)}}varobj={name:'monkeyliu',getName:function(){控制台。log(this.name)}}vargetName=obj.getNamegetName.bind(obj)()//MonkeyliuElevation是这样评价他们两个的:ES5的bind方法也实现了函数的柯里化。使用bind还是curry取决于是否需要object对象响应。它们都可以用来创建复杂的算法和函数,当然也不应该被滥用,因为每个函数都会带来额外的开销ReduxmiddlewareReduxmiddleware什么是Reduxmiddleware?我的理解是允许用户在dispatch(action)前后添加自己的代码。当然这种理解可能不是特别准确,但是对于刚接触redux中间件的同学来说,这是最好的理解方式。我会通过一个记录日志和打印执行时间的例子来帮助大家思考从分析问题到构建中间件解决问题的过程当我们派发一个action的时候,我们要记录当前的action值,记录变化后的state值.?最愚蠢的手动记录方式是在dispatch之前打印当前action,dispatch之后打印变化的状态。你的代码可能是这样的是大多数人都会想到的方法。简单,但通用性差。如果我们要在很多地方记录Log,上面的代码会写多次来封装Dispatch,为了重用我们的代码,我们会尝试把上面的代码封装成一个函数constdispatchAndLog=action=>{console.log('dispatching:',action)store.dispatch(action)console.log('nextstate:',store.getState())}但是这样只会减少我们的代码量,每次需要用到的时候还是要引入这个it方法,治标不治本。将原来的dispatch改造为直接覆盖store.dispatch,这样就不用每次都引入dispatchAndLog了。这种方法被网友称为monkeypatch。你的代码可能看起来像constnext=store.dispatchstore.dispatch=action=>{console.log('dispatching:',action)next(action)console.log('nextstate:',store.getState())}这样一改,多处使用,已经可以达到我们想要的目的了,但是,还没完(还没完)记录执行时间除了记录日志,我们还需要记录dispatch前后的执行时间。我们需要再创建一个中间件,然后依次执行这两个。你的代码可能是这样的',store.getState())}}constdate=store=>{constnext=store.dispatchstore.dispatch=action=>{constdate1=Date.now()console.log('date1:',date1)next(action)constdate2=Date.now()console.log('date2:',date2)}}logger(store)date(store)但在这种情况下,打印结果如下:date1:dispatching:nextstate:date2:中间件输出的结果与中间件的执行顺序相反使用高阶函数如果我们不覆盖logger和date中的store.dispatch,而是使用高阶函数返回一个新的函数,结果是什么?constlogger=store=>{constnext=store.dispatchreturnaction=>{console.log('dispatching:',action)next(action)console.log('nextstate:',store.getState())}}constdate=store=>{constnext=store.dispatchreturnaction=>{constdate1=Date.now()console.log('date1:',date1)next(action)constdate2=Date.now()console.log('date2:',date2)}}然后我们需要创建一个函数来接收记录器和日期,在这个函数体中我们循环遍历它们,赋值给store.dispatch,这个函数就是applyMiddleware的原型})}然后我们可以像applyMiddlewareByMonkeypatching(store,[logger,date])一样应用我们的中间件但这仍然是猴子补丁,但是我们将其实现细节隐藏在applyMiddlewareByMonkeypatching中并结合函数currymiddleware的一个重要特征是后者中间件可以使用之前中间件包装的store.dispatch。我们可以通过柯里化函数来实现。我们将之前的记录器和日期转换为constlogger=store=>next=>action=>{console.log('dispatching:',action)next(action)console.log('nextstate:',store.getState())}缺点tdate=store=>next=>action=>{constdate1=Date.now()console.log('date1:',date1)next(action)constdate2=Date.now()console.log('date2:',date2)}redux中间件就是上面这样写的,next是前一个中间件返回的函数,next返回一个新的函数作为下一个中间件的输入值,所以我们的applyMiddlewareByMonkeypatching也需要进行改造,我们将其命名为applyMiddlewareconstapplyMiddleware=(store,middlewares)=>{middlewares.reverse()letdispatch=store.dispatchmiddlewares.map(middleware=>{dispatch=middleware(store)(dispatch)})return{...store,dispatch}}我们可以这样用有一定的区别。下面我们分析一下原生applyMiddleware的源码,了解一下它们的区别。applyMiddleware源码直接上传applyMiddleware源码。exportdefaultfunctionapplyMiddleware(...middlewares){returncreateStore=>(...args)=>{conststore=createStore(...args)letdispatch=()=>{thrownewError(`在构造中间件时进行调度是不允许的。`+`其他中间件不会应用于此调度。`)}constmiddlewareAPI={getState:store.getState,dispatch:(...args)=>dispatch(...args)}constchain=middlewares.map(middleware=>middleware(middlewareAPI))dispatch=compose(...chain)(store.dispatch)return{...store,dispatch}}}原生的applyMiddleware是在createStore的第二个参数中,我们也贴上了createStore的相关核心代码,然后分析exportdefaultfunctioncreateStore(reducer,preloadedState,enhancer){if(typeofpreloadedState==='function'&&typeofenhancer==='undefined'){enhancer=preloadedStatepreloadedState=undefined}if(typeofenhancer!=='undefined'){if(typeofenhancer!=='function'){thrownewError('Expectedtheenhancertobeafunction.')}returnenhancer(createStore)(reducer,preloadedState)}....}当通过时进入applyMiddleware,这时候,最后执行enhancer(createStore)(reducer,preloadedState),返回一个store对象,enhancer就是我们传入的applyMiddleware,我们先执行它,返回一个函数,里面有个createStore参数,然后我们继续执行enhancer(createStore)并返回一个函数,最后我们执行enhancer(createStore)(reducer,preloadedState),我们来分析一下这个函数体做了什么?conststore=createStore(...args)首先使用reducer和preloadedState创建一个store对象appliedtothisdispatch.`)}这段代码表示在构建中间件的过程中不能调用dispath函数,否则会抛出异常constmiddlewareAPI={getState:store.getState,dispatch:(...args)=>dispatch(...args)}定义了middlewareAPI对象包含两个属性getState和dispatch,作为中间件的输入参数storeconstchain=middlewares.map(middleware=>middleware(middlewareAPI))chain是一个数组,数组的每一项都是一个函数,其输入参数是next并返回另一个函数数组的每一项可能像这样consta=next=>{returnaction=>{console.log('dispatching:',action)next(action)}}最后几行代码dispatch=compose(...chain)(store.dispatch)return{...store,dispatch}其中compose实现代码如下exportdefaultfunctioncompose(...funcs){if(funcs.length===0){returnarg=>arg}if(funcs.length===1){returnfuncs[0]}returnfuncs.reduce((a,b)=>(...args)=>a(b(...args)))}compose是一个Merge方法,当没有传入funcs时,会返回一个arg=>arg函数,当funcs的长度为1时,会返回funcs[0],当funcs的长度大于1时,它将执行合并操作,让我们举个例子constfunc1=(a)=>{returna+3}constfunc2=(a)=>{returna+2}constfunc3=(a)=>{returna+1}constchain=[func1,func2,func3]constfunc4=compose(...chain)func4就是这样一个函数func4=(args)=>func1(func2(func3(args)))所以上面的调度=compose(...chain)(store.dispatch)就是这样一个函数constchain=[logger,date]dispatch=compose(...chain)(store.dispatch)//相当于dispatch=action=>logger(日期(商店.dispatch))最后将store对象传出去,用我们的dispatch覆盖store中的dispatchreturn{...store,dispatch}。现在整个applyMiddleware的源码分析完毕,发现它并没有想象中的那么神秘,必须时刻保持求知欲和手写applyMiddleware的区别,差点忘了这一点。说完applyMiddleware的源码,再说说和自己手写的applyMiddleware的区别。区别三点:原来的只提供了getState和dispatch,而我手写的提供了store中所有的属性和方法。原生中间件只能应用一次,因为它作用于createStore;还有我自己手写的一个作用于store,可以调用多次。原生中间件可以调用中间件中的store.dispatch方法,不会产生任何副作用,我们手写的会覆盖store.dispatch方法。这个原生实现对于异步中间非常有用。最后查看demo查看源码。欢迎加星。你们的打赏是我写作的动力
