从函数式编程到redux、koa源码解析

540 阅读4分钟

本次分享是一个烧脑的过程,希望大家有所收获

分享大纲

  • redux 基础的api getState dispatch subscribe 编写
  • 函数式编程 -- curry (柯里化) 和 compose (组合) 介绍
  • redux 中间件处理 (运用到函数式编程)
  • redux-thunk、 redux-logger 编写
  • 再次引申一下 koa 洋葱圈 的 koa-compose实现

redux基本介绍

  1. 为什么需要redux

​ 其实主要是方便组件之间共用一个数据,只要这个共用的数据发生变化,这个数据所在的组件的视图也发生变化。 如果没有redux的话,组件之间的数据共用就会很麻烦。

  1. 分析一下如何能让一个数据做到响应式,即 数据变化之后,更这个数据关联的视图都发生变化

    • 如何让不同组件间共用一个数据了?

      需要定义一个变量 currentState, 当dispatch的时候操作这个变量就好了

    • 如何去做到dispatch之后,页面会变了?

      在组件里面订阅一个函数 主要是更新这个组件

      componentDidMount() {
          store.subscribe(() => {
            this.forceUpdate();
          });
       }
      
    • 当dispatch的时候 再去执行一下这个更新就好了,代码如下:

      1.可能很多组件都订阅了这个数据的变化,先定义一个数组
      let currentListeners = [];
      
      2.subscribe的时候需要把 更新函数放到这个数组里
      function subscribe(listener) {
          currentListeners.push(listener);
      }
      
      3.dispatch的时候去执行一下更新
      function dispatch(action) {
          currentState = reducer(currentState, action);
          // 执行订阅
          currentListeners.forEach(listener => listener());
      }
      

函数式编程

​ 倡导利用若干简单的执行单元让 计算结果不断渐进、逐层推导复杂的运算

​ 函数式编程有两个比较基本的运算 柯里化curry 组合 compose

柯里化 --- 就是把一个多参数的函数转成 单参数的函数

function test(x,y){
	return x + y
}
//柯里化一下:
const curry = fn => x => y => fn(x,y)

let curryTest = curry(test)
curryTest(1)(2)

​ 分析一下如果不知道 函数的参数,怎么写curry函数了?

function test(a,b,c,d,e){
	return a+b+c+d+e
}
const curry = function(fn){
	return function curriedFn(...args){
		if(args.length < fn.length){
			return function(){
				return curriedFn(...args.concat([...arguments]))
			}
		}
		return fn(...args)
	}
}
let curryTest = curry(test)
curryTest(1)(2)(3)(4)(5)

组合compose --- 就是把多个函数通过 compose 合并成一个函数

​ 我们先看一个例子,然后再分析一下compose的作用,给大家准备了这样一个例子

function f1(arg) {
  return `f1-${arg}`;
}
function f2(arg) {
  return `f2-${arg}`;
}
function f3(arg) {
  return `f3-${arg}`;
}

let res = f1(f2(f3("compose"))); 
console.log(res) // f1-f2-f3-compose 

通过对f1,f2,f3 compose 之后

// 定义一个 compose 把要处理的函数当做参数 来处理一下

function compose(...funcs) {
	if(funcs.length === 0){
		return args => args
	}
	if(funcs.length === 1){
		return funcs[0];
	}
	return funcs.reduce((a,b) => (...args) => a(b(...args)))
}

let res2 = compose(f1,f2,f3)('compose')
console.log(res2)

说一下compose的用处 我们用redux 中间件的时候 是不知道 中间件的个数和内容的 中间件的作用相当于去增强 dispatch 的功能 原始的dispatch 只能 接受一个 对象,通过redux-thunk 可以处理一个函数了 等

applyMiddleware redux 中间件处理

开始写一下createStore里面的第二个函数 applyMiddleware

//store 里加载中间件
const store = createStore(
  counter,
  applyMiddleware(thunk, logger)
);
// 柯里化 curry
createStore(reducer, enhancer) {
  if (enhancer) {
    // 要增强 首先把 createStore reducer 传过去
    return enhancer(createStore)(reducer);  
  }
  ...
}
applyMiddleware.js
// 柯里化的感觉 
export default function applyMiddleware(...middlewares) {
  return createStore => reducer => {
      let store = createStore(reducer)
      let { dispatch } = store;
      let midApi = {
        getState: store.getState,
        dispatch: (action,...args) => dispatch(action,...args)
      }
      let middlewareChain = middlewares.map(middleware => middleware(midApi))
      let dispatch = compose(...middlewareChain)(store.dispatch)
      return {
        ...store,
        dispatch
      }
  }
}

redux-thunk、 redux-logger 编写

logger中间件
export function logger({getState}) {
    return next => action => {
        console.log('---logger start--')
        console.log(action.type + "执行了!")
        let prevState = getState();
        console.log("prev state", prevState);

        const returnValue = next(action);
        let nextState = getState();
        console.log("next state", nextState);
        console.log('---logger end--');
        return returnValue;
    }
}

thunk中间件:

export function thunk({dispatch,getState}){
    return next => action => {
        if(typeof action === 'function'){
            return action(dispatch,getState)
        }
        return next(action)
    }
}

源码的写法:

compose

applyMiddleware

redux-thunk

koa-compose源码分析

​ 最后继续分析一下Koa的洋葱圈模型,如果通过compose 把 app.use 里面的函数给串起来。

​ 看一下koa 的洋葱圈模型的使用和koa的处理

app.use 的时候
use(fn) 
    this.middleware.push(fn);
}

app.listen的时候 调用this.callback
listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
}
callback(){
	const fn = compose(this.middleware);
}
最后的调用是通过then来处理
fnMiddleware(ctx).then(handleResponse).catch(onerror);

compose 源码

//主要是通过递归 通过Promsie.resolve依次把next包裹进去
function compose (middleware) {
  return function (ctx) {
    return dispatch(0)
    function dispatch(i) {
      let fn = middlewares[i]
      if (!fn) {
        return Promise.resolve()
      }
      return Promise.resolve(
        fn(ctx, function next() {
          return dispatch(i + 1)
        })
      )
    }
  }
}

最后如果展开之后会是这样:

const [fn1, fn2, fn3] = this.middleware;
const fnMiddleware = function(context){
    return Promise.resolve(
      fn1(context, function next(){
        return Promise.resolve(
          fn2(context, function next(){
              return Promise.resolve(
                  fn3(context, function next(){
                    return Promise.resolve();
                  })
              )
          })
        )
    })
  );
};
fnMiddleware(ctx).then(handleResponse).catch(onerror);

koa-comopse 返回一个Promise

Promise取出第一个函数执行

第一个next也是返回一个Promise await next() 之后 还是 一个Promsie 依次往下 最后把所有的中间件都串起来了 ,这就是洋葱圈模型。

不得不说 玩还是大神会玩

相比较 redux的中间件 koa 中间件是异步的 所有不能简单的 进行函数包裹,需要 Promise.resolve来包裹

源码是这样

最后 总结一下 函数式编程 已经运用在 redux koa 等各种库中,希望大家能 有所收获