从源代码的入口文件发现,其实 redux 最终就只是导出了一个对象,对象中有几个方法,代码如下:

export {createStore,combineReducers,bindActionCreators,applyMiddleware,compose,__DO_NOT_USE__ActionTypes
}
复制代码

所以重点分析几个方法:

createStore 方法

方法中定义的一些变量:

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
复制代码

这些变量会被 dispatch 或者别的方法引用,从而形成闭包。这些变量不会被释放。

创建 srore 的方法最终返回的是一个对象。对象中含有比较重要的方法dispatch,subscribe,getState

return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable
}
复制代码

其中 createStore 的第三个参数是应用中间件来做一些增强操作的。

if (typeof enhancer !== 'undefined') { // 如果增强方法存在就对 createStore 进行增强if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)
}
复制代码

subscribe 方法

其中 subscribe 用来注册监听方法,每一次注册后会将监听方法维护到数组currentListeners中,currentListenerscreateStore 中的一个变量,由于被 subscribe 引用着所以形成了一个闭包。也就是通过闭包来维护状态。

let currentListeners = []
复制代码

dispatch 方法

dispatch 方法用来分发 action, 函数里面会生成新的 currentState, 会执行所有注册了的函数。

核心代码:

try {isDispatching = truecurrentState = currentReducer(currentState, action) // 生成新的 state
} finally {isDispatching = false
}const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()
} // 遍历执行注册函数
复制代码

getState

仅仅用来获得当前的 state:

function getState() {return currentState
}
复制代码

combineReducers 函数

函数中定义的一些变量,

const finalReducers = {}
const finalReducerKeys = Object.keys(finalReducers)
复制代码

这个函数最后返回的是一个函数 combination, 返回的函数中引用了 finalReducersfinalReducerKeys,形成了闭包。

出于业务场景考虑,不同的模块采用不同的 reducer 进行处理,所以 reducer 函数有很多。这些 reducer 会遍历执行。

每一次 dispatch 一个 action 的时候就会执行

currentState = currentReducer(currentState, action) // 生成新的 state
复制代码

这里的 currentReducer 就是返回的 combination 函数。combination 函数中的核心代码:

function combination(state = {}, action) {...let hasChanged = false// 每一次 reducer 执行的时候都会生成一个新的对象来作为新的 state const nextState = {}// 通过 for 循环遍历 reducer for (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i]const reducer = finalReducers[key]// 获取当前的 stateconst previousStateForKey = state[key]// 执行相应的 reducer 后会生成新的 stateconst nextStateForKey = reducer(previousStateForKey, action)if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}// 给新的 state 赋值nextState[key] = nextStateForKey// 如果是一个简单类型比如 string,number // 如果前后值一样就不会触发改变// 但如果 state 中某个值是一个对象,// 尽管前后对象中的值一样,但是引用地址变化,还是会触发改变hasChanged = hasChanged || nextStateForKey !== previousStateForKey}// 所以如果简单值没有变化并且没有对象的引用地址改变就会返回原来的 statereturn hasChanged ? nextState : state
}
复制代码

结合 react-redux 中向 redux 订阅的方法发现

subscribe() {const { store } = this.props  // 这里的 store 是 createStore 方法执行后返回的对象this.unsubscribe = store.subscribe(() => { // 通过订阅方法注册监听事件const newStoreState = store.getState() // 获取新的 stateif (!this._isMounted) {return}// 通过使用函数替代对象传入 setState 的方式能够得到组件的 state 和 props 属性可靠的值。this.setState(providerState => {// 如果值是一样的就不会触发更新if (providerState.storeState === newStoreState) {return null}return { storeState: newStoreState }})})// Actions might have been dispatched between render and mount - handle thoseconst postMountStoreState = store.getState()if (postMountStoreState !== this.state.storeState) {this.setState({ storeState: postMountStoreState })}
}
复制代码

在注册的 listen 方法中会发现如果最 新的state和原来的state一样 就不会触发 setState 方法的执行,从而就不会触发 render

applyMiddleware 使用中间件

源码:

export default function applyMiddleware(...middlewares) {return createStore => (...args) => { // 接收 createStore 函数作为参数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)} // 中间件函数接收的 API 参数,能够获取到当前的 state 和 createStore 函数的参数// 所以这里就向中间件函数中传递了参数const chain = middlewares.map(middleware => middleware(middlewareAPI))// 通过函数组合生成一个新的 dispatch 函数dispatch = compose(...chain)(store.dispatch)return {...store,dispatch} // 这里返回的是最后生成的 store,相比不使用中间件的区别是对 dispatch 进行了增强。}
}
复制代码

结合 createStore 中的源码:

return enhancer(createStore)(reducer, preloadedState)
复制代码

所以上面 applyMiddleware 中返回的函数就是这里的 enhancer 方法,接收 createStore 作为参数。

(reducer, preloadedState) 对应着中间件中的 (...args)

react-redux

react-redux 通过提供 Provider 组件将 store 和整个应用中的组件联系起来。确保整个组件都可以获得 store, 这是通过 Context 来实现的。

Provider 组件最终渲染的组件:

render() {const Context = this.props.context || ReactReduxContextreturn (<Context.Provider value={this.state}>{this.props.children}</Context.Provider>)
}
复制代码

其中 state 的定义如下:

const { store } = props
this.state = {storeState: store.getState(),store
}
复制代码

所以 Provider 给应用提供 store 的写法如下,属性名必须是 store

<Provider store={store}><Router />
</Provider>
复制代码

redux-thunk

redux-thunk 是一个中间件,直接看中间件的源代码是绝对不可能看明白的

中间件不是一个完整的个体。它是为了丰富或者扩展某个模块而出现的,其中会调用一些原来的模块的方法,所以如果不看源模块的对应的方法实现,根本无法理解。

所以要想看懂一个中间件,必须结合源模块的代码一起看。

Thunk 函数的含义和用法

JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数

如何让 dispatch 分发一个函数,也就是 action creator??

dispatch 的参数只能是一个普通的对象,如果要让参数是一个函数,需要使用中间件 redux-thunk

设计思想就是一种面向切面编程AOP,对函数行为的增强,也是装饰模式的使用

redux-thunk 源码:

function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);};
}const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;export default thunk;
复制代码

如果只是用了 thunk,那么最终增强版的 dispatch 就是

action => {// 当 dispatch 参数是一个函数的时候执行这里if (typeof action === 'function') { // 这里的 dispatch 就是最原始的 dispatch// 所以 action 函数中可以直接使用参数 dispatch 和 getState 函数return action(dispatch, getState, extraArgument);}return next(action); // 这里的 next 是 store.dispatch
}
复制代码

异步操作带代码

异步操作如果使用 action creator, 则至少要送出两个 Action:

  • 用户触发第一个 Action,这个跟同步操作一样,没有问题;
  • action creator 函数中送出第二个 Action

代码实例:

handleClick = () => {const { dispatch } = this.propsdispatch(this.action); // 发出第一个 action(函数)
}action = (dispatch, getState) => setTimeout(() => {dispatch({ type: 'REQUESTSTART' })
}, 1000) // 发出第二个 action(普通对象)
复制代码

思考

异步代码的处理一定要使用 redux-thunk吗?

非也。在触发含有异步代码的函数执行时,把 dispatch 函数作为一个参数传给函数,然后这个异步函数里面在合适的时机调用 dispatch 发出 action 就行。

上面的异步代码可改写如下:

handleClick = () => {const { dispatch } = this.propsthis.action(dispatch);
}action = dispatch => setTimeout(() => {dispatch({ type: 'REQUESTSTART' })
}, 1000)
复制代码

不过相比 redux-thunk 有个缺陷就是不能获取 getState 这个方法。

使用示例

使用 redux 演示代码

转载于:https://juejin.im/post/5c26e210e51d45342a255ad2

