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

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)

    }

}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理