关于react.js:redux-源码解读

2次阅读

共计 16818 个字符,预计需要花费 43 分钟才能阅读完成。

redux 作为 react 生态圈中最常见的状态库,从源码来探索一下 redux 是如何做状态治理的。redux 源码并不简单,理解源码之前,咱们先看看咱们会怎么应用 redux。

import {createStore, combineReducers, applyMiddleware} from 'redux'
import reduxThunk from 'redux-thunk'

const initState = {list: []
}

// 纯函数,用做状态更新
function reducer(state = initState, action){switch(action.type){

        case 'todoAdd':

            return {list: state.list.concat(action.text)

            }

        case 'todoRemove':

            return {list: state.list.filter((v) => v !== action.text)

            }

        default:

            return state

    }

}

/*
    // 多个 reducer 能够应用 combineReducers 合并成一个 reducer
    const reducer = combineReducers({
    
      todo,

      user,

  })
*/

// 承受 reducer 返回 store reducer 少数状况下不止一个你可能会用 combineReducers
// applyMiddleware 的作用就是注册 redux 中间件从新包装 dispatch 赋予 dispatch 新能力
// reduxThunk 赋予 action 有解决异步的能力
let store = createStore(reducer, applyMiddleware(reduxThunk))

// 订阅 store 更新
store.subscribe(() => {
    // 在这里能够获取到最新的状态 来更新你的利用
    console.log(store.getState())

})

// 派发 action,批改可监听状态的惟一形式
/*{

    type: 'todoAdd',

    text: '吃饭',
} 这就是 action 的格局
*/
store.dispatch({

    type: 'todoAdd',

    text: '吃饭',

})

store.dispatch({

    type: 'todoAdd',

    text: '睡觉',

})

store.dispatch({

    type: 'todoRemove',

    text: '睡觉',

})

接下来咱们从例子动手来剖析源码

createStore

这是咱们创立 store 的入口,源码如下:

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 {
  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.'
    )
  }
  // 如果第二个参数是函数,把第二个参数当作 enhancer,也就是说 preloadedState 能够疏忽不传
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    // 检测 enhancer 的类型是否非法
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(enhancer)}'`
      )
    }
    // 扩大包装 createStore
    // 其实就是 applyMiddleware 返回的函数
    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  // 查看 reducer 合法性
  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
  let isDispatching = false

  /**
   * This makes a shallow copy of currentListeners so we can use
   * nextListeners as a temporary list while dispatching.
   *
   * This prevents any bugs around consumers calling
   * subscribe/unsubscribe in the middle of a dispatch.
   */
  // 浅拷贝监听列表
  // 浅拷贝的目标可能是避免正在进行派发告诉的时候可能又有监听退出进来避免程序出错
  function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice()
    }
  }

  // 返回以后状态
  /**
   * Reads the state tree managed by the store.
   *
   * @returns The current state tree of your application.
   */
  function getState(): S {
    // 如果是正在做派发更改 state 的动作就抛出异样
    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.'
      )
    }

    return currentState as S
  }

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * You may call `dispatch()` from a change listener, with the following
   * caveats:
   *
   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
   * If you subscribe or unsubscribe while the listeners are being invoked, this
   * will not have any effect on the `dispatch()` that is currently in progress.
   * However, the next `dispatch()` call, whether nested or not, will use a more
   * recent snapshot of the subscription list.
   *
   * 2. The listener should not expect to see all state changes, as the state
   * might have been updated multiple times during a nested `dispatch()` before
   * the listener is called. It is, however, guaranteed that all subscribers
   * registered before the `dispatch()` started will be called with the latest
   * state by the time it exits.
   *
   * @param listener A callback to be invoked on every dispatch.
   * @returns A function to remove this change listener.
   */
  // 订阅 store 状态的变动
  function subscribe(listener: () => void) {
    // 非函数抛错
    if (typeof listener !== 'function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(listener)}'`
      )
    }
    
    // 如果正在做状态更改的操作抛出异样 状态更新结束能力做监听
    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

    // 浅拷贝监听函数 至 nextListeners 中
    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()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. Its return value will
   * be considered the **next** state of the tree, and the change listeners
   * will be notified.
   *
   * The base implementation only supports plain object actions. If you want to
   * dispatch a Promise, an Observable, a thunk, or something else, you need to
   * wrap your store creating function into the corresponding middleware. For
   * example, see the documentation for the `redux-thunk` package. Even the
   * middleware will eventually dispatch plain object actions using this method.
   *
   * @param action A plain object representing“what changed”. It is
   * a good idea to keep actions serializable so you can record and replay user
   * sessions, or use the time travelling `redux-devtools`. An action must have
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  function dispatch(action: A) {
    // action 必须是一般对象
    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 类型必须存在
    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.')
    }

    // 是否正在做状态更改操作
    if (isDispatching) {throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 调用 reducer 返回新的状态
      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
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param nextReducer The reducer for the store to use instead.
   * @returns The same store instance with a new reducer in place.
   */
  function replaceReducer<NewState, NewActions extends A>(nextReducer: Reducer<NewState, NewActions>): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {if (typeof nextReducer !== 'function') {
      throw new Error(
        `Expected the nextReducer to be a function. Instead, received: '${kindOf(nextReducer)}`
      )
    }

    // TODO: do this more elegantly
    ;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer

    // This action has a similar effect to ActionTypes.INIT.
    // Any reducers that existed in both the new and old rootReducer
    // will receive the previous state. This effectively populates
    // the new state tree with any relevant data from the old one.
    dispatch({type: ActionTypes.REPLACE} as A)
    // change the type of the store by casting it to the new store
    return store as unknown as Store<
      ExtendState<NewState, StateExt>,
      NewActions,
      StateExt,
      Ext
    > &
      Ext
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
  function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer: unknown) {if (typeof observer !== 'object' || observer === null) {
          throw new TypeError(
            `Expected the observer to be an object. Instead, received: '${kindOf(observer)}'`
          )
        }

        function observeState() {
          const observerAsObserver = observer as Observer<S>
          if (observerAsObserver.next) {observerAsObserver.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return {unsubscribe}
      },

      [$$observable]() {return this}
    }
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  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
}

