Redux 核心源码

424 阅读7分钟

xps-7ZWVnVSaafY-unsplash.jpg

图片来源 unsplash.com/photos/7ZWV…

前言

Redux 是 JS 应用的状态容器,提供可预测的状态管理。可以让你开发出行为稳定可预测的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。

基础示例

import { createStore } from 'redux'


function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}


let store = createStore(counterReducer)



store.subscribe(() => console.log(store.getState()))


store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}

Redux 工作流程

未命名绘图 (25).png

几个核心概念

  • Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。

  • State:Store 对象包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State,通过 Store 的 getState() 方法获得。

  • Action:Action 是一个普通对象,State 的变化,会导致 View 的变化。用户接触不到State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

  • Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。

  • Reducer:Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State的计算过程就叫做 Reducer。Reducer是一个函数,它接受 Action和当前State作为参数,返回一个新的State。

  • dispatch:是 View 发出 Action 的唯一方法。

整个用户交互流程:

  1. 用户(通过 View )通过 dispatch 方法发出 Action。

  2. 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer会返回新的 State。

  3. 当 State 一旦有变化,Store 就会调用 subscribe 监听函数,来更新 View。

ReduxDataFlowDiagram-49fa8c3968371d9ef6f2a1486bd40a26.gif

可以看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。

one-way-data-flow-04fe46332c1ccb3497ecb04b94e55b97.png

Redux 三大原则

单一数据源

整个应用的全局 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

State 是只读的

唯一改变 state 的方法就是触发 action ,action 是一个用于描述已发生事件的普通对象。

使用 Reducer 纯函数来执行修改

为了描述 action 如何改变 state tree,你需要编写纯的 reducers

Redux 设计思想

Redux 作者在 Redux.js 官方文档 Motivation 一章的最后一段明确提到:

Following in the steps of FluxCQRS, and Event SourcingRedux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen.

我们就先了解一下 FluxCQRSES(Event Sourcing 事件溯源)这几个概念。

Event Sourcing

Event Sourcing 也叫事件溯源,是由 Martin Fowler 提出的一种架构模式,它有几个特点:

  • 整个系统以事件为驱动,所有业务都由事件驱动来完成。

  • 事件是一等公民,系统的数据以事件为基础,事件要保存在某种存储上。

  • 通过事件追溯得到对象最新状态。

举个例子:我们平常记账有两种方式,直接记录每次账单的结果或者记录每次的收入/支出,那么我们自己计算的话也可以得到结果,ES 就是后者。

CQRS(Command Query Responsibility Segregation)

CQRS,是 Command Query Responsibility Segregation 的缩写,也就是通常所说的读写分离

CQRS 将系统中的操作分为两类,即「命令」(Command) 与「查询」(Query)。命令则是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。而查询则和字面意思一样,即不会对数据产生变化的操作,只是按照某些条件查找数据。

CQRS 的核心思想是将这两类不同的操作进行分离,然后在两个独立的「服务」中实现。这里的「服务」一般是指两个独立部署的应用。在某些特殊情况下,也可以部署在同一个应用内的不同接口上。

9e655d12.png

Flux

Flux 是一种架构思想,下面过程中,数据总是“单向流动”,任何相邻的部分都不会发生数据的“双向流动”,这保证了流程的清晰。Flux 的最大特点,就是数据的“单向流动”

Action -> Dispatcher -> Store -> View

efd23693.png

整个流程如下:

  • 首先要有 action,通过定义一些 action creator 方法根据需要创建 Action 提供给 dispatcher
  • View 层通过用户交互(比如 onClick)会触发 Action
  • Dispatcher 会分发触发的 Action 给所有注册的 Store 的回调函数
  • Store 回调函数根据接收的 Action 更新自身数据之后会触发一个 change 事件通知 View 数据更改了
  • View 会监听这个 change 事件,拿到对应的新数据更新组件 UI

Redux 与 Flux 的区别

Redux 只有一个 Store

Flux 中允许有多个 Store,但是 Redux 中只允许有一个,相较于 Flux,一个 Store 更加清晰,容易管理。Flux 里面会有多个 Store 存储应用数据,并在 Store 里面执行更新逻辑,当 Store 变化的时候再通知 controller-view 更新自己的数据;Redux 将各个 Store 整合成一个完整的Store,并且可以根据这个 Store 推导出应用完整的 State。

同时 Redux 中更新的逻辑也不在 Store 中执行而是放在 Reducer 中。单一 Store带来的好处是,所有数据结果集中化,操作时的便利,只要把它传给最外层组件,那么内层组件就不需要维持State,全部经父级由 props 往下传即可。

Redux 中没有 Dispatcher 的概念

Redux 去除了这个 Dispatcher ,使用 Store 的 Store.dispatch() 方法来把 action 传给Store,由于所有的 action 处理都会经过这个 Store.dispatch() 方法,Redux 聪明地利用这一点,实现了与 Koa、RubyRack 类似的 Middleware 机制。Middleware 可以让你在 dispatch action 后,到达 Store 前这一段拦截并插入代码,可以任意操作 action 和 Store。

