Redux源码浅析

Redux解决的问题

JavaScript 须要管理比任什么时候候都要多的 state (状态)编程

state 在何时,因为什么缘由,如何变化已然不受控制。redux

经过限制更新发生的时间和方式,Redux 试图让 state 的变化变得可预测。设计模式

Redux设计分析

三个原则

  • 单一数据源
  • state是只读的 不可写(想要修改就必须按照redux的单向数据流逻辑来操做),是实现单向数据流的根本保障
  • 使用纯函数来执行修改 纯函数意味着依赖单一,咱们只须要派发一个用于描述state变化的action便可。 这让时间旅行、记录和热更新成为可能 尽量的简化单向数据流,不须要魔法

流程图

action => middleware => reducer(s) => Store闭包

功能设计

  • createStore(rootReducer, initStore, middleware). 建立store
  • applyMiddleware(...middlewares). 使用中间件 链式使用
  • compose(...fn). 嵌套函数
  • combineReducers(...reducer). 组合reducer
  • bindActionCreator(actionCreators, dispatch). 封装多个action

这是redux提供的几个关键文件和它们的做用,其实简单他们提供的功能不难发现他们函数式编程的影子。redux里面设计比较巧妙的点我的感受是在中间件里。middleware在redux中被设计为在action发起后,到达reducer以前的拓展点。咱们能够利用middleware实现相似日志记录,错误定位或者路由,还有异步处理action这些操做。app

关键点分析

redux的源码是比较典型的FP风格,掌握一些基本FP概念,再去阅读redux源码会轻松不少异步

  • 高阶函数

Higher order functions can take functions as parameters and return functions as return values.函数式编程

接受函数做为参数传入,并能返回封装后函数。函数

  • 科里化

Currying > Currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument学习

是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。 add(1,2,3) => add(1)(2)(3)this

  • Compose > Composes functions from right to left. 组合函数,将从右向左执行。

    compose(subtract,multiple,add)(200) 等同于 subtract(multiple(add(200)));

内部使用reduce,而不是直接嵌套。

单向数据流

store.dispatch(action) => middleware =>reducer => store

贴上实现单向数据流的关键源码(部分删减)

dispatch函数的实现 (/createStore.js)

每次咱们调用原生dispatch时,都会有这样的流程,在上图第五行里dispatch函数将拿到的action交给reducer函数处理,这里的 isDispatching变量用来控制在reducer函数执行过程当中不容许再次dispatch,这个过程用try/finally提供可靠性;第十行取得当前监听器函数列表的快照,在for循环中依次执行,这里执行也是有讲究,并无直接 listener[i]()调用,而是采用了分割this的行为逐个调用监听器函数。总结这个dispatch函数关键点以下

  • 状态位控制流程,在reducer过程当中不容许dispatch
  • 用快照的形式存储监听器列表,避免在监听器函数中调用subscribe函数引起的不可预期行为
  • 隔离监听器this,营造私有变量。

combineReducer函数的实现(/combineReducers.js)

  • reducer name 决定了state节点的key 调用由store提供的dispatch函数,便可触发reducer,将返回的state更新,并触发state监听器列表中的方法。

中间件到底作了什么

中间件发挥做用的时间点在派发action后,达到reducer前,能够理解为在调用原生dispatch(action)前,使用了中间件。 与其按照时间节点来理解,倒不如说中间件是为了加强dispatch函数而作的设计 applyMiddleware的源码很是精炼

带着问题来阅读源码,中间件是如何实现如下几点功能的

  • 如何让中间件均可以获取到state
  • 如何让中间件可链式使用
  • 中间件的函数签名为何是middleware = store => next => action => { next(action) }
如何让中间件均可以获取到state

这里声明了一个middlewareAPI,经过里面的getState方法就能够拿到store里的数据,另一个dispatch并无什么实际的做用,就算调用了,它也会告诉你不能使用,这里利用map将middlewareAPI传入到每一个中间件里,构造了一个闭包,让中间件能够访问到state数据,这里也利用了currying函数延迟执行的特性,它接受了参数执行可是返回的是另一个待执行的函数。 如此就保证了每一个中间件能够获取到state,关键点在于中间件科里化的设计,让其能够延迟执行和参数复用。

const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } 
// 利用currying函数延迟执行的特性 
复制代码
如何让中间件可链式使用

如何将中间件串联起来,并保存最后一个函数传入的参数为store.dispatch 想实现这个特性就要用到compose组合函数, 将中间件串联起来,而且最后一个函数入参为store.dispatch, 传入的next就是下一个中间件,固然最后一个函数接受的next就是原生的store.dispatch,那个时候中间件就处理完毕,将action派发到reducer了。

const a = next => action => next(action) 
const b = next => action => next(action) 
const c = dispatch => action => dispatch(action) 
compose(c, b, a)(store.dispatch) 
// 源码实现 dispatch = compose(...chain)(store.dispatch) 
复制代码
中间件的函数签名

函数签名middleware = store => next => action => { next(action) }

其实看到这里应该也大体明白了为何要这么设计中间件,返回的第一个函数是为了保证中间件能够取到全局状态,返回的第二个函数是为了保证中间件能够依次调用。redux里的中间件是一个科里化的函数,其主要目的是为了利用其延迟计算和参数复用的能力,来实现中间件的众多特性。

一些遗憾

redux虽然为咱们解决了state的管理问题,但依然不是百分之百的完美。逻辑上redux提供了一套简单可行且很是清晰规范的state管理方案,数据的单数据流和其三个原则,与之带来的是会写一些模板代码,若是使用了中间件,特别是redux-saga那种独立规范特别多的中间件,会耗费咱们不少的时间在写模板上,虽然咱们能够对数据流动掌控的特别精细,可是时间成本依然减缓了咱们开发的效率。

改进方案

redux的改进应该在保留优点设计,解决痛点的基础上进行。其实在多数开发者使用redux时通常会对其作简单的封装再使用,对redux增长一些设计模式或是使用企业内部的diapatch加强方法,这里抛砖引玉,提出几个redux理想改进的几个须要注意的地方

  • 尽量保留redux的核心概念,下降学习成本
  • 减小redux模板代码,能够从提升复用性和提供内置模板的角度来减小开发者的重复工做
  • 能无缝接入redux的生态,支持中间件,enhancer
  • 保留redux的特性,保留其三个原则
  • 如何简单抽象action和reducer之间的关系是一个很是重要的思考点
  • 提供对复杂场景的功能支持,好比动态增长reducer,提供多个store实例
相关文章
相关标签/搜索