redux 源码分析

376 阅读5分钟

在写 react 的项目中, redux 经常被使用,所以明白 redux 的源码就显得很重要,虽然最近我看到有人推荐了其他的状态管理库,但是目前 redux 仍然是使用面最广的。

1. 从官方示例 todos 出发

我们先看 index.js 文件中内容:

import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'

const store = createStore(rootReducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

这里跟 redux 相关的就只有 createStore 函数,我们使用的时候知道,这个就是创建 store 。我们更新 store 中的数据可以通过 store.dispatch 函数来完成,我们知道这样只能修改 store 中的数据,实际上界面的刷新并非这个控制,所以对于我们来说要想更新界面,那么就需要与 react-redux ,而这个是另外的库,我们现在暂时先跳过,将来补上,我们就假定只要执行 store.dispatch 这个函数就更新。

我们先看这个 api 的说明,看看各个参数的含义:

这个示例就传入一个参数,就是 reducer ,这是个函数,函数接受两个参数,一个是 state ,也就是当前的状态;还有一个是 action ,就是下一步的行为,一般包含一个 type 属性,用来表明此时具体的行为。现在我们看看示例中传入的 rootReducer 是怎样编写的:

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
  todos,
  visibilityFilter
})

这里又出现一个新的 apicombineReducers 是将定义的多个 reducer 合并成一个,到时候看源码再具体说说,我们看其中一个 reducer

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        (todo.id === action.id)
          ? {...todo, completed: !todo.completed}
          : todo
      )
    default:
      return state
  }
}

export default todos

这里定义了两个行为,一个是 ADD_TODO ,是添加 todo 的;还有一个是 TOGGLE_TODO ,是切换 todo 完成状态的。现在我们看看是怎样更新数据的,也就是调用 dispatch 的地方:

dispatch({
  type: 'TOGGLE_TODO',
  id
})

我们知道上面的 action 也就是这里传入的值。

2. createStore

根据示例我们知道这个函数返回的是一个对象:

{
  dispatch: dispatch as Dispatch<A>,
  subscribe,
  getState,
  replaceReducer,
  [$$observable]: observable
}

2.1 store.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 是不是包含 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 ,保证总是只有一个 dispatch 进行时
    isDispatching = true
    // 拿着之前的 state 和传入的 action 得到当前的 state
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  // 通知所有的监听者
  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }
	// 返回当前 action ,如果有中间件这里的返回值就有用处了
  return action
}

我们发现非常简单就是更新 state 的情况下同时触发监听。

下面我们看一个更简单的,也就是 getState

2.2 store.getState

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

  return currentState as S
}

就是返回当前的 state ,如果更新了这个值就变成最新的那个值。我们上面看到当调用 dispatch 的时候就会触发监听。

2.3 store.subscribe

我们来看是怎么监听的,也就是 subscribe 函数的实现。

function subscribe(listener: () => void) {
  // 如果不是方法就报错
  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.'
    )
  }
	// 记录是否订阅,默认是 true
  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.'
      )
    }
  	// 取消订阅了就置为 false
    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    // 从数组中删除
    nextListeners.splice(index, 1)
    currentListeners = null
  }
}

现在我们已经明白 redux 的核心实现了,其他的一般很少使用。

2.4 原理流程图

所以我们再梳理梳理其核心原理。

跟网上的比起来,我新增了订阅器和触发,主要体现订阅的这一过程,所以 redux 很简单,就是我们大家熟知的观察者模式。

3. combineReducers

下面我们看一下上面遇到的 combineReducers 函数的实现:

function combineReducers(reducers: ReducersMapObject) {
  // 拿到所有的 key ,所以 key 的命令取决于这个
  const reducerKeys = Object.keys(reducers)
  // 保存对应值是方法的 reducer
  const finalReducers: ReducersMapObject = {}
  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]
    }
  }
  // 取出是方法的 key
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // keys multiple times.
  let unexpectedKeyCache: { [key: string]: true }
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError: unknown
  try {
    // 检查每个 reducer 返回的值是否正确
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
	// 返回合并的 reducer
  return function combination(
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    // 如果有错误就直接抛出错误
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

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

    let hasChanged = false
    // 保存新的 state
    const nextState: StateFromReducersMapObject<typeof reducers> = {}
    // 循环去更新每个 reducer 的值,所以 action.type 应该全局唯一,除非真的想同时更新多个
    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 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
      // 看看是否有改变,如果是 true 就就不用判断后面的了
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 这里主要是看看长度是否发生改变
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    // 如果有改变就返回新的,否则发返回之前的,这样就不会刷新了
    return hasChanged ? nextState : state
  }
}

4. compose

从右到左来组合多个函数。

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

这里的函数是后面的中间件的执行的结果传递给前面的中间件。比如你的数组为: [a, b, c, d, e] ,那么就相当于 a(b(c(d(e(...args))))) ,按道理是先执行 e(...args) ,当然实际上也是,只不过由于这些中间件返回的都是函数,所以最后回调实际结果为 a -> b -> c -> d -> e

5. applyMiddleware

使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。

参数

  • ...middleware (arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受 StoredispatchgetState 函数作为命名参数,并返回一个函数。该函数会被传入被称为 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。所以,middleware 的函数签名是 ({ getState, dispatch }) => next => action。

返回值

(Function) 一个应用了 middleware 后的 store enhancer。这个 store enhancer 的签名是 createStore => createStore,但是最简单的使用方法就是直接作为最后一个 enhancer 参数传递给 createStore() 函数。

function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return createStore =>
    <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)
      }
      // 给中间件传递 getState 和 dispatch ,这样中间件能方便拿到数据和进行 action
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      // 拿到最新的 dispatch 函数,新的 dispatch 执行才会执行中间件
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

      return {
        ...store,
        dispatch
      }
    }
}

到这里中间件也明白了,只不过看到这里还是不知道怎么自定义中间件,不用着急,我们写一个简单的中间件看看:

const createTestMiddleware = ({getState, dispatch}) => next => action => {
  // 这里就是自己发挥的空间了
	// 执行 next 传递 action
  return next(action)
}

这里的 next 在上一个中间件返回的结果,这里总是像:

action => {}

这样的函数,所以只有执行了 next(action) 这样的函数才会传递,不然中间件就会中断。而我们定义的函数 action => {} 就是中间件的 dispatch ,全部中间件的 dispatch 完成了就会执行 redux 自己定义的 dispatch 函数了;所以上面的 applyMiddleware 函数中传入的就是 store.dispatch