重读redux源码

之前5.1假期的时候,有去读了下redux的源码,当时感触很深,今天重新梳理遍redux的源码:
从源码中的src文件夹看起:


redux源码目录.png

从这里可以看出源码主要分为两大块,一块为自定义的工具库,另一块则是redux的逻辑代码。
上一次我是从createStore.js也就是redux的逻辑代码开始看的,那么这一次我从工具库开始看起:

utils

actionTypes.js

const randomString = () =>
  Math.random()
    // 转化成36进制的字符串
    .toString(36)
    .substring(7)
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes

这个文件返回了两个action的类型,两个类型的值都是通过一个随机数字转化成36进制的随机字符串拼接得到的。

isPlainObject.js

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

这个方法判断传入参数的原型是否等于Object.prototype换句话说,判断参数的父类是不是Object

warning.js

export default function warning(message) {
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  try {
    throw new Error(message)
  } catch (e) {}
}

就是打印错误信息,然后为了兼容ie8,做了下判断

逻辑代码

index.js

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

function isCrushed() {}
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

去除警告代码,我们可以看到index.js就是将我们熟悉的一些api导出了,这些api后面我会挨个重新看的哈~

但是__DO_NOT_USE__ActionTypes这个是什么?好像我们日常和文档里都没有见过这个东西呀!回到引用的地方可以看的就是我们之前分析过的actionTypes方法,里面定义了redux自带的action的类型,从这个变量的命名来看,这是帮助开发者检查不要使用redux自带的action的类型,以防出现错误。

createStore.js

接着往下看就是我们最长用的createStore.js文件啦,我们的源码分析也终于进入正餐了~

export default function createStore (reducer,preloadedState, enhancer) {}

函数createStore接受了三个参数(reducer、preloadedState、enhancer)

reducer就是我们常用的处理数据的纯函数,reducer会根据传入的state和action,返回新的state。

preloadedState就是初始状态

enhancer的意思是增强器,其实就是增强redux功能的函数。

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 当enhancer不为空且为函数时,就执行该函数,并return回去,作为creatStore的返回值。
    return enhancer(createStore)(reducer, preloadedState)
  }

这段代码重点在于enhancer(createStore)(reducer, preloadedState)
可以推出enhancer的结构

function enhancer(createStore) {
  return (reducer,preloadedState) => {
       //逻辑代码
      .......
  }
}

我们举个常见的传入中间件的例子:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

首先看下声明的变量:

  // 初始化数据
  let currentReducer = reducer //把 reducer 赋值给 currentReducer
  let currentState = preloadedState //把 preloadedState 赋值给 currentState
  let currentListeners = [] //当前订阅者列表
  let nextListeners = currentListeners  //新的订阅事件列表
  let isDispatching = false //标记正在进行dispatch

我们了解了具体的参数和变量,接着往下看createStore的对外接口:

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

从dispatch开始看,dispatch接受一个action作为参数

    function dispatch (action) {
      // action要求是一个简单对象,
      //  而一个简单对象就是指通过对象字面量和new Object()创建的对象,如果不是就报错。
      if (!isPlainObject(action)) {
        throw new Error(
          ....
        )
      }
      //  reducer内部是根据action的type属性来switch-case,
      // 决定用什么逻辑来计算state的,所以type属性是必须的。不传会报错
      if (typeof action.type === 'undefined') {
        throw new Error(
          '......'
        )
      }
      // 如果是已经在dispatch的,就报错,避免不一致
      if (isDispatching) {
        throw new Error('.....')
      }
      // 计算新的state,并赋值给currentState
      try {
        isDispatching = true
        // 根据reducer计算出新的state
        currentState = currentReducer(currentState, action)
      } finally {
        isDispatching = false
      }
      // state更新后,将subscribe中注册的监听器,回调触发一遍
      const listeners = (currentListeners = nextListeners)
      for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
      }
      // 返回当前的action
      return action
    }

分析下dispach的操作:

  1. 将isDispatching置为true
  2. 根据reducer计算出新的state
  3. 得到新的state值,isDispatching置回false
  4. 一一通知订阅者state数据已经更新了

getState

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.'
      )
    }
    //返回当前的state tree
    return currentState
  }

这里就可以看出来getState是直接返回currentState,如果我们直接修改state的话,就不会通知订阅者做更新了。

subscribe

  function subscribe (listener) {
    // listener是state变化时的回调,必须是个函数
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
    // 如果是正在dispatch中,就报错。因为要确保state变化时,
    // 监听器的队列也必须是最新的。所以监听器的注册要在计算新的state之前。
    if (isDispatching) {
      if (isDispatching) {
        throw new Error(
          ......
        )
      }
      //标记有订阅的 listener
      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)
      }
    }
  1. 首先标记正在订阅
  2. 保存了一份快照
 function ensureCanMutateNextListeners() {
    // 如果nextListeners和currentListeners是否为同一个引用,那么就复制一份,做一个浅拷贝
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

其实我们曾经在dispatch中做了const listeners = (currentListeners = nextListeners)的操作,那为什么这里又要去给他做浅拷贝呢?
因为有这么一种的情况存在。当redux在通知所有订阅者的时候,此时又有一个新的订阅者加进来了。如果只用currentListeners的话,当新的订阅者插进来的时候,就会打乱原有的顺序,从而引发一些严重的问题。

  1. 将新的订阅者加入nextListeners中
  2. 返回一个取消订阅的函数

replaceReducer

 function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }

这个方法将reducer替换成传入的参数,并且触发一次state更新操作。

combineReducers.js

因为createStore只接受一个reducer,这个方法主要用来整合reducer。
第一步:检验用户传入的reducers的准确性

// 把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]
  }
}
//把 finalReducers 对象中可枚举的属性转换成一个数组
const finalReducerKeys = Object.keys(finalReducers)

对传入的reducer做了过滤操作,过滤掉不是函数的部分,然后用定义的finalReducers和finalReducerKeys分别存放拷贝后的reducers和其属性。

第二步:调用assertReducerShape方法

function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
  const reducer = reducers[key]
  const initialState = reducer(undefined, { type: ActionTypes.INIT })

  if (typeof initialState === 'undefined') {
    throw new Error(
      `....`
    )
  }

  if (
    typeof reducer(undefined, {
      type: ActionTypes.PROBE_UNKNOWN_ACTION()
    }) === 'undefined'
  ) {
    throw new Error(
      `......`
    )
  }
})
}

主要用来判断每个reducer是否都有默认返回值

第三步:返回了一个函数,这个函数接收 state 和action
也就是之前creteStore中调用的reducer

currentReducer(currentState, action)
return function combination(state = {}, action) {
    
    ......
    // 定义了一个hasChanged变量用来表示state是否发生变化
    let hasChanged = false
    const nextState = {}
     //循环遍历 finalReducerKeys ,执行所有的 reducer, 所以一定不要有相同的 action.type ,否则状态一定会混乱的
    for (let i = 0; i < finalReducerKeys.length; i++) {
      //获取当前的 key
      const key = finalReducerKeys[i]
      //获取当前 reducer
      const reducer = finalReducers[key]
      //获取当前 key 的 state
      const previousStateForKey = state[key]
      //执行 reducer ,获取 state
      const nextStateForKey = reducer(previousStateForKey, action)
      //判断执行完Reducer后, 返回回来的 nextStateForKey 是 undefined
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      //赋值给 nextState
      nextState[key] = nextStateForKey
      //判断 state 是否经过 Reducer 改变了(判断地址是否改变)
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 改变了的话,返回新的state否则返回之前的state
    return hasChanged ? nextState : state
  }

从这里
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
我们可以看出来,reducer为什么一定要是个纯函数。

compose

当需要使用多个redux中间件 依次执行的时候,需要用到它。

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

这段代码主要重点在reduce,reduce接收一个回调函数,那我们把这段代码中的箭头函数先去掉:

function compose(...funcs) {
  return funcs.reduce(function (total, currentVal) {
      return function () {
          return a(b.apply(undefined, arguments));
      }
  })
}

也就是说如果传入a,b两个参数

function compose(a, b) {
  return a(b())
  }

所以compose的执行顺序是从右向左。

applyMiddleware(...middlewares)

Middleware 主要是包装storedispatch方法。可以同时传入多个middleWare组合到一起使用,形成 middleware链。每个middleware接受StoredispatchgetState 函数作为命名参数,并返回一个函数。

这是文档中的Middleware的解释,之前在讲createStore的时候,讲到了enhancer的结构

function enhancer(createStore) {
  return (reducer,preloadedState) => {
       //逻辑代码
      .......
  }
}

enhancer 实际上就是applyMiddleware(...middlewares)的返回值。
我们继续看applyMiddleware的源码:

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args) // 原始的store 此时的dispatch 就是原始的dispatch
    // 声明一个dispatch
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    // 
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 组合运算
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 在中间件中调用dispatch的时候, 其实就是调用了这个dispatch, 而不是一开始声明的逻辑
    // 这个dispatch是已经经过compose的包装的函数
    // const composeRes = compose(...chain);
    // 第二行通过传入store.dispatch,
    // 这个store.dispatch就是最后一个 next => action => {}的next参数
    // 所以中间件其实就是改装dispatch
    // dispatch作为有中间件的store的dispatch属性输出
    //  当用户调用dispatch时, 中间件就会一个一个
    // 执行完逻辑后, 将执行权给下一个, 直到原始的store.dispacth, 最后计算出新的state
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

第一步:通过传入的createStore创建了一个store

第二步:声明了一个原始的dispatch,如果在中间件的调用过程中出现了错误,则抛出错误

第三步:定义middlewareAPI,有两个方法,一个是getState,另一个是dispatch,将其作为每个中间件调用的store的参数,整合出一个chain

第四步:通过compose的包装chain,并赋值给dispatch

第五步:将新的dispatch替换原先的store.dispatch

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