认识 redux

472 阅读6分钟

作为一个刚入门的码农来说,开始接触 redux 时那兴奋感,现在想想都令人振奋,好吧,初学者对于啥都感觉兴奋,计算机的世界总是那么给人神秘,令人神往,最后让人伤痕累累。不过那会儿是真的不容许自己把它扒光了看,再说,那会儿能把它扒掉几层皮啊,就算扒光了,凭我这捉急的智商,咱也看不懂哇。好吧,管它四八三十六干就得了,毕竟拿到工资才能买5毛钱的萝卜丝饼填肚子啊。是不是惨的不像话。这也算惨?实际情况是当我拿到那5毛钱工资时才发现,萝卜丝饼涨价了,萝卜丝饼涨价了,萝卜丝饼涨价了(重要的事情说三遍)...好吧,继续码代码吧,隔壁小卖部那一块钱一瓶的冰汽水仍然是我遥不可及的梦想...

虽然本人已经将近一年没有用 redux 了,但是还是觉得 redux 更适合自己,可能 redux 的 logo 比 mobx 的更炫酷吧

哈哈,玩笑玩笑...

话不多说,直接开始手撸 redux 源码。基于 v4.0.4 版本

源码解读

先来看下 redux 给我们提供了哪些 api

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

接下来我们一一介绍

createStore

export default function createStore(reducer, preloadedState, enhancer) {
  // 如果 preloadedState 和 enhancer 都为 function 的情况,抛错
  // 必须符合 preloadedState 为 object, enhancer 为 function
  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.'
    )
  }
  // preloadedState 为 function,enhancer 为 undefined 的时候说明 initState 没有初始化, 但是有 middleware
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
  // 如果 enhancer 存在
  if (typeof enhancer !== 'undefined') {
    // enhancer 不是 function,抛错
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 这段代码打动了我,但是目前我并不懂它内部的逻辑,到底是个什么神操作
    // 好吧,现在先略过吧,只知道做了一些处理,我们先看下去,之后再解释
    return enhancer(createStore)(reducer, preloadedState)
  }
  // 如果 reducer 不是 function,抛错
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer          // 当前 reducer
  let currentState = preloadedState     // 当前 state
  let currentListeners = []             // 取名为 listeners,又是数组,基本是个监听器
  let nextListeners = currentListeners  // 做浅拷贝
  let isDispatching = false             // 是否正在执行 dispatching

  // 保存一份订阅的快照,我们可以在调度时将 nextListeners 用作临时列表。
  // 可以防止使用者在调度过程中调用订阅/取消订阅的任何错误。
  function ensureCanMutateNextListeners() {
    // 判断 nextListeners 和 currentListeners 是不是同一个引用
    if (nextListeners === currentListeners) {
      // 如果是,转换引用地址(浅层深拷贝)
      nextListeners = currentListeners.slice()
    }
  }

  // 获取当前 state
  function getState() {
    // 阻止在 dispatching 过程中执行操作,否则抛错(因为要确保生成最新的 state)
    if (isDispatching) {
      // ...
    }

    // 为什么是最新的 state,参考 subscribe
    return currentState
  }

  // 设置订阅器,一旦执行 dispatch 就会触发这个订阅器执行
  // listener 是一个 callback
  function subscribe(listener) {
    // listener 必须是一个 function,否则抛错
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    // 同上
    if (isDispatching) {
      // ...
    }

    // 一个是否已订阅的标记,通过下面代码可以知道,其实就是是否有 listener
    let isSubscribed = true

    // 保存一份 nextListeners 的快照
    ensureCanMutateNextListeners()
    // 添加一个订阅函数
    nextListeners.push(listener)

    // 返回取消的 function,用于在组件卸载时移除订阅器
    return function unsubscribe() {
      // 用于确定是否有 listener
      if (!isSubscribed) {
        // 如果没有,直接返回
        return
      }

      // 同上
      if (isDispatching) {
        // ...
      }

      // 是否已订阅标记为 false
      isSubscribed = false

      // 保存订阅快照
      ensureCanMutateNextListeners()
      // 找到当前监听器的位置
      const index = nextListeners.indexOf(listener)
      // 从 nextListeners 中删除(也就是说 subscribe 每次触发都会保存一份含有当前监听器的 nextListeners 列表,结束后被删除)
      nextListeners.splice(index, 1)
    }
  }

  // dispatch action
  function dispatch(action) {
    // action 必须是一个 object,否则抛错
    if (!isPlainObject(action)) {
      // ...
    }

    // action 必须有 type 属性,否则抛错
    if (typeof action.type === 'undefined') {
      // ...
    }

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

    try {
      // dispatch 时标记 isDispatch 为 true,阻止同时执行其它任何操作
      isDispatching = true
      // 执行 reducer
      // 这就是改变 store state 的唯一途径
      currentState = currentReducer(currentState, action)
    } finally {
      // dispatch 过后,标记 isDispatching 为 false
      isDispatching = false
    }

    // 这里拿到所有监听器
    const listeners = (currentListeners = nextListeners)
    // 执行每一个监听器
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    // 返回传入的 action
    return action
  }

  // 这个方法其实在看 redux 源码前我根本不知道,官网好像也没有特别重点介绍么
  // 看名字基本是替换更新 state 的 reducer
  // redux 热加载机制的时候用到了
  function replaceReducer(nextReducer) {
    // 类型判断,不过多解释,reducer 必须是 function
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    // 替换当前 reducer 为 nextReducer 
    currentReducer = nextReducer

    // ActionTypes 的三个属性类似,只不过名称不同
    // 这里应该就是执行 replaceReducer 这个方法时发送一个 dispatch,表明一下是 @@redux/REPLACE
    dispatch({ type: ActionTypes.REPLACE })
  }

  // 应该是可变的?观察?
  // 好吧,凭我这捉急的智商,还是不解释这里,感觉会被我带跑偏啊
  // 希望有能之士帮忙解释下,在此先谢谢了
  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

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

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

  // 当一个 redux store 被创建时,都会自动 dispatch 一个 "@@redux/INIT" 的 action
  // 从而使 reducer 拥有初始 state 值
  // 看到这里,感慨下,为啥我以前就没有想过 reducer 的初始值是怎么来的呢?
  // 好吧,凭我这智商,就算想过这个问题,应该也想不到这么简单就能实现
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

