New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compose and middleware 源码分析 #1

Open
aototo opened this Issue Mar 7, 2017 · 0 comments

Comments

Projects
None yet
1 participant
@aototo
Owner

aototo commented Mar 7, 2017

Redux compose and middleware 源码分析

花了一天时间看了redux 几个function的源码感触颇深,接下来总结下源码的作用。

compose.js

https://github.com/reactjs/redux/blob/master/src/compose.js#L21

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

一头雾水啊,函数编程功力不够,第一眼懵逼,细细琢磨才领悟其中的奥妙。
先不谈奥妙之处我们先写段代码,慢慢解析。

下面代码我们需要组合2个函数,构建成一个新的函数

var f1 = function(a) {
	return a * 10;
}

var f2 = function(a) {
	return a + 1;
}

function compose(f1, f2) {
	return function(x) {
		return f1(f2(x))
	}
}

var number = compose(f1, f2)(4) 
number == 50 // true

让代码从右向左运行变成f1(f2(x)),4 -> f2 -> f1 -> 50,当然了你也可以在内部reverse一下让函数从第一个开始执行。
接下来我们看下实际运用的compose

let compose = (...funs) => (result) => {
	for (var i = funs.length - 1; i > -1; i--) {
      result = funs[i].call(this, result);
    }
    
	return result;
}

var number = compose(Math.round, parseFloat)('5.8');
// number => 6

parseFloat函数return的结果作为Math.round函数的参数
Math.round(parseFloat('5.8'))`

我们在回到redux compose.js

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

跟上面的compose 的原理其实是一样一样的,就是写法很精妙。
先分析reduce 这个method

[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

reduce()把返回的结果继续和序列的下一个元素做累积计算,其效果就是上面这样。

拆分上面的源码步骤如下:
第一次 返回 (...args) => a(b(...args) 且与下一个c函数做计算
第二次 a 等于 (...args) => a(b(...args) ,b 等于 c 这个function , 然后再一次做 (...args) => a(b(...args)) 计算,就形成了

	 (c(...args)) => a(b(...args))
	 //返回 a(b(c(...args)))

第三次也是如此

	(d(...args)) => a(b(c(...args)))
	//返回 a(b(c(d(...args))))

结果就如注释的一样

	// from right to left. For example, compose(f, g, h) is identical to doing
	 // (...args) => f(g(h(...args))).

到这里我们这点了compose 的实现原理,接下来我们看下applyMiddleware.js
中如何实现中间件的。
源码

	let dispatch = store.dispatch
    let chain = []

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

然后我们看下middleware函数的签名
({ getState, dispatch }) => next => action => {...}

  1. 第一步把middlewares数组里的函数带着middlewareAPI这个参数都挨个执行个遍且,且保存在chain数组中。也就是middleware中的第一个参数,返回 next => action => {...}

  2. 第二步 就是上面composeJs 中组合chain中的函数。变成f1(f2(f3(store.dispatch))),并且最终保存在dispatch 变量中。里面的函数最终会返回action => {...} 这个函数 作为上一个函数的next参数保存在上个函数的scope中,所以每个middleware 中必须执行next(action)才能进入下个middleware。而最后一个middleware传入的next正是原始的store.dispatch。

     //演示代码
     const f1 = ({ getState, dispatch }) => next => action => {
     	...
     }
     
     compose(f1)(store.dispatch)  
     dispatch = (action) => {  /* next = store.dispatch */ }
     
     compose(f1, f2)(store.dispatch) 
     // f1 = next => action => { ... }
     // (action) => {  /* next = store.dispatch */ }  作为f2的返回值赋值给next 参数
     //其中的next就是f2的函数体
    

基本的源码分析到此就差不多了,要注意的是为什么异步middleware中可以使用dispatch让action在重新执行一遍。因为其中的dispatch就是源码中的

dispatch = compose(...chain)(store.dispatch)

这样异步middleware就非常容易理解,也很好巧妙的实现异步流。而next则是进入下一个middleware。如果在middleware里面滥用store.dispatch,就会造成无限的循环。

	const f2 = store => next => action => {
		next(action) //进入下一个middleware
		store.dispatch(action) //从左边的第一个middleware开始
	}

redux-thunk的实现就是判断action是否是function,如果是就执行action(),action就可以是一个异步请求的函数,具体可以看https://github.com/gaearon/redux-thunk/blob/master/src/index.js#L3的源码!

就写到这里,函数编程的巧妙还需多多学习呀,不足之处还请指正。

参考
Middleware

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment