redux 源码阅读

【目录结构】

image-20190827201059192

Redux 源码可以在任意项目中的 node_modules 文件夹下的 redux 中找到。我们阅读学习中主要关注 src 即可。

src 下主要分成两个部分, 一部分是 utils 工具库, 一部分是 redux 逻辑代码。

【utils】

Redux 自定义的工具库

下属对应三个文件

  • actionTypes.js
  • isPlainObject.js
  • warning.js

actionTypes.js

源码如下:

const randomString = () =>
  Math.random()
    .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 类型,比较好理解。

其中的 randomString 方法用来获取指定长度的随机字符串, 这里有个很多同学都会忽略掉的知识点, Number.prototype.toString 方法 接受一个可选参数 radix ,该参数代表数字的基数, 也就是我们常数的二进制、八进制等, 默认为 10, 范围在 2~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
}

这个文件对外暴露一个用来判断是否为简单对象的方法。

简单对象

凡不是new Object()或者字面量的方式构建出来的对象都不是简单对象

就是该对象的 __proto__ 等于 Object.prototype

举?, 像 正则, 日期,类, 函数, 都不是简单对象

具体的相关知识点,可以去看下原型链的相关知识。

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

这个文件也是很简单只是用来打印一下错误信息, 这里对 console.error 加了层判断处理, 用于处理兼容性问题。原因是 ie8及其以下都是不支持 console

【逻辑代码】

下属文件如下:

  • applyMiddleware.js
  • bindActionCreators.js
  • combineReducers.js
  • compose.js
  • createStore.js
  • index.js

我们首先从入口文件开始阅读:

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

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}

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

这里的入口文件主要的工作是把其余的几个文件做一个统一导出, 需要关注的地方有两点

  1. __DO_NOT_USE__ActionTypes 。 通过导入路径可以得知,引用自之前 untils 中的 actionTypes.js 中,平时工作中也用不到,官方文档也未做解释,暂且放下不管,以后再研究 。。。

  2. 函数 isCrushed 从字面理解,这是函数是用来判断是否是碎的, 但函数体中却什么都没有,很是奇怪,带着这个疑问往下看这个 if 语句,在这里给出了答案,

    1. process.env.NODE_ENV !== 'production'
    2. typeof isCrushed.name === 'string'
    3. isCrushed.name !== 'isCrushed'

    这三个条件从字面上也非常好理解。 当三个条件都不满足时 执行 warning 抛出异常信息。信息内容翻译如下:

    “您目前正在使用NODE_ENV =''production'之外的缩小代码。”+
    '这意味着你正在运行一个较慢的Redux开发版本。 '+
    '你可以使用loose-envify(https://github.com/zertosh/loose-envify)进行浏览器化'+
    '或者为Webpack定义插件(http://stackoverflow.com/questions/30030031)'+
    '以确保您拥有正确的生产构建代码。

    可能有同学有对于前两个条件还好理解, 对第三个条件会感到有些疑惑, 觉得没有必要。这里了解过些打包原理的同学应该对这个判断还是比较好理解的。

    create-react-app 为例: 项目代码在生产环境下会对代码内容进行一个深度压缩,会将所有的变量名替换成 a, b, c 之类的字母, 所以当进行生成环境编译后 函数 isCrushed 可以就变成了 函数 i

    这个函数的主要作用就是防止开发者在开发环境下对代码进行压缩, 影响调试

createStore.js

源码如下:

import $$observable from 'symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

export default function createStore(reducer, preloadedState, enhancer) {
  
  // 参数处理
  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'
    )
  }

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

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

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
    
  // 定义变量
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 定义方法
  function ensureCanMutateNextListeners() {
    ...
  }

  function getState() {
    ...
  }

  function subscribe(listener) {
    ...
  }

  function dispatch(action) {
    ...
  }

  function replaceReducer(nextReducer) {
    ...
  }
    
  dispatch({ type: ActionTypes.INIT })

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

函数 createStore 创建一个 Redux store 来以储存应用中所有的 state。应用用应有且仅有一个 store。

createStore(reducer, [preloadedState], enhancer)

返回的接口分别是 dispathc subscribe getState replaceReducer[$$observable]