redux 和 react-redux 部分源码阅读相关推荐

  1. React 表单源码阅读笔记

    1 概念 1.1 什么是表单 实际上广义上的表单并不是特别好界定,维基上讲表单是一系列带有空格的文档,用于输写或选择.更具体的,在网页中表单主要负责数据采集的功能,我们下文中所提到的表单都指后者.如下 ...

  2. 应用监控CAT之cat-client源码阅读(一)

    CAT 由大众点评开发的,基于 Java 的实时应用监控平台,包括实时应用监控,业务监控.对于及时发现线上问题非常有用.(不知道大家有没有在用) 应用自然是最初级的,用完之后,还想了解下其背后的原理, ...

  3. centos下将vim配置为强大的源码阅读器

    每日杂事缠身,让自己在不断得烦扰之后终于有了自己的清静时光来熟悉一下我的工具,每次熟悉源码都需要先在windows端改好,拖到linux端,再编译.出现问题,还得重新回到windows端,这个过程太耗 ...

  4. 源码阅读:AFNetworking(十六)——UIWebView+AFNetworking

    该文章阅读的AFNetworking的版本为3.2.0. 这个分类提供了对请求周期进行控制的方法,包括进度监控.成功和失败的回调. 1.接口文件 1.1.属性 /**网络会话管理者对象*/ @prop ...

  5. 源码阅读:SDWebImage(六)——SDWebImageCoderHelper

    该文章阅读的SDWebImage的版本为4.3.3. 这个类提供了四个方法,这四个方法可分为两类,一类是动图处理,一类是图像方向处理. 1.私有函数 先来看一下这个类里的两个函数 /**这个函数是计算 ...

  6. mybatis源码阅读

    说下mybatis执行一个sql语句的流程 执行语句,事务等SqlSession都交给了excutor,excutor又委托给statementHandler SimpleExecutor:每执行一次 ...

  7. 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment

    24 UsageEnvironment使用环境抽象基类--Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类--Live555源码阅读 ...

  8. Transformers包tokenizer.encode()方法源码阅读笔记

    Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode

  9. 源码阅读笔记 BiLSTM+CRF做NER任务 流程图

    源码阅读笔记 BiLSTM+CRF做NER任务(二) 源码地址:https://github.com/ZhixiuYe/NER-pytorch 本篇正式进入源码的阅读,按照流程顺序,一一解剖. 一.流 ...

  10. 源码阅读:AFNetworking(八)——AFAutoPurgingImageCache

    该文章阅读的AFNetworking的版本为3.2.0. AFAutoPurgingImageCache该类是用来管理内存中图片的缓存. 1.接口文件 1.1.AFImageCache协议 这个协议定 ...

最新文章

  1. 资源 | 吴恩达《机器学习训练秘籍》中文版58章节完整开源
  2. batch_normalization (bn)层以及实际使用中合并bn层
  3. 康普在金色一号中心缔造光纤新历史
  4. 全面支持开源,微软加速 Visual Studio 和 Azure DevOps 云升级
  5. 10个强大实用数据地图,不懂代码也能做!(附demo)
  6. MyBatis学习存档(4)——进行CRUD操作
  7. 博客索引-pyhui-第二版
  8. Python3爬虫入门之pyquery库的使用
  9. 第十五讲 循环体for基础
  10. iOS开发之Xcode项目文件自动展开问题的解决办法
  11. 一个比较有含金量的架构认证
  12. matlab遗传工具箱ga,用遗传算法工具箱(GA)识别Bouc-Wen模型微分方程参数
  13. 随机数和随机数种子——学不会找我
  14. Dash中文文档: Python2.7.16 和 Python3.8
  15. yabailv 运放_运放块压摆率单位增益频宽和逻辑器件传输延时
  16. 开关电源原理、电路组成部分
  17. 苹果appstore新推出了抽成收益降到15%的计划,正在申请中...
  18. 《深入理解计算机系统》第一章
  19. python2.7下的urllib和urllib2
  20. 简体遇繁体 饭菜先生餐饮软件悄入台湾

热门文章

  1. Kafka(1)-概述
  2. css3属性box-sizing:border-box 用法解析
  3. Spring整合Quartz定时发送邮件
  4. 众多Android 开源项目推荐,给力工作给力学习
  5. 全面掌握ISO8583报文协议
  6. 几个极品笑话,放松下心情
  7. hdu4685 最大匹配可能性
  8. 【Android 逆向】ART 脱壳 ( DexClassLoader 脱壳 | oat_file_assistant.cc 中涉及的 oat 文件生成流程 )
  9. 【Android 逆向】类加载器 ClassLoader ( Android 的八种类加载器 | ClassLoader | BaseDexClassLoader | DexClassLoader )
  10. 【C 语言】数组 ( 指针退化验证 | 计算数组大小 | #define LENGTH(array) (sizeof(array) / sizeof(*array)) )