combineReducers

// 用于合并多个 reducer。一般用法:combineReducers({ a, b, c... })
export default function combineReducers(reducers) {
  // 获取 reducers 中的所有 key
  const reducerKeys = Object.keys(reducers)
  // 最终 reducer's keys
  const finalReducers = {}
  // 遍历传入的 reducers 的所有 key
  // 目的是过滤不符合规范的 reducer
  for (let i = 0; i < reducerKeys.length; i++) {
    // 获取每个 key
    const key = reducerKeys[i]

    // 除生产环境外,key 没有提供 reducer 都会给警告
    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    // 一切 ok 的情况下,给 finalReducers 赋值
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  // 获取所有符合规范的 reducer 的 key
  const finalReducerKeys = Object.keys(finalReducers)

  // 这用于确保我们不会多次警告相同的键,先往下看
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  // reducer 断言
  let shapeAssertionError
  try {
    // reducer 的标准规范:(previousState, action) => newState
    // 规范 reducer,如果有不符合规范的 reducer,则抛错
    assertReducerShape(finalReducers)
  } catch (e) {
    // 断言失败,赋值
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    // reducers 断言失败的话,抛错
    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++) {
      // 获取 finalReducers 的 key 和 value
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 当前 key 的 state
      const previousStateForKey = state[key]
      // 执行 reducer,返回新的 state
      const nextStateForKey = reducer(previousStateForKey, action)
      // 如果没有返回新的 state,则抛错
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 把新的 state 放入 nextState 对应的 key 里面
      nextState[key] = nextStateForKey
      // 判断新的 state 和 前一个 state 是不是同一个引用,以检验 reducer 是不是纯函数
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

bindActionCreators

// 返回一个注入 store dispatch 的 action creator 方法
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}
// 官网是这样介绍 bindActionCreators。大家可以仔细品味
// 把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。
// 一般情况下你可以直接在 Store 实例上调用 dispatch。如果你在 React 中使用 Redux,react-redux 会提供 dispatch 函数让你直接调用它 。
// 惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。
export default function bindActionCreators(actionCreators, dispatch) {
  // 这里说明你也可以传入一个函数作为第一个参数,它会返回一个函数
  if (typeof actionCreators === 'function') {
    // 返回一个注入 store 的 dispatch 方法的 action creator
    return bindActionCreator(actionCreators, dispatch)
  }

  // 类型检查,如果 actionCreators 不是一个对象,则抛错
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(/** error */)
  }

  // 定义最终返回的 boundActionCreators
  const boundActionCreators = {}
  // 遍历 actionCreators
  for (const key in actionCreators) {
    // 获取当前 key 对应的 actionCreator
    const actionCreator = actionCreators[key]
    // 如果 actionCreator 是一个 function,则给 boundActionCreators 对应的 key 赋值一个注入 store 的 dispatch 方法的 action creator
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  // 返回给每个 action creator 绑定了 dispatch 方法的 boundActionCreators 对象
  return boundActionCreators
}

compose

一个组合函数的函数,将函数串联起来从右到左执行

// 注意,因为compose的执行顺序原因,所以有的 middleware 插件会要求要放在最后面
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)))
}

applyMiddleware

// 添加中间件,看下官网介绍

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

  // 还记得在 createStore.js 中的一段代码吗?
  /**
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 这段代码打动了我,但是目前我并不懂它内部的逻辑,到底是个什么神操作
    // 好吧,现在先略过吧,只知道做了一些处理,我们先看下去,之后再解释
    return enhancer(createStore)(reducer, preloadedState)
  }
  */
  // 有 applyMiddleware 的时候直接先执行这里
  // 到这里也就可以解释 enhancer(createStore)(reducer, preloadedState) 这段代码了
  
  return createStore => (...args) => {
    const store = createStore(...args)
    // 定义一个 dispatch,调用会报错。
    // 原因:dispatching 虽然构造middleware但不允许其他middleware应用
    let dispatch = () => {
      throw new Error(/** error */)
    }

    // 定义 middleware 中的 api
    const middlewareAPI = {
      // 注入 store 的 getState 方法
      getState: store.getState,
      // 调用每一个这样形式的 middleware = store => next => action =>{}, 
      // 组成一个这样 [f(next)=>acticon=>next(action)...] 的 array,赋值给 chain
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose(...chain) 会形成一个调用链, next 指代下一个函数的注册, 这就是中间件的返回值要是 next(action) 的原因
    // 如果执行到了最后 next 就是原生的 store.dispatch 方法
    dispatch = compose(...chain)(store.dispatch)

    // 返回增强的 store
    return {
      ...store,
      dispatch
    }
  }
}

写在最后

redux 确实非常感人啊,代码精简易懂,看完之后确实收获满满~~~