Redux 源码解析

概述

Redux 是 JavaScript 状态容器,提供可预测化的状态管理方案。其三大原则为:

  • 单一数据源 => 整个应用的 state 被储存在一颗 object tree 中,并且这个 object tree只存在于唯一一个 store 中
  • state 是只读的 => 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
  • 使用纯函数来执行修改 => 为了描述 action 如何改变 state tree,需要编写 reducers

纯函数: 即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用

Redux 的核心:

  • store => store 是由 Redux 提供的 createStore 生成的,通过 createStore 方法创建的 store 是一个对象
  • dispatch => 调度 state 的更新
  • reducer => 处理 state 的更新。reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。指定了应用状态的变化如何响应 actions 并发送到 store 的
  • action => action 是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源。action 是一个对象,其中包括 typepayload 属性。描述变化
  • Provider => 全局注册,在 内的组件都可拿到 state
  • connect => 连接组件和 store

自我实现

代码,这部分主要参照了这个系列的相关文章。相关的逻辑可以参照 commit message

官方文档

  1. 变化 vs 异步
  2. React 把处理 state 中数据的问题留给了开发者,Redux -> 处理 state 中的数据
  3. Redux 试图让 state 的变化变得可预测
  4. reducer 合成 -> 每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。开发一个函数作为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。
  5. combineReducers API
  6. store 职责:
    • 维持应用的 state
    • 提供 getState() 方法获取 state
    • 提供 dispatch(action) 方法更新 state
    • 通过 subscribe(listener) 注册监视器
    • 通过 subscribe(listener) 返回的函数注销监听器

源码

类型

  • export type Reducer = (state: S | undefined, action: A) => S;

createStore

  1. 类型:Function

  2. 参数:

    • reducer
    • 【可选】preloadedState> => 初始化 state
    • 【可选】enhancer> => 增强器,用来扩展store的功能
  3. 返回值:store, A, StateExt, Ext> & Ext>

源码分析

源码
  • 1 - 10:preloadedStateenhancer 不能同时为 Function
  • 12 - 15:如果 preloadedStateFunction 但是 enhancerundefined 那么将 preloadedState 赋给 enhancer,并且 preloadedState 置为 undefined
源码
  • 1 - 10:如果 enhancer 不为 undefined,那么 enhancer 必须为 Function,如果是 Function 直接返回 enhancer(createStore)(reducer, preloadedState)。这个在 applyMiddleware 时具体讲解
// TODO: 使用一个 enhancer 作为例子
  • 12 - 14: 如果 reducer 不是 Function 则抛错
源码
  • currentReducer => 传入的 reducer
  • currentState => preloadedState => undefined | 用户传入的 state
  • currentListeners: (() => void)[] | null => [] => 存储更新函数的数组
  • nextListeners => [] => 下次dispatch将会触发的更新函数数组
  • isDispatching => Lock,当前是否在 dispatch
源码

定义 ensureCanMutateNextListenersgetStatesubscribedispatchreplaceReducerobservable 函数

源码
  • 2:dispatch 一个 ActionTypes.INITaction => 使得每个 reducer 返回初始 state
  • 4 - 11: 创建 store 变量并返回
dispatch
dispatch
  • 2 - 4:判断 action 是否是纯粹的对象。即不是 Array | Function | null,isPlainObject 定义
  • 6 - 8: action 必须有 type
  • 10 - 12:如果当前正在 dispatch 则不能 dispatch => 不可以同时 dispatch 两个 action
  • 14 - 19:执行 currentReducer,传入 currentStateaction
  • 21 - 25:将 nextListeners 赋给 currentListenerslisteners,之后循环遍历执行 listener
    最终 return action
getState
getState

getState 方法比较简单,就是 return currentState

subscribe
subscribe
  • 2 - 8:如果 listener 不是 Function 或者 isDispatching === true 抛错
  • 10:定义 isSubscribed => 是否已经订阅
  • 12: 执行 ensureCanMutateNextListeners 函数
  • 13:将 listener 推入 nextListeners
  • 15:return unsubscribe 函数
  • 16 - 20:如果 isSubscribed === false 直接返回, 如果 isDispatching === true 抛错
  • 22:关
  • 24:执行 ensureCanMutateNextListeners 函数
  • 25 - 27:在 nextListeners 中删除 listener 函数,并将 currentListeners 置为 null
ensureCanMutateNextListeners
ensureCanMutateNextListeners

currentListeners 做了浅拷贝以便于可以在 dispatch 的时候使用 nextListeners 作为临时的 listener。这样可以防止使用者在 dispatch 的时候调用 subscribe/unsubscribe 出现 bug。避免相互影响

combineReducers

  1. 类型:Function
  2. 参数:reducers
  3. 返回值:Function
  4. 使用 combineReducers 例子

源码分析

源码
  • 2 - 13:将用户传入的 reducers 做一个 shallow copy,并且剔除不是 Functionreducer
    • finalReducers 为有效的 reducer
    • finalReducerKeys => 有效 reducerkey
  • 15 - 20:执行 assertReducerShape 函数,如有 ErrorError 赋值给 shapeAssertionError
  • 22:返回 combination 函数
assertReducerShape
assertReducerShape

遍历给到的 reducer

  • 如果在初始化 action 时返回 undefined抛错
  • 如果执行一个随机 action 时返回 undefined抛错
combination
combination
  • 2 - 4:如果 assertReducerShape 函数执行时出错,则抛出错误
  • 6 - 7: 定义 hasChangednextState 变量
  • 8 - 19:遍历 finalReducerKeys 数组,previousStateForKey = state[key]nextStateForKey = reducer(previousStateForKey, action),如果 nextStateForKeyundefined 抛错。将 reducer 计算出来的 state 存入 nextState 中。并且判断 previousStateForKeynextStateForKey 是否改变了
  • 20 - 21:判断 finalReducerKeys 和传入 statekeylength 是否改变。最终 return hasChanged ? nextState : state

compose

  1. 类型:Function
  2. 参数:Function[]
  3. 返回值:Function
  4. 使用 compose 例子

源码分析

compose
  • 2 - 5:如果数组为空,返回一个空函数
  • 7 - 9:如果数组的 length 为1,则返回第一个函数
  • 11:对函数数组进行 reduce 操作
    compose(increase, square, add) // (...args) => increase(sqpare(add(...args)))
    

注意:Array.reduce() 的使用 => 回调函数第一次执行时,accumulatorcurrentValue 取值有两种情况:如果调用 reduce() 时提供了 initialValueaccumulator 取值 initialValuecurrentValue 取数组中的第一个值;如果没有提供 initialValue,那么 accumulator 取数组中第一个值,currentValue 取数组中的第二个值

Lodash compose -> flow

Lodash compose

applyMiddleware

  1. 类型:Function
  2. 参数:Middleware[]
  3. 返回值:Function
  4. Middleware:({getState}) => next => dispatch => ({getState}) => dispatch => action => any

applyMiddleware的功能:改造dispatch函数,产生真假dispatch,而中间件就是运行在假真(dispatchAndLog假和next真)之间的代码。

源码分析

applyMiddleware

applyMiddleware 函数,直接返回一个函数,我们直接看这个函数即可。这个函数的结构是 (createStore) => (reducer, preloadedState?) => store

  • 6:执行 createStore 函数,获取 store
  • 7 - 12:定义 dispatch 函数
  • 14 - 17:定义 middlewareAPI 对象,这里 middlewareAPI 只有两个属性,getStatedispatch
  • 18:将用户传入的 middleware[] 依次执行,并将 middlewareAPI 作为参数传给各个 MiddlewareMiddleware 的例子如下
    function logger({ getState }) {
      return next => action => {
        console.log('will dispatch', action)
    
        const returnValue = next(action)
    
        console.log('state after dispatch', getState())
    
        return returnValue
      }
    }
    
    此时 chain 的值为 Middleware 返回值的数组
    // chain => [middleware1, middleware2, middleware3]
    next => action => {
        console.log('will dispatch', action)
    
        const returnValue = next(action)
    
        console.log('state after dispatch', getState())
    
        return returnValue
    }
    
  • 19: dispatch = compose(...chain)(store.dispatch)。使用 compose 函数,此时 Middleware 中的 next === store.dispatch
  • 21 - 24:return store,这里的 dispatch 是 19 行的 dispatch

之后看一下 Middleware 使用场所。是作为 applyMiddleware 的参数使用。applyMiddleware 做为 createStore 的第三个参数传入 createStore

createStore enhancer

此时的 enhancer === applyMiddleware,之后在 createStore 里面直接执行
applyMiddleware,将 createStorereducer + preloadedState 传入 applyMiddleware,即在 applyMiddleware 代码中的第 21 行 - 第 24 行的返回值即为 createStore 的返回值

applyMiddleware 做了什么

  1. 执行传入的 Middleware,并将 state 作为参数传入 Middleware
  2. 更新 dispatch,这个更新后的 dispatch 是用户传入的(强化的 dispatch),例如上述的 logger 例子
    action => {
        console.log('will dispatch', action)
    
        // 此处的 next === dispatch === store.dispatch === createStore(reducer, preloadedState).dispatch
        const returnValue = next(action)
    
        console.log('state after dispatch', getState())
    
        return returnValue
    }
    

注意点

  1. enhancer store 的增强器。enhancer 是一个高阶函数,返回值是一个经过包装的强化的 storeapplyMiddleware 就是一个 enhancer

疑问

Redux 做了什么?

  1. 存储 state
  2. 增删改查 state => 可以理解为 state 的变化
  3. 触发更新

middleware 的执行顺序

Redux 的中间件模型类似于 koa。在 next 前面以及 next,由外向内依次执行。当最里面的 next 执行完成之后,next 后面的代码会由内向外执行。非常类似于 Koa 的洋葱中间件模型。

dispatch 之后,Redux 是如何去处理的?

  1. 执行 reducer 更新 currentState
  2. 依次执行 listener 函数

state 中的数据被修改之后,订阅者们如何去收到更新后的数据?

subscribe 中通过 store.getState() 获取数据

applyMiddlewaredispatch 为何赋值两次

第一次赋值表示 => 在 Middleware 创建时,不能进行 dispatch
第二次赋值表示 => 此 dispatch 是一个强化后的 dispatch

middlewareAPI 中的 dispatch 什么时候会被调用

注意使用场景

function logger({ getState, dispatch }) {
  // 此时的 dispatch 为抛错函数,即第一次赋值的函数,即第 7 - 12 行
  return next => {
    // 此处的 next === store.dispatch
    // 此时的 dispatch === 下面 return 的函数
    return action => {
        console.log('will dispatch', action)

        const returnValue = next(action)

        console.log('state after dispatch', getState())

        return returnValue
      }
  }
}

middlewareAPI 中的 dispatch 为啥要用匿名函数包裹

let dispatch = () => console.log('Error!');
const obj = {
  name: 'dispatch',
  dispatch,
}
obj.dispatch();

dispatch = () => console.log('Ready!');
obj.dispatch();

上述的 log 是什么结果呢?


result
let dispatch = () => console.log('Error!');
const obj = {
  name: 'dispatch',
  dispatch: (...args) => dispatch(...args),
}
obj.dispatch();

dispatch = () => console.log('Ready!');
obj.dispatch();

上述的 log 是什么结果呢?


result

store.subscribe 为啥要有 isDispatching

dispatch 执行时候会循环执行更新函数,要保证 listeners 数组在这时候不能被改变

以下代码作用

createStore

答:有了这一层判断,我们就可以这样传:createStore(reducer, initialState, enhancer) 或者这样:createStore(reducer, enhancer),其中 enhancer 还会是 enhancer

Redux 优势

  1. 纯函数,做测试的时候 easy

你可能感兴趣的:(Redux 源码解析)