createStore 承受三个参数:

  • reducer 更新 state 的村函数
  • preloadedState 初始化的状态值
  • enhancer 加强函数会返回一个新的 store 对象,对于咱们的例子中就是 applyMiddleware 的返回函数

返回的 store 对象蕴含如下办法:

  • dispatch 派发,也就是更新状态的入口
  • subscribe 订阅状态更新
  • getState 获取以后状态
  • replaceReducer 用得少暂不关怀,看源码是用来 reducer 插队的,也就是插队更新

从 createStore 主函数开始

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.'
    )
  }
  // 如果第二个参数是函数,把第二个参数当作 enhancer,也就是说 preloadedState 能够疏忽不传
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    // 检测 enhancer 的类型是否非法
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(enhancer)}'`
      )
    }
    // 扩大包装 createStore
    // 其实就是 applyMiddleware 返回的函数
    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  // 查看 reducer 合法性
  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
  let isDispatching = false

第一个 if 判断切实判断 enhancer 是不是间断传了两个,第二个 if 断定,自身 create 承受三个参数依照程序是 reducer,preloadedState(初始化状态),enhancer(扩大函数),createStore 容许只传递两个函数,不传 preloadedState。所以呈现了 enhancer = preloadedState 这样子的代码,第三个 if 示意 enhancer 存在要扩大 store 的能力。至于是什么能力,大部分状况下是看你的 redux 中间件的性能是什么。

接下来 getState 就是简略的返回以后的状态对象

subscribe

订阅,redux 的状态更新是基于公布订阅模式的,也就是说有状态更新就会登程 subscribe 的订阅回掉。subscribe 源码如下:

// 订阅 store 状态的变动
  function subscribe(listener: () => void) {
    // 非函数抛错
    if (typeof listener !== 'function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(listener)}'`
      )
    }
    
    // 如果正在做状态更改的操作抛出异样 状态更新结束能力做监听
    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

    // 浅拷贝监听函数 至 nextListeners 中
    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()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

后面两个断定仍然是在做合理性校验,顺次断定监听回掉是否是函数断定,以及以后是否有 dispatch 操作正在进行中未实现。ensureCanMutateNextListeners 的作用是做 subscribe 回掉函数的浅拷贝。将以后监听对象拷贝到 nextListeners 中,浅拷贝的目标是避免新来的监听打乱原来的监听对象程序,所以没有间接操作原来的监听队列。subscribe 会返回一个勾销监听的函数

dispatch

触发更新的 dispatch 函数,源码如下:

function dispatch(action: A) {
    // action 必须是一般对象
    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 类型必须存在
    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.')
    }

    // 是否正在做状态更改操作
    if (isDispatching) {throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 调用 reducer 返回新的状态
      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
  }

后面的断定都是在做合理性校验,isPlainObject 在查看 action 是否是一个一般对象,一般对象只能是通过对象字面量或者 new Object 创立进去的。所以咱们个别写 action 都是 {type: ‘**’, paload: {} } 这种形式。其余的 action.type 必须存在,isDispatching 的作用是避免上一个更新操作还没有实现,新的 dispatch 又开始了,这样子会呈现状态凌乱,所以如果上次更新操作没有实现,就不能进行 dispatch。做完
reducer 的更新操作就开始程序执行 listeners,listeners 就是 store.subscribe 的监听函数。告诉监听函数状态产生了变动,你能够开始做状态更新之后的操作,比方:更新视图

applyMiddleware

applyMiddleware 的作用是注册 redux 中间件就像咱们下面的例子 createStore 的第二个参数就是注册了 redux-thunk 这个中间件,给 action 赋予了解决异步的能力,那这是怎么办到的呢。先看 applyMiddleware 源码:

export default function applyMiddleware(...middlewares: Middleware[]
): StoreEnhancer<any> {return (createStore: StoreEnhancerStoreCreator) =>
    <S, A extends AnyAction>(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>
    ) => {const store = createStore(reducer, preloadedState)
      let dispatch: Dispatch = () => {
        throw new Error(
          'Dispatching while constructing your middleware is not allowed.' +
            'Other middleware would not be applied to this dispatch.'
        )
      }

      const middlewareAPI: MiddlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }

      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      // 从新包装 dispatch 这里就能够给 dispatch 赋予异步能力了
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

      return {
        ...store,
        dispatch
      }
    }
}

能够看见 applyMiddleware 承受中间件列表。而后返回了一个新的 store 对象。所以 applyMiddleware 的作用就是通过传递过去的中间件来扩大 store,再看 applyMiddleware 返回的函数:

(createStore: StoreEnhancerStoreCreator) =>
    <S, A extends AnyAction>(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>
    ) => {...}

返回了一个函数承受 createStore,而后在承受 reducer,preloadedState 做操作,这个返回函数就和 createStore 中的 enhancer 是一样的。会看 createStore 中 enhancer 的调用:

enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext

他们是一样的。
接下来咱们查看 applyMiddleware 是如何解决中间件从新包装的,外围代码:

      const store = createStore(reducer, preloadedState)
      let dispatch: Dispatch = () => {
        throw new Error(
          'Dispatching while constructing your middleware is not allowed.' +
            'Other middleware would not be applied to this dispatch.'
        )
      }

      const middlewareAPI: MiddlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }

      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      // 从新包装 dispatch 这里就能够给 dispatch 赋予异步能力了
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

      return {
        ...store,
        dispatch
      }

看看 redux 中间件的格局,redux 中间件代码须要如下的样子:

const middlware = (store) => (next) => (action) => {return next()
}

为什么须要这样子,因为 redux 绑定中间件和 store 的代码其实就是去柯里化的过程:
第一步:

const chain = middlewares.map(middleware => middleware(middlewareAPI))

这一步就是去柯里化第一层,传递 store 进去也就是 middlewareAPI。

第二步:

dispatch = compose(...chain)(store.dispatch)

先看看 compose 这个函数,代码如下:

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))
  )
}

通过第一步的去柯里化操作,当初的中间件曾经变成了这个样子:

const chain = [(next) => (action) => {...} ];

当然因为闭包 chain 中所有的元素都能够拜访到 middlewareAPI,这便是第一步的作用绑定 middlewareAPI 到中间件中去。再看 compose 函数次要就是通过 reduce,reduce 这个 api 数组调用,reduce 的回掉会承受两个参数 a, b。a 是上一次 reduce 调用的后果,b 是以后的元素。始终到数组遍历完。这就是第二步去柯里化的操作。
也就是说。如果有 a,b,c 三个中间件,这一步会这样子。a(b(c())).c 的后果返回给 b 作为参数 b 的后果返回给 a 作为参数。业界形象的说这叫“洋葱模型”。所以这一步操作是在给 next 确定值,next 的格局就是 (action) => {…} 这样子,就是一个 dispatch 的样子,直到最初返回一个新的 (action) => {…} 也就是 dispath。
那如果咱们要解决异步,咱们就能够写一个异步中间件,如下:

const syncMiddlware = (store) => (next) => (action) => {
    
    // 咱们能够吧 action 写成函数,函数承受 dispatch,也就是让 action 决定什么时候去派发,这样子就能够在 action 中做异步解决了
    if(typeof action === 'function'){action(store.dispatch)

    }else{return next(action)

    }

}
正文完
 0