Redux 源码

WeChat54bcd533be80a403fbd409fba4e75b2b.png

redux 源码地址:github.com/reduxjs/red…

index.ts 入口文件

可以从 这里 看到源码

.....

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

入口文件主要是导出 createStorecombineReducersbindActionCreatorsapplyMiddlewarecompose 几个 API。

createStore.ts 主流程文件

可以从 这里 看到源码

const store = createStore(reducer, preloadedState, enhancer)
import $$observable from './utils/symbol-observable'

import {
  Store,
  PreloadedState,
  StoreEnhancer,
  Dispatch,
  Observer,
  ExtendState
} from './types/store'
import { Action } from './types/actions'
import { Reducer } from './types/reducers'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
import { kindOf } from './utils/kindOf'


export default function createStore<
  S,
  A extends Action,
  Ext = {},
  StateExt = never
>(
  reducer: Reducer<S, A>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
export default function createStore<
  S,
  A extends Action,
  Ext = {},
  StateExt = never
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
export default function createStore<
  S,
  A extends Action,
  Ext = {},
  StateExt = never
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {

 // preloadedState 和 enhancer 不都为 function,否则会报错。
 
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
    )
  }
  
  // 当 preloadedState 为 function ,enhancer 为 undefined 的时候说明 initState 没有初始化, 但是传入了 middleware 中间件。

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined')   {   
    // 把 preloadedState 赋值给 enhancer 。
    
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    
    // preloadedState 赋值 undefined
    preloadedState = undefined
  }
  
  // enhancer 存在
  if (typeof enhancer !== 'undefined') {
  
    // enhancer 不为 function , 抛出错误。
    
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(
          enhancer
        )}'`
      )
    }
    
   // 执行 enhancer 函数
   
    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  // enhancer 不为函数抛出错误
  
  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
    )
  }
  // 进行变量赋值操作
  
  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  // 浅拷贝队列
  let nextListeners = currentListeners
  // 否正在执行 dispatch 
  let isDispatching = false


  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 获取当前的 state 
  function getState(): S {
  
    // 如果 dispatch 正在进行中,则抛出错误。
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }
    // 返回 state 
    return currentState as S
  }

  // subscribe 方法设置监听函数,一旦触发 dispatch ,就会自动执行这个函数。
  
  function subscribe(listener: () => void) {
    // listener 不为函数时抛出错误
    if (typeof listener !== 'function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(
          listener
        )}'`
      )
    }
    // 当正在 dispatch 中抛出错误
    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api/store#subscribelistener for more details.'
      )
    }
    // 订阅标记作用
    let isSubscribed = true

    ensureCanMutateNextListeners()
    
    nextListeners.push(listener)
    // 返回取消监听函数
    return function unsubscribe() {
      
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api/store#subscribelistener for more details.'
        )
      }

      isSubscribed = false
      
      // 保存订阅快照
      ensureCanMutateNextListeners()
      // 找到并删除 listener
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

 
  function dispatch(action: A) {
  
    // acticon 必须是由 Object 构造的函数
    
    if (!isPlainObject(action)) {
      throw new Error(
        `Actions must be plain objects. Instead, the actual type was: '${kindOf(
          action
        )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
      )
    }
    // action.type 是否存在
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
      )
    }
    // 判断 dispatch 是否正在进行中
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

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

    return action
  }


  // 替换 reducer,用于 热替换、按需加载等场景
  function replaceReducer<NewState, NewActions extends A>(
    nextReducer: Reducer<NewState, NewActions>
  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
    // nextReducer 不是函数则报错
    if (typeof nextReducer !== 'function') {
      throw new Error(
        `Expected the nextReducer to be a function. Instead, received: '${kindOf(
          nextReducer
        )}`
      )
    };
    // 当前的 currentReducer 更新为 nextReducer
    
    (currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer

  
    dispatch({ type: ActionTypes.REPLACE } as A)
    
    return store as unknown as Store<
      ExtendState<NewState, StateExt>,
      NewActions,
      StateExt,
      Ext
    > &
      Ext
  }


  .....


  dispatch({ type: ActionTypes.INIT } as A)

  const store = {
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  } as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  return store
}

applyMiddleware 中间件

在实际项目中,一般都会有同步和异步操作,所以 FluxRedux 之类的思想,最终都要落地到同步异步的处理中来。

在 Redux 中,同步的表现就是:Action 发出以后,Reducer 立即算出 State。那么异步的表现就是:Action 发出以后,过一段时间再执行 Reducer

为了 Reducer 在异步操作结束后自动执行,Redux 引入了中间件 Middleware 的概念。

const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))

applyMiddleware.ts

可以从 这里 看到源码

import compose from './compose'
.....

export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreEnhancerStoreCreator) =>
    <S, A extends AnyAction>(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>
    ) => {
      // 执行 createStore 方法并且赋值给 store
      const store = createStore(reducer, preloadedState)
      
      // 定义了一个dispatch, 调用会抛出错误
      let dispatch: Dispatch = () => {
        throw new Error(
          'Dispatching while constructing your middleware is not allowed. ' +
            'Other middleware would not be applied to this dispatch.'
        )
      }
      
      // 定义middlewareAPI, 中间件中的 store ,将会传递给中间件作为参数。
      const middlewareAPI: MiddlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }
      
      // 调用每一个这样形式的 middleware = middlewareAPI => next => action =>{},
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

      // 返回增强的 store
      return {
        ...store,
        dispatch
      }
    }
}

从上面的代码中我们可以知道,applyMiddleware 返回一个参数为 createStore 的函数。

return (createStore)=>{
     return (reducer,preloadedState?){
        .......
     }
}

createStore.ts 中,

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(
          enhancer
        )}'`
      )
    }
    return enhancer(createStore)(
      reducer,
      preloadedState
    ) 
  }

我们执行 enhancer 中间件,传入 createStore 参数,它就会返回一个参数为 reducerpreloadedState 的函数。

compose 函数

从右到左来组合多个函数。这是函数式编程中的方法,为了方便,被放到了 Redux 里。当需要把多个 store 增强器 依次执行的时候,需要用到它。

 dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

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

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

重点看这段代码:

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

reduce 函数 MDN 文档

假如 funcs 是有三个函数的数组 [ f1 , f2 , f3 ] ,执行 funcs.reduce((a, b) => (...args) => a(b(...args))) ,那么 reduce 执行过程如下:

  1. 第一次调用:a = f1 , b = f2 , returnValue1 = (...args) => f1(f2(...args))

  2. 第二次调用:a = returnValue1, b = f3, returnValue2 = (...args) => returnValue1(f3(...args),即 f1(f2(f3(...args)))

combineReducers

combineReducers 辅助函数的作用是把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名

combineReducers 源码:github.com/reduxjs/red…

export default function combineReducers(reducers: ReducersMapObject) {
  // 使用 Object.keys 方法将reducers中可枚举的属性生成一个keys数组
  const reducerKeys = Object.keys(reducers)
  const finalReducers: ReducersMapObject = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    
     // 省略开发环境判断的代码 ....
     
     // reducer要是一个function
    if (typeof reducers[key] === 'function') {
      // 赋值给finalReducers
      finalReducers[key] = reducers[key]
    }
  }
  
  

  // 将finalReducers 中可枚举的属性生成一个keys 数组
  const finalReducerKeys = Object.keys(finalReducers)


  
   // 省略开发环境判断的代码 ....
  

  ........

  return function combination(
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    ......

    // 省略开发环境判断的代码 ....

    // 使用 hasChanged 变量来判断前后的 state 是否发生修改
    let hasChanged = false
    
    // 声明一个 nextState 对象用来存储更新后的 state
    const nextState: StateFromReducersMapObject<typeof reducers> = {}
    
    for (let i = 0; i < finalReducerKeys.length; i++) {
      // 获取finalReducerKeys的key和value(function)
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      
      const previousStateForKey = state[key]
      
      const nextStateForKey = reducer(previousStateForKey, action)
      
      if (typeof nextStateForKey === 'undefined') {
        const actionType = action && action.type
        
        throw new Error(
          `When called with an action of type ${
            actionType ? `"${String(actionType)}"` : '(unknown type)'
          }, the slice reducer for key "${key}" returned undefined. ` +
            `To ignore an action, you must explicitly return the previous state. ` +
            `If you want this reducer to hold no value, you can return null instead of undefined.`
        )
      }
      
      nextState[key] = nextStateForKey
      
      // 对比两次 key , 不相等则发生变化
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

bindActionCreators

把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。

惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。

源码:github.com/reduxjs/red…

.....
// bindActionCreator 的作用:1.生成动作 2.执行动作
function bindActionCreator<A extends AnyAction = AnyAction>(
  actionCreator: ActionCreator<A>,
  dispatch: Dispatch
) {
  // 这是一个闭包
  return function (this: any, ...args: any[]) {
    // 执行后返回结果为传入的 actionCreator 直接调用 arguments
    return dispatch(actionCreator.apply(this, args))
  }
}


export default function bindActionCreators(
  actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
  dispatch: Dispatch
) {
  // actionCreators为function
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  // 错误处理
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, but instead received: '${kindOf(
        actionCreators
      )}'. ` +
        `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  const boundActionCreators: ActionCreatorsMapObject = {}
  
  for (const key in actionCreators) {
    
    const actionCreator = actionCreators[key]
    
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  
  return boundActionCreators
}

参考资料

Redux 中文官网

Redux从设计到源码