Redux源码阅读(一)——createStore、dispatch、subscribe

Redux源码阅读(一)——createStore、dispatch、subscribe_第1张图片
Redux单向数据流

简述

目的

Redux是一个用于更好地在组件外部管理状态的东西。通过使用它,我们可以轻松地更新或者获取state而不需要关心当state发生变动之后,触发页面的渲染。
通过看源码,来弄清楚Redux究竟是怎么管理state的。这会给日常的编码带来一些启发。

准备

仓库地址: https://github.com/reduxjs/react-redux

  • 修改rollup.config.js文件,给里面的所有outputsourcemap选项,比如这样
    (有个小知识,rollup比webpack更适合打包这种小型的js库,所以看一些结构和功能更复杂的库的时候他们用的是webpack。)
output: {
      file: 'lib/redux.js',
      format: 'cjs',
      indent: false,
      sourcemap: true
    },
  • 就可以build出有.map文件的redux了


    打包产物
  • 然后去examples里面找项目引用这个js
  • http-server启动项目,我找的是counter-vanilla这个项目。它是一个简单的用原生js写的页面。
    突然有个想法,如果用jquery去写项目,用redux来管理数据和页面渲染好像也ok。
  • 在浏览器调试起来


    counter-vanilla

Redux.createStore

 function createStore(
  reducer: Reducer,
  preloadedState?: PreloadedState | StoreEnhancer,
  enhancer?: StoreEnhancer
){
……

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


  function subscribe(listener: () => void) {
      ……
  }


  function dispatch(action: A) {
  ……
  }


  function replaceReducer(
    nextReducer: Reducer
  ): Store, 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() {
    ……
  }

  // 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,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  } as unknown) as Store, A, StateExt, Ext> & Ext
  return store
}
  • 这里就是定义一些会被store暴露出来的方法和这些方法公用的一些变量,比如currentReducer这种。
  • 并且调用了一次dispatch来获取默认的state。也就是我们传入的那个reducer默认return的那个对象。
  • createStore函数的最后,return了挂着这些方法的store对象出来。

store.subscribe

调试代码

  function render() {
    valueEl.innerHTML = store.getState().count.toString()
  }

  render()
  store.subscribe(render)

源码

  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false

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

  function subscribe(listener: () => void) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    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()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
  • subscribe接受一个函数称为listener,然后对nextListeners数组去添加这个函数。
  • 特别地,在push之前调用了ensureCanMutateNextListeners
    这个函数的作用是通过slice获取currentListeners的副本赋值给nextListeners,新的listener是添加进这个array而不是当前的currentListeners
    这可以避免当消费者(consumer)dispatch的时候调用subscribe而引发问题。
  • 之后return了unsubscribe,用于注销这个listener。实现也很一目了然,即修改nextListeners。

store.dispatch

  function dispatch(action: A) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      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
  }
  • 执行currentReducer之前,将isDispatching置为true。这个标志位为true会阻止调用getStatedispatchsubscribeunsubscribe等方法来避免一些可能的问题。
  • 执行currentReducer之后,再遍历执行nextListeners里的函数,就是subscribe注册的那些。

总结

第一节总结

同系列:
Redux源码阅读(二)——combineReducers
Redux源码阅读(三)——connect

你可能感兴趣的:(Redux源码阅读(一)——createStore、dispatch、subscribe)