redux源码阅读

Redux 是可预测的状态管理框架,它很好的解决多交互,多数据源的诉求。

三大原则:

单一数据源:
整个应用的state被存储在一颗object tree中,并且这颗 object tree存在于唯一的store里,store就是存储数据的容器,容器里面会维护整个应用的state。store提供四个API:dispatch subscribe getState replaceReducer。 数据源是唯一的,所以获取数据的唯一方式就是store.getState()。
state只读:
根据state只读原则,state的变更要通过store的dispatch(action)方法,action就是变更数据的载体,action = {type: '', payload},type是变更数据的唯一标志, payload是需要变更的数据。
使用纯函数变更state
reducer是纯函数,接受两个参数即 state和action, 根据action的type属性执行对应的代码,更改相应的数据,然后返回新的state。

完整的数据流过程:

view层触发actionCreator, actionCreator通过store.dispatch(action) ,reducer执行对应的代码,返回新的state,更新页面。


image.png

源码结构:

src
├── utils ---------------------------------------- 工具函数
├── applyMiddleware.js --------------------------- 加载 middleware
├── bindActionCreators.js ------------------------ 生成将 action creator 包裹在 dispatch 里的函数
├── combineReducers.js --------------------------- 合并 reducer 函数
├── compose.js ----------------------------------- 组合函数
├── createStore.js ------------------------------- 创建一个 Redux store 来储存应用中所有的 state
├── index.js ------------------------------------- 入口 js

index.js

index.js文件是整个代码的入口,该文件暴露了一些接口供开发者使用: createStore, combineReducers, bindActionCreators, applyMiddleware, compose,接下来逐个分析这些接口。

createStore

createStore是redux最重要的API,它创建一个store,保存应用中的state。
createStore函数接受三个参数:reducer, preloadedState, enhancer,
返回值:dispatch、subscribe、getState、replaceReducer 和 [$$observable],这就是我们开发中主要使用的几个接口。

参数

reducer: 函数,返回下一个状态,接收state和action作为参数
preloadedState: state初始值,可以忽略不传
enhancer:store的增强器,一般是第三方中间件,返回一个增强后的store creator,这个函数通常由applyMiddleware函数来生成。

返回值

dispatch

function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }
    // 判断 action 是否有 type{必须} 属性
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }
    // 如果正在 dispatch 则抛出错误
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    // 对抛出 error 的兼容,但是无论如何都会继续执行 isDispatching = false 的操作
    try {
      isDispatching = true
      // 使用 currentReducer 来操作传入 当前状态和 action,返回处理后的状态
      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
  }

dispatch函数是触发动作的函数,接收action行为作为参数,action必须是一个对象,且该对象必须有type参数,如果action满足条件,则调用currentReducer(其实就是createStore的参数 reducer的引用),currentReducer会根据action来改变对应的值,生成新的store,然后再遍历nextListeners列表,调用每一个监听函数。

getState

// 读取由 store 管理的状态树
function getState() {
  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
}

这个函数可以获取当前的状态,createStore 中的 currentState 储存当前的状态树,这是一个闭包,这个参数会持久存在,并且所有的操作状态都是改变这个引用,getState 函数返回当前的 currentState。

subscribe

function subscribe(listener) {
  // 判断传入的参数是否为函数
  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-reference/store#subscribe(listener) 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-reference/store#subscribe(listener) for more details.'
      )
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

除去一些边界条件的判断,subscribe函数最主要的是给store状态添加监听函数,该函数接收一个函数作为参数,会往nextListeners监听列表加入这个函数,然后会返回一个unsubscribe函数,用于解绑,如果解绑,就从nextListeners列表中去掉该函数,一旦调用dispatch改变store,监听函数就会全部执行。

combineReducers

reducer是管理state的一个模块,在应用初始化的时候,它返回initialState,当用户调用action时,它会根据action的type属性,进行相应的更新,reducer是纯函数,不会更改传入的state,会返回新的state。
当所有的reducer逻辑都写在同一个reducer函数里面会非常的庞大,所以我们会将reducer进行适当的拆分,但是最终传入createStore里面的是唯一的reducer函数,所以我们传入createStore前要进行合并,就需要combineReducers方法。

