redux源码阅读

445 阅读8分钟

一.设计者的品味

在某个特定背景(即需求)下,任何工具的产生,都是为了解决某个问题而存在,并且不断优化的过程。

在 props多级传递state引发不可测的连锁反应 即系统复杂度提升 下,redux的产生,使得state的变化可预测,优化是随着开发场景的需要而不断产生的:函数职责单一的合并而产生combineReducers,需dispatch增强而产生applyMiddleware......

好设计是简单的设计简单意味着符合我们的思维 --- 控制事情的发展。为了state变化的可预测可预测通常意味着命令式(即 “要怎样”)。所以,redux采用:state只读(即 不可变),action描述变化,reducer纯函数执行修改(即 变化)后返回新state。

所以,只有触发action才能改变state,为了连接action和state而产生reducer。

封装 state、action、reducer 而 产生 store。store相当于一个黑盒子,只需输入action,黑盒内部通过调用reducer和插件middleware处理state,输出 预期结果。

纯函数:相同输入映射相同输出,输出结果只有参数决定,无副作用

例1,我对着 安静的手机说,“siri 播放 《痒》”,手机播放了痒。

state对应手机状态,初始state为 安静,action描述变化(即 播放痒), reducer是 siri执行修改(即 播放痒),且无副作用(即 不会打开音乐以外的应用),新state为 播放痒的手机。

reducer(state, action) 连接了 state 和 action ,表现结果为 手机由 “安静” 到 “播放痒”。

例2,此时我又喊,“siri 发送查询话费的短信”,几秒钟后收到话费余额为0的短信。

state为 播放痒的手机且无话费短信,action为 发送短信,reducer执行了查询话费,新state为 播放痒的手机且有话费短信。reducer包含了两个动作:播放痒,发短信。

单一即简单。应用 函数单一职责,将 原reducer 拆分成两个reducer 并携带对应的state,分别拥有 播放痒(即reducer1) 、 发短信(即 reducer2)。应用 数据源单一, 将state以object tree形式 存储到唯一一个store。故 开发一个新reducer(即 combineReducers) 调用 reducer1 和 reducer2 ,返回新state作为object tree根节点 管理整个应用state。

单一数据源 因redux应用只有一个store,故拆分数据处理逻辑,应使用reducer组合
 每个reducer返回的新state 作为 object tree 的 节点。
相当于所有reducer组成一棵树rooReducer,store.js中调用createStore( rootReducer, initialState, applyMiddleware(...middlewares) ) 而产生 唯一一个store。

此时,redux的三大原则跃然纸上:单一数据源,state是只读的,用纯函数执行修改。

同时,闭包 在源码中展现的淋漓尽致。

二. redux核心源码

1. 分析

通过createStore()创建的黑盒子store: 如何生成黑盒子 ---> 黑盒子的规则 ---> 黑盒子内部运行原理 

看什么?专注于黑盒子store的产生 即createStore()函数,关注其中使用的辅助函数。


createStore(reducer, preloadedState, enhancer)涉及三个参数:

第一个参数reducer 相当于上文说的 object tree,即需要 combineReducers 去合并出一个根节点。该参数是对象。combineReducers源码见  “三.辅助函数源码 删减版” 。

第二个参数preloadedState 是初始化的state。该参数可省略。

第三个参数enhancer 是增强store功能,通常传入 applyMiddleware()函数applyMiddleware源码见 “三.辅助函数源码 删减版”。

createStore()可 两个参数或三个参数,故其内部需  if  判断 传入参数的个数。


createStore()创建出来的黑盒子(即 对象),提供了与state相关的4个API:(1)getState()返回当前的state树(2)dispatch()派发action,改变state的唯一途径(3)subscribe()添加监听函数,每次dispatch时调用(4)replaceReducer()替换当前用于计算的reducer


2. 源码

createStore():执行增强函数enhancer,返回 包含4个API 的对象。

function createStore(reducer, preloadedState, enhancer) {
  // 传入参数为(reducer, enhancer)
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error()
    }
    // 使用 enhancer 对 createStore 增强
    return enhancer(createStore)(reducer, preloadedState)
  }

  let currentReducer = reducer // store对应的reducer
  let currentState = preloadedState // 初始状态
  let currentListeners = [] // 监听函数listener的集合
  let nextListeners = currentListeners // listeners副本:增删操作在此进行
  let isDispatching = false // 是否正在dispatch 的标志位
  // 返回当前state
  function getState() {
    if (isDispatching) {
      throw new Error()
    }
    return currentState
  }
  // 订阅 store变化
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error()
    }
    if (isDispatching) {
      throw new Error()
    }

    let isSubscribed = true

    // listeners副本 添加监听函数
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error()
      }

      isSubscribed = false

      // listeners副本 删除监听函数
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
  // 派发action,更改state
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error()
    }

    if (typeof action.type === 'undefined') {
      throw new Error()
    }

    if (isDispatching) {
      throw new Error()
    }

    try {
      // 执行reducer
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 执行监听函数
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 动态修改reducer
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error()
    }

    currentReducer = nextReducer

    dispatch({ type: ActionTypes.REPLACE })
  }

  // 保证 nextListeners 和 currentListeners 不会指向同个数组
  function ensureCanMutateNextListeners() {
    // 指向相同数组,nextListeners作为副本
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
  }
}


3. 潜在问题

引子:有没有感觉getStore直接返回currentState这种做法不太严谨?

问题:直接返回 对象引用,对于刚上手redux的开发者极有可能对其进行篡改。

解决方案:源码中 返回一个副本。

或者在开发中使用 immutable 保证数据不可变。


三. 辅助函数源码  删减版

1. combineReducers

作用:将多个reducer函数合并成一个reducer函数。即单纯把一系列的函数以树的形式挂到一个root函数上。

执行时机是在dispatch时, 执行 传入reducers对象 中的各个reducer,记录返回的新state。

let reducers = combineReducers({ music: musicReducer, message: messageReducer });

思路:遍历reducers执行各个reducer,使用 nextState 记录  执行各个reducer返回的新state,返回nextState。

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  // 过滤 reducers ,保证 其key都有对应reducer
  const finalReducers = reducers
  const finalReducerKeys = reducerKeys

  return function combination(state,action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      // 取reducers对象中的key, 即 music、message
      const key = finalReducerKeys[i]

      // 取reducers对象中对应的reducer, 即 musicReducer、messageReducer
      const reducer = finalReducers[key]

      // 初始state
      const previousStateForKey = state[key]

      // reducer执行修改,返回的新state
      const nextStateForKey = reducer(previousStateForKey, action)

      // 更新state
      nextState[key] = nextStateForKey

      // 判断state是否改变。有一个reducer返回新state,则标志位为true。
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
    // state 改变则返回 新state,未变则返回 初始state
    return hasChanged ? nextState : state
  }
}


2. applyMiddleware

作用:执行各个中间件,对dispatch进行扩展,增强dispatch功能。最常见的dispatch接收函数,就是redux-thunk中间件。redux-thunk源码 见4.

在createStore()中若有enhancer则执行,而通常传入enhancer实参为applyMiddleware

enhancer(createStore)(reducer, preloadedState)

接收中间件数组middlewares,map循环执行各个中间件,得到 改造dispatch的函数集 数组

function applyMiddleware(...middlewares) {
  // 返回 增强版的createStore方法(增强体现在middlewares对dispatch的改造)
  return createStore => (...args) => {
    // ...arg就是reducer,[preloadedState]
    const store = createStore(...args);
    // 初始化dispatch,不允许构造中间件过程中 调用dispatch
    // 后续会被更改,映射到闭包中
    let dispatch = () => {
      throw new Error()
    }

    const middlewareAPI = {
      getState: store.getState,
      // 闭包,保存了上下文的dispatch变量。此值最终为 改造后的dispatch
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // middleware 通常是对 dispatch() 的两层封装。即 解封两次 可得到 改造版dispatch
    // 构造中间件,解封一次。
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 函数柯里化,依次使用中间件对dispatch进行改造,最终改造结果用dispatch接收,通过闭包映射
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}



3. compose

作用:根据函数柯里化,使用reduce依次执行各个中间件,改造dispatch。

因为使用reduce,所以中间件从右向左执行,故开发中可能对中间件的顺序有一定要求。

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

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


4. redux-thunk: 

作用:作为中间件, 改造dispatch,可使其处理异步请求。

被调用时,如下

// 构造中间件,解封一次。const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 函数柯里化,依次使用中间件对dispatch进行改造,最终改造结果用dispatch接收,通过闭包映射dispatch = compose(...chain)(store.dispatch)

思路:如果传入的action是函数,则执行action,并传入改造后的dispatch;如果是对象,则调用原dispatch派发action。

function createThunkMiddleware(extraArgument) {
// 第一个参数:middlewareAPI,其中的dispatch为 改造版dispatch
// 第二个参数:store.dispatch 即next为 原dispatch
// 第三个参数:action 
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action); // 原dispatch会返回 action,继续供下一个中间件使用
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;


四.React-Redux

redux是状态管理方案,它提供了一个状态容器。

直接将状态容器应用到项目中,会造成react组件和redux的高度耦合。

故 react-redux 封装 状态容器 后,可与 项目 进行连接,降低耦合度。

react-redux提供了Provider让所有组件可使用store,提供了高阶函数connect生成新组件。


五.改造自己的中间件

开发时,参照redux-thunk对dispatch封装 两层 回调函数。

可根据项目需要,开发适合项目的中间件,比如说 dispatch监听请求的状态,封装ajax,在发送请求后的回调函数中,根据响应状态进行不同的操作。