参数

  1. reducer: function 接收两个参数, 分别是当前的 state 树和要处理的 action, 返回新的 state 树。

  2. [preloadedState]: any 初始时的 state, 如果只有两个参数,并且第二个参数是个函数时,将此参数赋值为第三个参数, 并忽略此参数

    所以我们在使用时可以不传第二个参数,直接将第三个参数书写到第二个参数的位置 同样可以正常使用

  3. enhancer: func enhaner 增强器 是一个组合 stroe creator 的高阶函数,返回一个新的强化后的 store creator。 通常可以理解为中间件,同时它也允许你通过复合函数改变 store 接口。

    注意: 当存在多个参数时,且从第三个参数开始类型同为 函数, createStroe 将默认理解你想要使用多个 增强器, 这里会抛出异常提示你需要将多个增强器合并成一个函数传入。

    常见的 enhancer 有 redux-thunk 以及 redux-sage。一般都会配合 applyMiddleware 一起使用, 其作用就是将这些 enhancer 格式化成符合 redux 要求的 enhancer。

    举个?:

    import {createStore, applyMiddleware} from 'redux'
    import thunk from 'redux-thunk'
    import logger from 'redux-logger'
    
    import reducer from './reducers'
    
    const store = createStore(reducer, applyMiddleware(thunk))
    
    export default store

变量

let currentState = preloadedState //从函数createStore第二个参数preloadedState获得
let currentReducer = reducer  //从函数createStore第一个参数reducer获得
let currentListeners = [] //当前订阅者列表
let nextListeners = currentListeners //新的订阅者列表
let isDispatching = false

其中的 isDispatching 是作为锁来用的, 我们都知道 redux 是一个统一的状态管理容器。所以同一时间里我们必须保证只要一个action在执行

ensureCanMutateNextListeners

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

确保 nextListeners 与 currentListeners 不存在同一个引用关系, 主要服务于 dispatch 与订阅。

dispatch

function dispatch(action) {
  if (!isPlainObject(action)) {
    throw new Error(
      'Actions must be plain objects. ' +
      'Use custom middleware for async actions.'
    )
  }

  if (typeof action.type === 'undefined') {
    throw new Error(
      'Actions may not have an undefined "type" property. ' +
      'Have you misspelled a constant?'
    )
  }

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

  try {
    isDispatching = true
    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 方法中做了三重判断,

1. action 必须是个简单函数; 
2. action.type 存在;
3.  未锁住状态。 

只有当三重判断都通过时, 才往下执行 action 并进行上锁

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

  return currentState
}

getState 非常简单, 返回 currentState 即可。这个 currentState 在每次 dispatch 的时候都会进行更新, 同dipatch 一样在进行 reducer 操作的时候, 是不可以读取到当前 state 里的值的。

提问: 执行 createStore 函数生成 store, 可不可以直接修改它的 state?

答案是: 可以的, 但redux 不允许这么去做, 因为这样不会通知到订阅者重新更新数据。

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

这个函数可以给 store 的状态添加订阅监听函数,一旦调用 dispatch ,所有的监听函数就会执行;
nextListeners 就是储存当前监听函数的列表,调用 subscribe,传入一个函数作为参数,那么就会给 nextListeners 列表 push 这个函数;
同时调用 subscribe 函数会返回一个 unsubscribe 函数,用来解绑当前传入的函数,同时在 subscribe 函数定义了一个 isSubscribed 标志变量来判断当前的订阅是否已经被解绑,解绑的操作就是从 nextListeners 列表中删除当前的监听函数。

replaceReducer

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

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

这个函数可以替换 store 当前的 reducer 函数,首先直接把 currentReducer = nextReducer,直接替换;
然后 dispatch({ type: ActionTypes.INIT }) ,用来初始化替换后 reducer 生成的初始化状态并且赋予 store 的状态;平时很难用到。

observable

/**
 * Interoperability point for observable/reactive libraries.
 * @returns {observable} A minimal observable of state changes.
 * For more information, see the observable proposal:
 * https://github.com/tc39/proposal-observable
 */
function observable() {
  ...
}

这个不是暴露给我们开发者的, 不需要掌握。因此贴给注释,就不贴详细代码, 关于 函数 observable 有兴趣的可以去作者的 gayhub(github)去学习一下: https://github.com/tc39/proposal-observable

compose.js

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.js

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    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 = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware 需要结合到 createStore 中的第三个参数 enhancer 增强器。通过对比我们可以得出 applyMiddleware 通过组合多个中间件返回的一个 enhaner 。

可以看一下前面 createStore 中 enhaner 的相关代码

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

  return enhancer(createStore)(reducer, preloadedState)
}

