redux源码重读

67 阅读2分钟

唉,现在的内部分享都卷起来了,不弄个源码解读,都不好意拿出来讲

redux基本用法

import { createStore } from 'redux'

// 这是一个reducer
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

let store = createStore(counterReducer)

store.subscribe(() => console.log(store.getState()))

store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}

createStore

createStore 会生成唯一的store

// reducer如上文中的reducer 函数,preloadedState 初始状态,enhancer 是用来做扩展的
export default function createStore(reducer, preloadedState?, enhancer?) {
  // ...校验
  // 当前store中的reducer
  let currentReducer = reducer
  // 当前store中存储的状态
  let currentState = preloadedState as S
  // 当前store中放置的监听函数
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false
  
  // 获取state
  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
  }
  
  // 在每次订阅和取消订阅的时候,会让 nextListeners 和 currentListeners 不是同一个引用
  // 这里要是不明白,可看 ensureCanMutateNextListeners 的理解
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
  
  // 订阅
  function subscribe(listener: () => void) {
    // ...校验
    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
      // 从下一轮的监听函数数组(用于下一次dispatch)中删除这个监听器。
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
  
  // dispatch,想要改变state只有通过dispatch
  function dispatch(action: A) {
    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.`
      )
    }

    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.')
    }
    // 主要是预防的是在reducer中做dispatch操作,
    // 如果在reduder中做了dispatch,而dispatch又必然会导致reducer的调用,就会造成死循环。
    try {
      isDispatching = true
      // 更新 state
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 在每次 dispatch的时候,当 reducer执行完毕,订阅执行前,
    // 让 nextListeners 和 currentListeners 是一个引用 
    const listeners = (currentListeners = nextListeners)
    // 执行所有添加到store中的监听函数
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
  
  /**
   * replaceReducer是替换当前的reducer的函数,replaceReducer接受一个新的reducer,
   * 替换完成之后,会执行 dispatch({ type: ActionTypes.INIT }) ,用来初始化store的状态。
   * replaceReducer的三种使用场景
   *    当你的程序要进行代码分割的时候
   *    当你要动态的加载不同的reducer的时候
   *    当你要实现一个实时reloading机制的时候
   */
  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

    dispatch({ type: ActionTypes.REPLACE } as A)
    // change the type of the store by casting it to the new store
    return store
  }
  
  // 这个方法用于提供观察者模式的操作
  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) {

        function observeState() {
          const observerAsObserver = observer as Observer<S>
          // 观察者模式的链式结构,传入当前的state
          if (observerAsObserver.next) {
            observerAsObserver.next(getState())
          }
        }

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

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

ensureCanMutateNextListeners 的理解

这两个例子对照着看

let currentListeners = []
let nextListeners = currentListeners
function subscribe(fn) {
  nextListeners.push(fn)
}

function dispatch() {
  const listeners = currentListeners = nextListeners
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }
}

subscribe(() => {
  console.log('1')
  // 在订阅里面增加了订阅
  subscribe(() => {
    console.log('2')
  })
})

// 最后两个订阅都执行了,输出 1、2
// 这是因为在for循环的时候,中途改变数组的长度,循环的次数也会发生变化
dispatch() 
let currentListeners = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}
function subscribe(fn) {
  ensureCanMutateNextListeners()
  // 这时候 nextListeners 和 currentListeners 已经不相等了,因为不是同一个引用
  console.log(nextListeners === currentListeners) // false
  nextListeners.push(fn) 
}

function dispatch() {
  const listeners = currentListeners = nextListeners
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }
}

subscribe(() => {
  console.log('1')
  // 在订阅里面增加了订阅
  subscribe(() => {
    console.log('2')
  })
})

// 输出 1
dispatch() 

combineReducers

combineReducers把子reducer合并成一个总的reducer。

// 基本用法
let state = {
  counter: {
    count: 0
  },
  info: {
    name: '你好哇',
    description: '哈哈哈哈!'
  },
  info1: {
    name: '我不好',
    description: '嘿嘿嘿嘿!'
  }
}

/*counterReducer, 一个子reducer*/
/*注意:counterReducer 接收的 state 是 state.counter*/
function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return state;
  }
}


/*InfoReducer,一个子reducer*/
/*注意:InfoReducer 接收的 state 是 state.info*/
function InfoReducer(state, action) {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.name
      }
    case 'SET_DESCRIPTION':
      return {
        ...state,
        description: action.description
      }
    default:
      return state;
  }
}
// 注意,这里的counter和info要和state中的key相同
const reducer = combineReducers({
    counter: counterReducer,
    info: InfoReducer
});

combineReducers中没有info1对应的reducers,combineReducers返回的state就没有info1

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

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  // finalReducers是过滤后的reducers,确保它的每一个属性都是一个function
  const finalReducerKeys = Object.keys(finalReducers)

  // 返回合并后的新的reducer函数
  return function combination(state, action) {

    // 标志state是否有变化
    let hasChanged = false 
    const nextState: StateFromReducersMapObject<typeof reducers> = {}
    // 遍历执行所有的reducers,整合成为一个新的state
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 之前的 key 的 state
      const previousStateForKey = state[key]
      // 执行某一个reducer,获得新的state
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

compose

compose 的作用是把 [A, B, C] 转换成 A(B(C(next)))

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  
  if (funcs.length === 1) {
    return funcs[0]
  }
  
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

嵌套箭头函数

// redux官方文档middleware里面的示例代码
const loggerMiddleware = storeAPI => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', storeAPI.getState())
  return result
}
// 上面的代码可以翻译为下面的这段代码
function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}

其实箭头函数的嵌套,就是柯里化。对于上面的 loggerMiddleware 函数,它有 3 个箭头, 这个函数要被调用 3 次 loggerMiddleware (storeAPI)(next)(action),前两次调用只是在传递参数,只有最后依次调用才会返回 {xxx} 代码段的返回值

middleware

中间件就是对dispatch的扩展。比如现在有两个需求,记录日志和记录异常,现在通过重写store.dispatch 来实现。

// 第一步
const store = createStore(reducer);
const next = store.dispatch;

const loggerMiddleware = (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

store.dispatch = (action) => {
  try {
    console.log('exceptionMiddleware before', store.getState());
    loggerMiddleware(action);
    console.log('exceptionMiddleware after', store.getState());
  } catch (err) {
    console.error('错误报告: ', err)
  }
}

这样写,loggerMiddleware 写死了,如果再多个需求就没法变动了。所以现在需要把方法提取出来。

// 第二步
const store = createStore(reducer);
const next = store.dispatch;

const loggerMiddleware = (next) => (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

const exceptionMiddleware = (next) => (action) => {
  try {
    console.log('exceptionMiddleware before', store.getState());
    next(action);
    console.log('exceptionMiddleware after', store.getState());
  } catch (err) {
    console.error('错误报告: ', err)
  }
}

store.dispatch = exceptionMiddleware(loggerMiddleware(next));

发现loggerMiddleware 中还有store这个变量,所以进一步提取。

// 第三步
const store = createStore(reducer);
const next  = store.dispatch;

const loggerMiddleware = (store) => (next) => (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

const exceptionMiddleware = (store) => (next) => (action) => {
  try {
    console.log('exceptionMiddleware before', store.getState());
    next(action);
    console.log('exceptionMiddleware after', store.getState());
  } catch (err) {
    console.error('错误报告: ', err)
  }
}

const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
store.dispatch = exception(logger(next));

如果现在增加一个需求,在打印日志之前输出当前的时间戳

const timeMiddleware = (store) => (next) => (action) => {
  console.log('time', new Date().getTime());
  next(action);
}
...
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));

其实我们只需要知道这三个中间件,剩下的比如exception(time(logger(next))) 等可以封装起来。redux中是用applyMiddleware 来实现的,首先看下applyMiddleware的用法

/*接收旧的 createStore,返回新的 createStore*/
const newCreateStore = applyMiddleware(
  exceptionMiddleware,
  timeMiddleware,
  loggerMiddleware
)(createStore);

/*返回了一个 dispatch 被重写过的 store*/
const store = newCreateStore(reducer);
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState) => { // 返回重写后新的 createStore
      /*1. 生成store*/
      const store = createStore(reducer, preloadedState)
      let dispatch = () => {}

      const middlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }
      /*给每个 middleware 传下store,相当于 const logger = loggerMiddleware(store);*/
      /* const chain = [exception, time, logger]*/
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      /* 实现 exception(time((logger(dispatch))))*/
      dispatch = compose(...chain)(store.dispatch)
      
      // 返回store,用改造后的dispatch方法替换store中的dispatch
      return {
        ...store,
        dispatch
      }
    }
}

实际上applyMiddleware的用法是基于createStore的第三个参数enhancer

const middlewareEnhancer = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)
const store = createStore(rootReducer, {}, middlewareEnhancer)

// createStore 内部的enhancer
if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error(
      `Expected the enhancer to be a function. Instead, received: '${kindOf(
        enhancer
      )}'`
    )
  }

  return enhancer(createStore)(reducer, preloadedState)
}

bindActionCreators

bindActionCreators很少用得到。它是通过闭包,把dispatch 和 actionCreator 隐藏起来,让其他地方感知不到 redux 的存在。

// 核心的代码在这里,通过闭包隐藏了 actionCreator 和 dispatch
function bindActionCreator(actionCreator, dispatch) {
  return function (this, ...args) {
    return dispatch(actionCreator.apply(this, args))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  // actionCreators 必须是 function 或者 object
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, but instead received: '${kindOf(
        actionCreators
      )}'. ` +
        `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}


参考

juejin.cn/post/684490…

mp.weixin.qq.com/s/idWmfUbPV…