函数的柯里化与Redux中间件及applyMiddleware源码剖析

新鲜,如何把函数的柯里化和Redux中间件这两个八棍子撂不着的东西联络到了一同,假如你和我有一样疑问的话,申明你对Redux中间件的道理基础就不相识,我们先来讲下什么是函数的柯里化?再来讲下Redux的中间件及applyMiddleware源码

检察demo

检察源码,迎接star

高阶函数

说起函数的柯里化,就必需先说一下高阶函数(high-order function),高阶函数是满足下面两个前提个中一个的函数:

  • 函数可以作为参数
  • 函数可以作为返回值

看到这个,人人应当秒懂了吧,像我们日常平凡运用的setTimeout,map,filter,reduce等都属于高阶函数,固然另有我们本日要说的函数的柯里化,也是高阶函数的一种运用

函数的柯里化

什么是函数的柯里化?看过JS高程一书的人应当晓得有一章是特地讲JS高等技能的,个中关于函数的柯里化是如许形貌的:

它用于建立已设置好了一个或多个参数的函数。函数的柯里化的基础运用要领和函数绑定是一样的:运用一个闭包返回一个函数。两者的区分在于,当函数被挪用时,返回的函数还须要设置一些传入的参数

听得有点懵逼是吧,来看一个例子

const add = (num1, num2) => {
    return num1 + num2
}

const sum = add(1, 2)

add是一个返回两个参数和的函数,而假如要对add举行柯里化革新,就像下面如许

const curryAdd = (num1) => {
    return (num2) => {
        return num1 + num2
    }
}
const sum = curryAdd(1)(2)

更通用的写法以下:

const curry = (fn, ...initArgs) => {
    let finalArgs = [...initArgs]
    return (...otherArgs) => {
        finalArgs = [...finalArgs, ...otherArgs]
        if (otherArgs.length === 0) {
            return fn.apply(this, finalArgs)
        } else {
            return curry.call(this, fn, ...finalArgs)
        }
    }
}

我们在对我们的add举行革新来让它可以吸收恣意个参数

const add = (...args) => args.reduce((a, b) => a + b)

再用我们上面写的curry对add举行柯里化革新

const curryAdd = curry(add)

curryAdd(1)
curryAdd(2, 5)
curryAdd(3, 10)
curryAdd(4)
const sum = curryAdd() // 25

注重我们末了必需挪用curryAdd()才返回操纵效果,你也可以对curry举行革新,当传入的参数的个数到达fn指定的参数个数就返回操纵效果

总之函数的柯里化就是将多参数函数转换成单参数函数,这里的单参数并不仅仅指的是一个参数,我的明白是参数切分

PS:敏感的同砚应当看出来了,这个和ES5的bind函数的完成很像。先来一段我本身完成的bind函数

Function.prototype.bind = function(context, ...initArgs) {
    const fn = this
    let args = [...initArgs]
    return function(...otherArgs) {
        args = [...args, ...otherArgs]
        return fn.call(context, ...args)
    }
}

var obj = {
    name: 'monkeyliu',
    getName: function() {
        console.log(this.name)
    }
}

var getName = obj.getName
getName.bind(obj)() // monkeyliu

高程内里这么评价它们两个:

ES5的bind要领也完成了函数的柯里化。运用bind照样curry要根据是不是须要object对象相应来决议。它们都能用于建立庞杂的算法和功用,固然两者都不该滥用,由于每一个函数都邑带来分外的开支

Redux中间件

什么是Redux中间件?我的明白是在dispatch(action)前后许可用户增加属于本身的代码,固然这类明白可以并非迥殊正确,然则关于刚打仗redux中间件的同砚,这是明白它最好的一种体式格局

我会经由历程一个纪录日记和打印实行时间的例子来协助列位从剖析题目到经由历程构建 middleware 解决题目标思维历程

当我们dispatch一个action时,我们想纪录当前的action值,和纪录变化以后的state值该如何做?

手动纪录

最笨的方法就是在dispatch之前,打印当前的action,在dispatch以后打印变化以后的state,你的代码多是如许