这里的 enhancer === applyMiddleware(...)

。。。( 读不动了 ,?懵逼中,,, 理解不了 ?? , 跳过 跳过 ??)

bindActionCreators.js

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

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

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

  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') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

这部分代码很好理解, 返回一个能够直接触发 action 的函数。

第一个参数类型应为一个函数或者对象。 当为对象时, 直接调用 bindActionCreator 方法进行返回。 当为对象时, 对对象进行遍历 将其中为 类型为 function 的 再次整合到一个对象中进行集中返回。

combineReducers.js

import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'

function getUndefinedStateErrorMessage(key, action) {
  ...
}

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  ...
}

function assertReducerShape(reducers) {
  ...
}

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

这个js 主要用来将多个 reducer 合并成一个 进行统一返回, 工作中也比较常见。

工具方法:

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(
        `Reducer "${key}" returned undefined during initialization. ` +
          `If the state passed to the reducer is undefined, you must ` +
          `explicitly return the initial state. The initial state may ` +
          `not be undefined. If you don't want to set a value for this reducer, ` +
          `you can use null instead of undefined.`
      )
    }

    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
          `Don't try to handle ${
            ActionTypes.INIT
          } or other actions in "redux/*" ` +
          `namespace. They are considered private. Instead, you must return the ` +
          `current state for any unknown actions, unless it is undefined, ` +
          `in which case you must return the initial state, regardless of the ` +
          `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}

函数 assertReducerShape 将对传入的 reducer 做了两个判断处理, 两个判断分别针对于 初始的 state 未定义, 与 传入一个未知的 type 做异常处理。 通过这里我们就不难理解 为什么 reducer 中为何必须返回一个初始的 state 与 在 switch 中的 default 返回 原有的 state。

getUnexpectedStateShapeWarningMessage

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  const reducerKeys = Object.keys(reducers)
  const argumentName =
    action && action.type === ActionTypes.INIT
      ? 'preloadedState argument passed to createStore'
      : 'previous state received by the reducer'

  if (reducerKeys.length === 0) {
    return (
      'Store does not have a valid reducer. Make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    )
  }

  if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "` +
      {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }

  const unexpectedKeys = Object.keys(inputState).filter(
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })

  if (action && action.type === ActionTypes.REPLACE) return

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}

获取意外状态形状警告消息, 用来定义返回一下异常信息的描述内容, 供 warning 调用, 条件有: 传入的 reducer 不能为空对象、state 必须为一个简单对象、意外的 reducers 缓存。

getUndefinedStateErrorMessage

function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionDescription =
    (actionType && `action "${String(actionType)}"`) || 'an action'

  return (
    `Given ${actionDescription}, reducer "${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.`
  )
}

获取未定义的状态错误消息

业务逻辑

  1. 第一步将传入的 reducer 对象 做一层浅拷贝,再赋值到 finalReducers
  2. 通过 assertReducerShape 检测 finalReducers 是否都有默认返回值
  3. 通过 变量 hasChanged 来表示 state 是否产生变化,遍历reducers集合,将每个reducer对应的原state传入其中,得出其对应的新的state。紧接着后面对新的state做了一层未定义的校验。 校验后会与原 state 进行比对,发生变化则返回 nextState, 反之返回 原 state。

结束:

到这整个源码算是粗略的读了一遍,虽然代码量不多, 只有区区数百行, 但是很绕的, 到现在有些地方依旧不能理解。不过对自身的帮助还是很大, 这期间也拜读了不少大佬的源码剖析。在这也很感谢 有那么多的大佬分享自己的学习心得体会。其中特别是 wuming 大佬的 redux源码剖析 写的十分精彩,有需要的同学可以前往拜读一下。 这份源码阅读还存在很多理解不足, 随着后续的理解进一步深入,也会再次更新的。

转载于:https://www.cnblogs.com/vant850/p/11425287.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值