带着问题看redux源码

释放双眼,带上耳机,听听看~!
00:00
00:00

前言

作为前端状态管理器,这个比较跨时代的工具库redux有很多实现和思想值得我们思考。在深入源码之前,我们可以相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解。

常见问题

大概看了下主要有这么几个:

  1. redux三大原则
    这个可以直接参考官方文档
  2. redux 的优缺点。 关于优缺点,太主观了大家见仁见智。
  3. redux中间件相关,洋葱模型是什么,常见中间件。

背景

有关acton,reducer相关的部分可以看我前面的文章。我们主要关注针对store和中间件相关的部分来解读。

store的创建

作为维护和管理数据的部分,store在redux中的作用十分重要。在action发出指令,reduxer进行数据更新之后,监听数据变化和同步数据更新的操作都要借助store来实现。

createStore 输入和输出

首先看下createStore的使用,即常见的就是接受经过combineReducers处理之后的reducer和初始的state

  1. import reducer from \'./reducers\'
  2. const store = createStore(reducer,initialState)

此外还可以接受第三个参数enhancer(增强器,一般就是applyMiddleware)

  1. /**
  2. * 创建管理state 树的Redux store
  3. * 应用中只能存在一个store,为了区分不同action对应的reducer,
  4. * 可以通过combineReducers来关联不同的reducer
  5. * @param {Function} reducer combineReducers关联之后的reducer
  6. * @param {Object} preloadedState 初始state
  7. * @param {Function} enhancer 可以增强store功能的函数,例如中间件等。唯一适合
  8. * @returns 返回一个Store 以维护state和监听变化
  9. */
  10. export default function createStore(reducer, preloadedState, enhancer) {
  11. // 如果第二个参数为func,redux认为忽略了初始state,而是
  12. if (typeof preloadedState === \'function\' && typeof enhancer === \'undefined\') {
  13. // enhancer增强剂,即利用中间件等来增强redux能力
  14. enhancer = preloadedState
  15. preloadedState = undefined
  16. }
  17. // 返回具有dispatch等属性的对象 即store
  18. return {
  19. dispatch,
  20. subscribe,
  21. getState,
  22. replaceReducer,
  23. [$$observable]: observable
  24. }
  25. }

按照一般的执行顺序,我们先看下对于参数的处理(平时大家也是一样,一个函数,执行之前尽量判断入参是否符合预期,避免直接处理造成的错误)

入参处理