const action = { type: 'increase' }
console.log('dispatching:', action)
store.dispatch(action)
console.log('next  state:', store.getState())

这是平常的人都邑想到的方法,简朴,然则通用性较差,假如我们在多处都要纪录日记,上面的代码会被写屡次

封装Dispatch

要想复用我们的代码,我们会尝试封装下将上面那段代码封装成一个函数

const dispatchAndLog = action => {
    console.log('dispatching:', action)
    store.dispatch(action)
    console.log('next  state:', store.getState())
}

然则如许的话只是减少了我们的代码量,在须要用到它的处所我们照样得每次引入这个要领,治标不治本

革新原生的dispatch

直接掩盖store.dispatch,如许我们就不必每次引入dispatchAndLog,这类方法网上人称作monkeypatch(猴戏打补),你的代码多是如许

const next = store.dispatch
store.dispatch = action => {
    console.log('dispatching:', action)
    next(action)
    console.log('next  state:', store.getState())
}

如许已能做到一次修改,多处运用,已能到达我们想要的目标了,然则,it’s not over yet(还没完毕)

纪录实行时间

当我们除了要纪录日记外,还须要纪录dispatch前后的实行时间,我们须要新建别的一个中间件,然后顺次去实行这两个,你的代码多是如许

const logger = store => {
    const next = store.dispatch
    store.dispatch = action => {
        console.log('dispatching:', action)
        next(action)
        console.log('next  state:', store.getState())
    }
}

const date = store => {
    const next = store.dispatch
    store.dispatch = action => {
        const date1 = Date.now()
        console.log('date1:', date1)
        next(action)
        const date2 = Date.now()
        console.log('date2:', date2)
    }
}

logger(store)
date(store)

然则如许的话,打印效果以下:

date1: 
dispatching: 
next  state: 
date2: 

中间件输出的效果和中间件实行的递次相反

应用高阶函数

假如我们在logger和date中不去掩盖store.dispatch,而是应用高阶函数返回一个新的函数,效果又是如何呢?

const logger = store => {
    const next = store.dispatch
    return action => {
        console.log('dispatching:', action)
        next(action)
        console.log('next  state:', store.getState())
    }
}

const date = store => {
    const next = store.dispatch
    return action => {
        const date1 = Date.now()
        console.log('date1:', date1)
        next(action)
        const date2 = Date.now()
        console.log('date2:', date2)
    }
}

然后我们须要建立一个函数来吸收logger和date,在这个函数体内里我们轮回遍历它们,将他们赋值给store.dispatch,这个函数就是applyMiddleware的雏形

const applyMiddlewareByMonkeypatching = (store, middlewares) => {
    middlewares.reverse()
    middlewares.map(middleware => {
        store.dispatch = middleware(store)
    })
}

然后我们可以如许运用我们的中间件

applyMiddlewareByMonkeypatching(store, [logger, date])

然则如许依然属于猴戏打补,只不过我们将它的完成细节,隐藏在applyMiddlewareByMonkeypatching内部

连系函数柯里化

中间件的一个主要特征就是后一个中间件可以运用前一个中间件包装过的store.dispatch,我们可以经由历程函数的柯里化完成,我们将之前的logger和date革新了下

const logger = store => next => action => {
    console.log('dispatching:', action)
    next(action)
    console.log('next  state:', store.getState())
}

const date = store => next => action => {
    const date1 = Date.now()
    console.log('date1:', date1)
    next(action)
    const date2 = Date.now()
    console.log('date2:', date2)
}

redux的中间件都是上面这类写法,next为上一个中间件返回的函数,并返回一个新的函数作为下一个中间件next的输入值

为此我们的applyMiddlewareByMonkeypatching也须要被革新下,我们将其命名为applyMiddleware

const applyMiddleware = (store, middlewares) => {
    middlewares.reverse()
    let dispatch = store.dispatch
    middlewares.map(middleware => {
        dispatch = middleware(store)(dispatch)
    })
    return { ...store, dispatch }
}