参数
reducers (Object): 一个对象,它的值(value)对应不同的 reducer 函数,这些 reducer 函数后面会被合并成一个。

返回值
(Function): 它是真正 createStore 函数的 reducer,接受一个初始化状态和一个 action 参数;每次调用的时候会去遍历 finalReducer(有效的 reducer 列表),然后调用列表中每个 reducer,最终构造一个与 reducers 对象结构相同的 state 对象。

combineReducers用法

rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// This would produce the following state object
{
  potato: {
    // ... potatoes, and other state managed by the potatoReducer ...
  },
  tomato: {
    // ... tomatoes, and other state managed by the tomatoReducer, maybe some nice sauce? ...
  }
}

源码

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

   /* if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }*/

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  /*let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }*/

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

combineReducer函数返回一个combination 函数,该函数接收state和action作为参数,每次调用该函数时:

1、 for (let i = 0; i < finalReducerKeys.length; i++) { ... }:遍历 finalReducer(有效的 reducer 列表);
2、 var previousStateForKey = state[key]:当前遍历项的之前状态,看到这里就应该明白传入的 reducers 组合为什么 key 要和 store 里面的 state 的 key 相对应了;
3、 var nextStateForKey = reducer(previousStateForKey, action):当前遍历项的下一个状态;
4、 nextState[key] = nextStateForKey:将 当前遍历项的下一个状态添加到 nextState;
5、 hasChanged = hasChanged || nextStateForKey !== previousStateForKey:判断状态是否改变;
6、 return hasChanged ? nextState : state:如果没有改变就返回原有状态,如果改变了就返回新生成的状态对象。

applyMiddleware

bindActionCreators

  1. actionCreator创建动作
    在分析之前先明确ActionCreator是什么?ActionCreator是动作创造者或者说是动作工厂,如果我们想根据不同的参数来生成不同值的计数器,例子如下:
  const counterActionCreator = (step) => {
    return {
      type: 'increment',
      step:  step || 1,
    }
  }

2.bindActionCreator
从上述例子出发,如果我们想生成不同的计数器,并分发他们,则需要按照以下写法:

const action1 = counterActionCreator();
dispatch(action1);

const action2 = counterActionCreator(2);
dispatch(action2);

const action3 = counterActionCreator(3);
dispatch(action3);

从上面的写法来看,每次都需要去调用actionCreator,再调用dispatch分发action,会产生很多繁杂重复的代码,所以我们可以采用 bindActionCreators.js 文件里面的bindActionCreator 方法来优化代码,

bindActionCreator 源码:
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

bindActionCreator会返回一个函数,函数中会调用actionCreator生成action,并将action作为dispatch的参数分发掉,所以我们将action的生成动作和调用动作封装到了一个函数里面,我们直接调用bindActionCreator返回的函数就行,不用再每次去调用actionCreator,再调用dispatch分发action,例子如下:

const increment = bindActionCreator(counterActionCreator, dispatch);

increment();
increment(2);
increment(3);
  1. bindActionCreators
    接下来看bindActionCreators函数,其实是对bindActionCreator的增强,去掉一些判断条件,源码部分具体如下:
function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') { // #1
    return bindActionCreator(actionCreators, dispatch) // #2
  }

  ....

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') { // #3
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

当actionCreators是一个函数时,直接调用bindActionCreator将整个过程封装返回即可
当actionCreators是一个有多个函数方法组成的对象时,遍历该对象,如果键对应的值是函数,则调用bindActionCreator将整个过程封装,并将封装结果赋值给该键,最后返回一个对象,对象里面的值都被bindActionCreator封装过。
actionCreators为对象时的例子如下:

const actionCreators = {
  increment: function(step) {
    return {
      type: 'INCREMENT',
      step: step || 1
    }
  },

  decrement: function(step) {
    return {
      type: 'DECREMENT',
      step: - (step || 1)
    }
  }
}

const newActionCreators = bindActionCreators(MyActionCreators, dispatch)

newActionCreators.increment();
newActionCreators.increment(1);

newActionCreators.decrement();
newActionCreators.decrement(1);

你可能感兴趣的:(redux源码阅读)