对于三个参数,后两个是非必填的,但如果第二个参数是function,reduxe认为其实encher,不然初始状态是个函数不符合redux的预期,只能这样处理了。

  1. // 如果第二个参数为func,redux认为忽略了初始state,而是
  2. if (typeof preloadedState === \'function\' && typeof enhancer === \'undefined\') {
  3. // enhancer增强剂,即利用中间件等来增强redux能力
  4. enhancer = preloadedState
  5. preloadedState = undefined
  6. }
  7. if (typeof enhancer !== \'undefined\') {
  8. if (typeof enhancer !== \'function\') {
  9. throw new Error(\'Expected the enhancer to be a function.\')
  10. }
  11. // 对于存在的enhancer,高阶函数函数的用法,
  12. // 接受createStore返回一个增加功能之后的函数,然后再传入相关reducer得到store。
  13. return enhancer(createStore)(reducer, preloadedState)
  14. }
  15. if (typeof reducer !== \'function\') {
  16. throw new Error(\'Expected the reducer to be a function.\')
  17. }
  18. // 一切符合预期,没有 enhancer,那么初始赋值
  19. let currentReducer = reducer
  20. let currentState = preloadedState
  21. let currentListeners = []
  22. // 监听队列
  23. let nextListeners = currentListeners
  24. // dispatch标识
  25. let isDispatching = false
  26. // 初始状态更新之后,声明init状态完成。
  27. dispatch({ type: ActionTypes.INIT })

dispatch的实现

dispatch的作用就是根据action,执行对应reducer以更新state。并执行监听队列。
下面就来看dispatch的用法和实现。
常见使用:

  1. // redux要求 参数必须为纯对象
  2. dispatch({ type: ActionTypes.INIT })

那么对于纯对象,redux做了些什么呢

  1. /**
  2. * 通知方法,参数为纯的js对象,标明更新内容
  3. * @param {Object} action
  4. */
  5. function dispatch(action) {
  6. // 是否满足纯净对象
  7. if (!isPlainObject(action)) {
  8. throw new Error(
  9. \'省略\'
  10. )
  11. }
  12. // 必须的type是否存在
  13. if (typeof action.type === \'undefined\') {
  14. throw new Error(
  15. \'省略\'
  16. )
  17. }
  18. // 判断是否处于某个action的dispatch中,大家一起dispatch可能死循环
  19. if (isDispatching) {
  20. throw new Error(\'Reducers may not dispatch actions.\')
  21. }
  22. try {
  23. // 开始dispatch,加锁,标明状态
  24. isDispatching = true
  25. // 将当前状态和更新action,传给当前reducer处理
  26. // 这里可以回想下我们reducer中的两个参数,state和action 对应的是什么
  27. /**
  28. * const todos = (state = [], action) => {
  29. */
  30. currentState = currentReducer(currentState, action)
  31. } finally {
  32. // 有异常,锁置为false,不影响别的dispatch
  33. isDispatching = false
  34. }
  35. // 执行dispatch,并且更新当前监听队列为 最新队列
  36. const listeners = (currentListeners = nextListeners)
  37. // 依次执行,监听器
  38. for (let i = 0; i < listeners.length; i++) {
  39. const listener = listeners[i]
  40. listener()
  41. }
  42. return action
  43. }

createStore初始化完成之后会执行dispatch({ type: ActionTypes.INIT }),此时执行初始化操作。

我们要关注下currentState的计算,
将currentState,action传给reducer处理,然后更新currentState。

针对初始化来说currentState其实就是initState:

  1. // 初始化状态
  2. let currentState = preloadedState
  3. /****省略***/
  4. // 这里可以回想下我们reducer中的两个参数,state和action对应的值
  5. currentState = currentReducer(currentState, action)

reducer示例:

  1. const todos = (state = [], action) => {
  2. switch (action.type) {
  3. case \'ADD_TODO\':
  4. return [
  5. ...state,
  6. {
  7. id: action.id,
  8. text: action.text,
  9. completed: false
  10. }
  11. ]
  12. }

getSate实现

getState就是获得store的state。这个比较简单。当结合react-redux使用时,其会帮我们进行操作。我们就不用自行调用这个方法了,所以不要疑惑从哪里获取的state。

  1. /**
  2. * 返回应用当前状态
  3. * 不过需要看下当前是否有更新正在进行,是的话则提示
  4. */
  5. function getState() {
  6. // 判断是否isDispatching 中
  7. if (isDispatching) {
  8. throw new Error(\'省略\')
  9. }
  10. return currentState
  11. }

subscribe

subscribe是比较重要的一个方法,用来供我们监听状态的变化,以执行相关操作。
例如react-redux中的handleChange 会对是否pure组件及state进行对比,以提升渲染效率。

示例:

  1. this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))

实现:
返回的是一个函数,可以进行unsubscribe操作。

  1. /**
  2. * 订阅通知
  3. */
  4. function subscribe(listener) {
  5. if (typeof listener !== \'function\') {
  6. throw new Error(\'Expected the listener to be a function.\')
  7. }
  8. if (isDispatching) {
  9. throw new Error(
  10. \'省略\'
  11. )
  12. }
  13. // 是否已经监听过
  14. let isSubscribed = true
  15. // 监听队列是否相同,区分开,操作nextListeners
  16. ensureCanMutateNextListeners()
  17. // 新增监听事件
  18. nextListeners.push(listener)
  19. return function unsubscribe() {
  20. if (!isSubscribed) {
  21. return
  22. }
  23. if (isDispatching) {
  24. throw new Error(
  25. \'省略\'
  26. )
  27. }
  28. // 注册完成,可以进行取消操作
  29. isSubscribed = false
  30. // 保持事件队列 同步
  31. ensureCanMutateNextListeners()
  32. const index = nextListeners.indexOf(listener)
  33. // 删除监听事件
  34. nextListeners.splice(index, 1)
  35. }
  36. }

replaceReducer

这个开发比较少用,用于热更新

  1. // 用于reducer的热替换,开发中一般不会直接使用
  2. function replaceReducer(nextReducer) {
  3. if (typeof nextReducer !== \'function\') {
  4. throw new Error(\'Expected the nextReducer to be a function.\')
  5. }
  6. currentReducer = nextReducer
  7. // 更新值之后,进行dispatch。
  8. dispatch({ type: ActionTypes.REPLACE })
  9. }

到这里createStore已经解析完成了,大家应该了解该方法到底做了些什么操作吧。
简单概括一下就是:接收reducer和initState,返回一个store 对象。该对象提供了监听、分发等功能,以实现数据的更新。

实际使用中的问题

经过上面的解读之后,对于redux的常规应用应该有所了解了。不过实际使用中可能会遇到些问题。
例如action要求是纯对象,而我们获取数据一般是异步的,这就需要借助redux-thunk这个中间件了。
actionCreater返回一个函数。如下:

  1. export function func1() {
  2. return dispatch => {
  3. dispatch({
  4. type:\'test\',
  5. data:\'a\'
  6. })
  7. }
  8. }

在了解如何实现之前,需要先看下redux中间件的原理。
因为reducer更多的关注的是数据的操作,对于一些公共的方法,需要抽离出来,不过这些方法在何时使用呢,redux为我们提供了中间件来满足需求。

redux中间件原理

redux 借鉴了 Koa里 middleware 的思想,即鼎鼎大名的洋葱模型。
带着问题看redux源码

不过这里请求对应的是dispatch的过程。

每次dispatch的过程中,都要依次将中间件执行一遍。
遇到阻塞或者操作完成,执行下个中间件,直到执行完成,以便我们事先日志,监控、异常上报等功能。
那么redux 又是如何支持中间件的呢。这就离不开applyMiddleware了。
这里前面的

applyMiddleware实现思路

实现思想比较简单,通过科里化和compose,为符合规范的中间件分配访问dispatch和store的途径,以便在不同阶段来自定义数据更新。
例如异步操作,返回的不是对象,那么就执行返回的函数,然后调用下一个中间件。等异步请求结束,再次dispatch 对应的action。

  1. export default function applyMiddleware(...middlewares) {
  2. return createStore => (...args) => {
  3. const store = createStore(...args)
  4. let dispatch = () => {
  5. throw new Error(
  6. `Dispatching while constructing your middleware is not allowed. ` +
  7. `Other middleware would not be applied to this dispatch.`
  8. )
  9. }
  10. // 赋予每个中间件访问store的能力。
  11. const middlewareAPI = {
  12. getState: store.getState,
  13. // 箭头函数保存dispatch,保证其的同步更新
  14. dispatch: (...args) => dispatch(...args)
  15. }
  16. // 串联中间件,并赋予每个中间件访问dispatch的能力。
  17. const chain = middlewares.map(middleware => middleware(middlewareAPI))
  18. // 关联dispatch与中间件,组合调用之后得到类似下面的新对象
  19. // dispatch = f1(f2(f3(store.dispatch))));
  20. dispatch = compose(...chain)(store.dispatch)
  21. return {
  22. ...store,
  23. dispatch
  24. }
  25. }
  26. }

这样执行之后返回的,对象就是增强之后的store了。

compose的实现

redux中compose是柯里化函数的一个示例,目的是将函数串联起来。

  1. /**
  2. * 函数组合,科里化的串联
  3. */
  4. export default function compose(...funcs) {
  5. if (funcs.length === 0) {
  6. return arg => arg
  7. }
  8. if (funcs.length === 1) {
  9. return funcs[0]
  10. }
  11. return funcs.reduce((a, b) => (...args) => a(b(...args)))
  12. }

结合redux-thunk示例

redux-thunk源码,实现也很优雅,对于返回的function,将dispatch等参数传递进去,然后执行,等待回调异步完成再dispatch。对于正常对象则进行下一步。

  1. function createThunkMiddleware(extraArgument) {
  2. return ({ dispatch, getState }) => next => action => {
  3. // 每次dispatch的时候都会进行判断,如果是个函数,那就执行函数,不再进行下一步吧,这样就避免了,函数不满足action要求的问题
  4. if (typeof action === \'function\') {
  5. return action(dispatch, getState, extraArgument);
  6. }
  7. return next(action);
  8. };
  9. }
  10. const thunk = createThunkMiddleware();
  11. thunk.withExtraArgument = createThunkMiddleware;
  12. export default thunk;

那么实际使用时,在createStore时加入该中间件即可:

  1. import { createStore, applyMiddleware } from \'redux\'
  2. import thunk from \'redux-thunk\';
  3. const store = createStore(
  4. reducer,
  5. applyMiddleware({
  6. ...middleware,
  7. thunk})
  8. )

那么到这里对于redux的中间件 也就是问题2,我想大家也比较清楚了。
对于常见中间件可以参考

结束语

参考文章

redux中文文档
深入React技术栈
加上重读redux源码一带着问题看 react-redux 源码实现总算将redux及react-redux重读了一遍。可能有人会说道这些源码,看完也会忘,有这个必要吗。我感觉分情况来看,如果我们只是使用,那么看官方文档就可以了,当遇到某些疑问好像找不到贴切解释的时候,不放一看。
此外也是学习大佬们的设计思路和实现方式,有的放矢才能开卷有益。

随笔日记

java序列化,看这篇就够了

2020-11-9 4:59:40

随笔日记

Android 从零编写一个带标签 TagTextView

2020-11-9 4:59:42

0 条回复 A文章作者 M管理员
欢迎您,新朋友,感谢参与互动!
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
私信列表
搜索