我们可以如许运用它

let store = createStore(reducer)

store = applyMiddleware(store, [logger, date])

这个applyMiddleware就是我们本身着手完成的,固然它跟redux供应的applyMiddleware照样有肯定的区分,我们来剖析下原生的applyMiddleware的源码就可以晓得他们之间的差别了

applyMiddleware源码

直接上applyMiddleware的源码

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

原生的applyMiddleware是放在createStore的第二个参数,我们也贴下createStore的相干中心代码,然后连系两者一同剖析

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
  ....
}

当传入了applyMiddleware,此时末了实行enhancer(createStore)(reducer, preloadedState)并返回一个store对象,enhancer就是我们传入的applyMiddleware,我们先实行它并返回一个函数,该函数带有一个createStore参数,接着我们继承实行enhancer(createStore)又返回一个函数,末了我们实行enhancer(createStore)(reducer, preloadedState),我们来剖析这个函数体内做了些什么事?

const store = createStore(...args)

起首应用reducer和preloadedState来建立一个store对象

let dispatch = () => {
  throw new Error(
    `Dispatching while constructing your middleware is not allowed. ` +
      `Other middleware would not be applied to this dispatch.`
  )
}

这句代码的意义就是在构建中间件的历程不可以挪用dispath函数,否则会抛出异常

const middlewareAPI = {
  getState: store.getState,
  dispatch: (...args) => dispatch(...args)
}

定义middlewareAPI对象包括两个属性getState和dispatch,该对象用来作为中间件的输入参数store

const chain = middlewares.map(middleware => middleware(middlewareAPI))

chain是一个数组,数组的每一项是一个函数,该函数的入参是next,返回别的一个函数。数组的每一项多是如许

const a = next => {
    return action => {
        console.log('dispatching:', action)
        next(action)
    }
}

末了几行代码

dispatch = compose(...chain)(store.dispatch)
return {
  ...store,
  dispatch
}

个中compose的完成代码以下

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose是一个合并要领,当不传入funcs,将返回一个arg => arg函数,当funcs长度为1,将返回funcs[0],当funcs长度大于1,将作一个合并操纵,我们举个例子

const func1 = (a) => {
  return a + 3
}

const func2 = (a) => {
  return a + 2
}

const func3 = (a) => {
  return a + 1
}

const chain = [func1, func2, func3]

const func4 = compose(...chain)

func4是如许的一个函数

func4 = (args) => func1(func2(func3(args)))

所以上述的dispatch = compose(…chain)(store.dispatch)就是这么一个函数

const chain = [logger, date]
dispatch = compose(...chain)(store.dispatch)
// 等价于
dispatch = action => logger(date(store.dispatch))

末了在把store对象通报出去,用我们的dispatch掩盖store中的dispatch

return {
    ...store,
    dispatch
}

到此全部applyMiddleware的源码剖析完成,发明也没有设想中的那末神奇,永久要坚持一颗求知欲

和手写的applyMiddleware的区分

差点忘记了这个,讲完了applyMiddleware的源码,在来讲说和我上述本身手写的applyMiddleware的区分,区分有三:

  • 原生的只供应了getState和dispatch,而我手写的供应了store中所有的属性和要领
  • 原生的middleware只能运用一次,由于它是作用在createStore上;而我本身手写的是作用在store上,它可以被屡次挪用
  • 原生的可以在middleware中挪用store.dispatch要领不发生任何副作用,而我们手写的会掩盖store.dispatch要领,原生的这类完成体式格局关于异步的middle异常有效

末了

检察demo

检察源码,迎接star

你们的打赏是我写作的动力

<img alt=’微信’ src=’https://user-gold-cdn.xitu.io…;h=1280&f=webp&s=39482′ width=’200′ />

<img alt=’支付宝’ src=’https://user-gold-cdn.xitu.io…;h=1350&f=webp&s=45674′ width=’200’/>

    原文作者:刘源泉
    原文地址: https://segmentfault.com/a/1190000015907726
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