本次分享是一个烧脑的过程,希望大家有所收获
分享大纲
- redux 基础的api getState dispatch subscribe 编写
- 函数式编程 -- curry (柯里化) 和 compose (组合) 介绍
- redux 中间件处理 (运用到函数式编程)
- redux-thunk、 redux-logger 编写
- 再次引申一下 koa 洋葱圈 的 koa-compose实现
redux基本介绍
- 为什么需要redux
其实主要是方便组件之间共用一个数据,只要这个共用的数据发生变化,这个数据所在的组件的视图也发生变化。 如果没有redux的话,组件之间的数据共用就会很麻烦。
-
分析一下如何能让一个数据做到响应式,即 数据变化之后,更这个数据关联的视图都发生变化
-
如何让不同组件间共用一个数据了?
需要定义一个变量 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 等各种库中,希望大家能 有所收获