[万字长文]redux@4.0.5源代码浅析

前言

建议先下载redux,然后在node_modules中找到redux/src找到源代码。

npm install -s redux

redux简介

在react应用中,随着功能的不断丰富,组件越来越多,state也越来越复杂,直到有一天你发现,修改和共享某个state变得极其艰难:

[万字长文]redux@4.0.5源代码浅析
在这里插入图片描述

共享的state需要放在最顶层维护,然后一层一层地往下传递修改state的方法和展现的数据。这时你会发现,很多数据中间层的组件根本不需要用到,但由于子组件需要用,不得不经过这些中间层组件的传递。更令人头疼的事,当state变化的时候,你根本分不清楚是由哪个组件触发的。

  • 这时候如果使用Redux对应用进行重构,状态的变化就会变得非常清晰:
[万字长文]redux@4.0.5源代码浅析
clipboard.png

应用的state统一放在store里面维护,当需要修改state的时候,dispatch一个action给reducer,reducer算出新的state后,再将state发布给事先订阅的组件。

所有对状态的改变都需要dispatch一个action,通过追踪action,就能得出state的变化过程。整个数据流都是单向的,可检测的,可预测的。当然,另一个额外的好处是不再需要一层一层的传递props了,因为Redux内置了一个发布订阅模块。

[万字长文]redux@4.0.5源代码浅析
clipboard.png

redux使用场景

Redux虽好,但并不适用于所有项目。使用Redux需要创建很多模版代码,会让 state 的更新变得非常繁琐,谁用谁知道

正如 Redux 的作者 Dan Abramov 所言,Redux 提供了一个交换方案,它要求应用牺牲一定的灵活性以达到以下三个要求:

  • 通过简单对象和数组描述应用状态
  • 通过简单对象描述应用状态的改变
  • 使用纯函数来描述状态改变的逻辑

相应的,你会得到以下好处:

  • 可以很方便的将 state 存储到 Local Storage 中并在需要的时候取出并启动应用
  • 可以在服务器端直接计算出 state 再存到 HTML 中,然后在客户端秒开页面
  • 方便的序列化用户操作和对应的 state 快照,在出现 bug 的时候可以利用这些信息快速复现问题
  • 通过在网络中传递 action 对象,可以在对代码进行很小改动的情况下实现分布式应用
  • 可以在对代码进行很小改动的情况下实现撤销和恢复功能
  • 在开发过程中可以任意跳转到应用的某个历史状态并进行操作
  • 提供全面的审查和控制功能,让开发者可以定制自己的开发工具
  • 将 UI 和业务逻辑分离,使业务逻辑可以在多个地方重用

另外,对于 React 来说,当遇到以下情况你或许需要 Redux 的帮助:

  • 同一个 state 需要在多个 Component 中共享
  • 需要操作一些全局性的常驻 Component,比如 Notifications,Tooltips 等
  • 太多 props 需要在组件树中传递,其中大部分只是为了透传给子组件
  • 业务太复杂导致 Component 文件太大,可以考虑将业务逻辑拆出来放到 Reducer 中

redux基本概念

Redux基本概念主要有以下几个:

Store

人如其名,Store就是一个仓库,它存储了所有的状态(State),还提供了一些操作他的API,我们后续的操作其实都是在操作这个仓库。假如我们的仓库是用来放牛奶的,初始情况下,我们的仓库里面一箱牛奶都没有,那Store的状态(State)就是:

{
 milk0
}

Actions

一个Action就是一个动作,这个动作的目的是更改Store中的某个状态,Store还是上面的那个仓库,现在我想往仓库放一箱牛奶,那”我想往仓库放一箱牛奶”就是一个Action,代码就是这样:

{
  type"PUT_MILK",
  count1
}

Reducers

前面”我想往仓库放一箱牛奶”只是想了,还没操作,具体操作要靠Reducer,Reducer就是根据接收的Action来改变Store中的状态,比如我接收了一个PUT_MILK,同时数量count是1,那放进去的结果就是milk增加了1,从0变成了1,代码就是这样:

const initState = {
  milk: 0
}

function reducer(state = initState, action) {
  switch (action.type) {
    case 'PUT_MILK':
      return {...state, milk: state.milk + action.count}
    default:
      return state
  }
}

可以看到Redux本身就是一个单纯的状态机,Store存放了所有的状态,Action是一个改变状态的通知,Reducer接收到通知就更改Store中对应的状态。

简单例子

下面我们来看一个简单的例子,包含了前面提到的Store,Action和Reducer这几个概念:

import { createStore } from 'redux';

const initState = {
  milk0
};

function reducer(state = initState, action{
  switch (action.type) {
    case 'PUT_MILK':
      return {...state, milk: state.milk + action.count};
    case 'TAKE_MILK':
      return {...state, milk: state.milk - action.count};
    default:
      return state;
  }
}

let store = createStore(reducer);

// subscribe其实就是订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用
// 如果是结合页面更新,更新的操作就是在这里执行
store.subscribe(() => console.log(store.getState()));

// 将action发出去要用dispatch
store.dispatch({ type'PUT_MILK' });    // milk: 1
store.dispatch({ type'PUT_MILK' });    // milk: 2
store.dispatch({ type'TAKE_MILK' });   // milk: 1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iIOmYE1-1608541169102)(http://qinniu.douziqianduan.icu/bVQtuq-500×300)]

源码目录

[万字长文]redux@4.0.5源代码浅析
image-20201211111558226

这里主要分为两大块,一块为自定义的工具库,另一块则是redux的逻辑代码。各个文件的大概功能如下。

  • utils/warnimng.js 控制台输出一个警告

  • utils/isPlainObject.js判断一个对象是否是纯粹对象

  • utils/actionTypes.js定义了一些基本的action的TYPE,并给action打上id

  • applyMiddlewar.js 使用自定义的 middleware 来扩展 Redux

  • bindActionCreators.js 把 action creators 转成拥有同名 keys 的对象,使用时可以直接调用

  • combineReducers.js 一个比较大的应用,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分

  • compose.js 从右到左来组合多个函数,函数编程中常用到

  • createStore.js 创建一个 Redux Store 来放所有的state


utils工具文件

actionTypes.js

/**
 * These are private action types reserved by Redux.
 * For any unknown actions, you must return the current state.
 * If the current state is undefined, you must return the initial state.
 * Do not reference these action types directly in your code.
 */


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这里,它返回了是何样的随机字符串,并且有什么作用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q8LUJ69l-1608541169108)(http://qinniu.douziqianduan.icu/image-20201211134829801.png-500×300)]

从上可以看出,最终结果大概是这个样子的,其主要作用就是为每个Action打上id。

ActionTypes = {
 INIT'@@redux/INITm.6.z.c.u',
    REPLACE'@@redux/REPLACEn.y.7.8.x.7'
    PROBE_UNKNOWN_ACTION() => '@@redux/PROBE_UNKNOWN_ACTIONk.y.a.h.a.q'
}


isPlainObject.js

源代码如下

/**
 * @param {any} obj The object to inspect.
 * @returns {boolean} True if the argument appears to be a plain object.
 */

export default function isPlainObject(obj{
  if (typeof obj !== 'object' || obj === nullreturn false

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

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

isPlainObject.js向外暴露了一个用于判断是否简单对象的函数。

如何判断一个对象是否是纯粹对象?

其原理就是,简单对象的__proto__等于Object.prototype。

用一句通俗易懂的话就是:

凡不是new 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) {} 
}

打印错误信息,若浏览器不支持console对象,则改用异常抛出错误信息。

主要文件

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'

/*
 * This is a dummy function to check if the function name has been altered by minification.
 * If the function has been minified and NODE_ENV !== 'production', warn the user.
 */

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
}

主要注意的点只有两个,其它的都主要是将其它功能模块整理到index.js中,并export出来。


第一个,__DO_NOT_USE__ActionTypes。

import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

可以理解为如下行为。

// 一个例子
ActionTypes = {
 INIT'@@redux/INITm.6.z.c.u',
    REPLACE'@@redux/REPLACEn.y.7.8.x.7'
    PROBE_UNKNOWN_ACTION() => '@@redux/PROBE_UNKNOWN_ACTIONk.y.a.h.a.q'
}
__DO_NOT_USE__ActionTypes = ActionTypes

里面定义了redux自带的action的类型,从这个变量的命名来看,这是帮助开发者检查不要使用redux自带的action的类型,以防出现错误。


第二个,函数isCrushed。 这里面定义了一个函数isCrushed,但是函数体里面并没有东西。

  • 当process.env.NODE_ENV===’production’这句话直接不成立,所以warning也就不会执行;

  • 当process.env.NODE_ENV!==’production’,即开发环境。不压缩代码的时候typeof isCrushed.name === ‘string’ && isCrushed.name !== ‘isCrushed’也不会成立;

  • 当process.env.NODE_ENV!==’production’,同样是开发环境,进行了代码压缩,此时isCrushed.name === ‘string’ && isCrushed.name !== ‘isCrushed’就成立了。这里涉及到一些代码丑化和压缩的原理。经过压缩后,函数isCrushed的函数名将会被一个字母所替代。

举个例子,将redux项目的在development环境下进行了一次压缩打包。代码做了这么一层转换:

未压缩

function isCrushed() {}

压缩后

function d(){}"string"==typeof d.name&&"isCrushed"!==d.name

此时判断条件就成立了,错误信息就会打印出来。

由此可以判断,isCrushed这个主要作用就是防止开发者在开发环境下对代码进行压缩。

当然,英语好的也都容易知道,crushed的意思就有压缩的、压缩的意思。

createStore.js文件

该文件主要导入的模块如下所示

import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

导出内容

导出的只有一个createStore函数,函数createStore接受三个参数(reducer、preloadedState、enhancer)

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

reducer和enhancer用得比较多,preloadedState用的比较少。

第一个reducer很常用,就是处理Action的,这里就不过多解释了。

第二个preloadedState,它代表着初始状态,平时在项目里很少用到它,格式为对象,例如{count:55555}

* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.

@param{any}[preloadstate]初始状态。您可以选择指定它要在通用应用程序中从服务器中补充状态,或恢复
以前序列化的用户会话。如果使用“combineReducers”生成根reducer函数,则必须与“combineReducers”有key形状相同的对象。

第三个,enhancer,中文名叫增强器,顾名思义就是来增强redux的,它的类型的是Function,可以理解为装饰器之类的东西,采用的是洋葱模型,可以实现state改变时自动打印等功能,通常和中间件一起出现。


判断和处理输入参数

在正式函数开始之前,先有一段代码是判断和处理输入参数的

function createStore(reducer, preloadedState, enhancer{
    // 以前 createStore 集成 redux-devtool 的时候提供 3 个参数
    // 现在需要将后面的 用middleware 全部包裹起来: 
    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.'
      )
    }
 
 // 只输入两个参数的情况    reducer 和 enhancer
    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
      enhancer = preloadedState
      preloadedState = undefined
    }
 
    // enhancer不为空的情况
    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.')
    }
}

在上面的代码中,对enhancer尤其注意。

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

这行代码展示了enhancer的调用过程。根据这个调用过程,可以推导出enhancer的函数体的架子应该是这样子的,可能是一个闭包的样子:

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

常见的enhancer就是redux-thunk(实现异步性)以及redux-saga(解决异步问题)。一般都会配合applyMiddleware(应用中间件)一起使用,而applyMiddleware的作用就是将这些enhancer格式化成符合redux要求的enhancer。

redux-thunk的使用的例子如下:

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

// 两个参数
const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

// 也可以自定义一些enhancer
// 自动打印current state
function autoLogger({
  return createStore => (reducer, initialState, enhancer) => {
    const store = createStore(reducer, initialState, enhancer)
    function dispatch(action{
      console.log(`dispatch an action: ${JSON.stringify(action)}`);
      const res = store.dispatch(action);
      const newState = store.getState();
      console.log(`current state: ${JSON.stringify(newState)}`);
      return res;
    }
    return {...store, dispatch}
  }
}
const enhancer = compose(
  applyMiddleware(...middlewares),
  autLogger()
);

const store = createStore(
  reducer,
  enhancer
);

// 三个参数的情况例子如下
// 使用 composeEnhancer 包裹之前的 Middleware 然后传递给第二个参数。
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? 
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const store = createStore(
    reducer,
    composeEnhancers(),
    applyMiddleware(thunk)
)


createStore返回的对象

下面说一下函数createStore执行完之后返回的对象。

在createStore.js最下面一行有这一行代码:

// 第287行开始
return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
}

主要返回了有这么几个方法,其中前三个最为常用,后面两个在项目基本上不怎么用。

下面将一一介绍dispatch,subscribe和getState三个函数。


函数中主要使用的变量

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

redux是一个统一管理状态容器,它要保证数据的一致性,所以同一个时间里,只能做一次数据修改,如果两个action同时触发reducer对同一数据的修改,那么将会带来巨大的灾难。所以变量isDispatching就是为了防止这一点而存在的。


dispatch函数

function dispatch(action{
    // 判断action是否为纯粹对象
    if (!isPlainObject(action)) {
        throw new Error(
            'Actions must be plain objects. ' +
            'Use custom middleware for async actions.'
        )
    }
 
    // 判断action.type是否存在
    if (typeof action.type === 'undefined') {
        throw new Error(
            'Actions may not have an undefined "type" property. ' +
            'Have you misspelled a constant?'
        )
    }
 
    // 根据锁来判断当前是否有执行其他的reducer操作
    if (isDispatching) {
        throw new Error('Reducers may not dispatch actions.')
    }

    try {
        // 上锁
        isDispatching = true
        
        // 调用currentReducer
        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在函数体一开始就进行了三次条件判断,分别是以下三个:

  • 判断action是否为纯粹对象
  • 判断action.type是否存在
  • 判断当前是否有执行其他的reducer操作

当前三个预置条件判断都成立时,才会执行后续操作,否则抛出异常。

在执行reducer的操作的时候用到了try-finally,与try-catch的主要区别如下:

  • try语句允许我们定义在执行时进行错误测试的代码块。

  • catch 语句允许我们定义当 try 代码块发生错误时,所执行的代码块。

  • finally 语句在 try 和 catch 之后无论有无异常都会执行。

  • 执行前使用isDispatching上锁,阻止后续的action进来触发reducer操作,保证同步操作;

  • 得到的state值赋值给currentState;完成之后再finally里将isDispatching再改为false,允许后续的action进来触发reducer操作;

  • 一一通知订阅者做数据更新,不传入任何参数

  • 最后返回当前的action。

getState函数

/**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */

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,但是前提得是在dispatch函数未上锁的情况下。

这个currentState在每次dispatch的时候都会得到响应的更新。


执行createStore函数生成的store,可不可以直接修改它的state,如果试着这样修改了,getState获取到的结果如何?

同样是为了保证数据的一致性,当在reducer操作的时候,是不可以读取当前的state值的。

但getState函数返回state的时候,并没有对currentState做一层拷贝再给我们,所以是可以直接修改的。并且getState也能获取到修改后的值。只是这么修改的话,就不会通知订阅者做数据更新。

总结来说就是:

store通过getState得出的state是可以直接被更改的,但是redux不希望这么做,因为这样不会通知订阅者更新数据。

subscribe函数

完整代码如下

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * You may call `dispatch()` from a change listener, with the following
   * caveats:
   *
   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
   * If you subscribe or unsubscribe while the listeners are being invoked, this
   * will not have any effect on the `dispatch()` that is currently in progress.
   * However, the next `dispatch()` call, whether nested or not, will use a more
   * recent snapshot of the subscription list.
   *
   * 2. The listener should not expect to see all state changes, as the state
   * might have been updated multiple times during a nested `dispatch()` before
   * the listener is called. It is, however, guaranteed that all subscribers
   * registered before the `dispatch()` started will be called with the latest
   * state by the time it exits.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */

  function subscribe(listener{
    // 判断监听者是否为函数
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
 
    // 是否有reducer正在进行数据修改(保证数据的一致性)
    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#subscribelistener for more details.'
      )
    }
 
    // 表示该订阅者在订阅状态中,true-订阅中,false-取消订阅
    let isSubscribed = true
    
    // 确保可以更改下一个订阅者数组
    ensureCanMutateNextListeners()
      
    // 将listener加入到新的订阅者数组里
    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#subscribelistener for more details.'
        )
      }

      isSubscribed = false
  
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

注释的机翻如下(仅供参考)

添加更改侦听器。它将在任何时候被调用,状态树的某些部分可能已经发生了变化。那你可以call`getState()`读取回调中的当前状态树。
您可以从变更侦听器调用'dispatch()',方法如下
注意事项:
1、订阅将在每次“dispatch()”调用之前快照。
如果在调用侦听器时订阅或取消订阅,则不会对当前正在进行的“dispatch()”产生任何影响。
但是,下一个“dispatch()”调用(无论是否嵌套)都将使用订阅列表的最近快照。

2、侦听器不应该期望看到所有状态更改,因为状态可能在之前的嵌套“dispatch()”期间更新了多次
倾听者被调用。但是,保证所有用户将使用最新的按时间退出。

@param{Function}listener在每次调度时调用的回调。
@返回{Function}一个删除此更改侦听器的函数。

在注册订阅者之前,做了两个条件判断:

  • 判断监听者是否为函数
  • 是否有reducer正在进行数据修改(保证数据的一致性)

接下来执行了函数ensureCanMutateNextListeners。

其具体实现逻辑如下:

// 判断nextListeners和currentListeners是否为同一个引用
function ensureCanMutateNextListeners({
    if (nextListeners === currentListeners) {
        // 如果是,则进行深度为1的浅拷贝
        nextListeners = currentListeners.slice()
    }
}

关于nextListenerscurrentListeners,上文提到的主要有两个地方。

// 刚开始声明的使用变量
let currentListeners = [] //当前订阅者列表
let nextListeners = currentListeners //新的订阅者列表

// Function dispatch中的语句
const listeners = (currentListeners = nextListeners)

这两处将nextListeners和currentListeners引用了同一个数组。

而ensureCanMutateNextListeners(字面意思:确保可以更改下一个订阅者数组)就是用来判断这种情况的,当nextListeners和currentListeners为同一个引用时,则做一层浅拷贝,Array.prototype.slice方法会返回一个新的数组,这样就可以达到浅拷贝(深度为1)的效果。

函数ensureCanMutateNextListeners作为处理之后,将新的订阅者加入nextListeners中,并且返回取消订阅的函数unsubscribe,也是以闭包的形式。

函数unsubscribe执行时,也会执行两个条件判断:

  • 是否已经取消订阅(已取消的不必执行)

    if (!isSubscribed) {
        return
    }
  • 是否有reducer正在进行数据修改(保证数据的一致性)

    if (isDispatching) {
        throw new Error(......)
    }

通过条件判断之后,讲该订阅者从nextListeners中删除。

isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)  // 从index的位置开始往后,删除1个元素
currentListeners = null

可能会问的问题:函数dispatch里面将二者合并成一个引用,这里为什么又要分开始用呢?为什么使用nextlistners而不是currentListeners?

答:为了数据的一致性。

设想一下,有如下情况:由于js是单线程的,当redux在通知所有订阅者的时候,每通知一个订阅者就是一个发布任务。发布任务门以异步任务的形式,在等待队列中一个个排队,如果有订阅者接收到订阅消息后,又添加了一个订阅的功能,如果插入到了微队列中,可能会影响到原来发布的顺序。

此时又有一个新的订阅者加进来了。如果只用currentListeners的话,当新的订阅者插进来的时候,就会打乱原有的顺序,从而导致索引错乱的问题。当然这只是可能,同时也有本人的一些猜想,具体情况可能还会更加奇怪和复杂

replaceReducer函数

/**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */

function replaceReducer(nextReducer{
    // 判断所传reducer是否为函数
    if (typeof nextReducer !== 'function') {
        throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer

    // This action has a similiar effect to ActionTypes.INIT.
    // Any reducers that existed in both the new and old rootReducer
    // will receive the previous state. This effectively populates
    // the new state tree with any relevant data from the old one.
    dispatch({ type: ActionTypes.REPLACE })
}

这个函数是用来替换reducer,replaceReducer函数执行前会做一个条件判断:

  • 判断所传reducer是否为函数

通过条件判断之后,将nextReducer赋值给currentReducer,以达到替换reducer效果,并触发state更新操作。

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({
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} 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) {
        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
      }
    }
  }

这个observable函数,并没有调用,在该文件中的这部分代码也难以让我们看明白其具体作用,即便暴露出来我们也办法使用,不过可以根据其字面量判断,这是一个用于观察订阅模式的功能。

// createStore在开头导入的
import $$observable from 'symbol-observable'

下面是源码仓库中部分介绍翻译。

这个建议引入了一个可观察键入ECMAScript标准库。这个可观察类型可用于建模基于推送的数据源,如DOM事件、计时器间隔和套接字。此外,可观察到的有:

  • 组分:可观测值可以由高阶组合子组成.
  • 懒惰可观测到的数据直到观察者已经订阅了。

示例:观察键盘事件

使用可观察构造函数,我们可以创建一个函数,它为任意DOM元素和事件类型返回可观察的事件流。

function listen(element, eventName{
    return new Observable(observer => {
        // Create an event handler which sends data to the sink
        let handler = event => observer.next(event);

        // Attach the event handler
        element.addEventListener(eventName, handler, true);

        // Return a cleanup function which will cancel the event stream
        return () => {
            // Detach the event handler from the element
            element.removeEventListener(eventName, handler, true);
        };
    });
}

然后,我们可以使用标准的组合器来过滤和映射流中的事件,就像我们使用数组一样。

// Return an observable of special key down commands
function commandKeys(element{
    let keyCommands = { "38""up""40""down" };

    return listen(element, "keydown")
        .filter(event => event.keyCode in keyCommands)
        .map(event => keyCommands[event.keyCode])
}

注:“过滤器”和“地图”方法不包括在本提案中。它们可以添加到本规范的未来版本中。

当我们想使用事件流时,我们使用观察者.

let subscription = commandKeys(inputElement).subscribe({
    next(val) { console.log("Received key command: " + val) },
    error(err) { console.log("Received an error: " + err) },
    complete() { console.log("Stream complete") },
});

返回的对象。订阅将允许我们随时取消订阅。取消后,将执行可观察到的清理功能。

// After calling this function, no more events will be sent
subscription.unsubscribe();

如果想深入了解这个observable,可以到作者给的github的地址了解一下。

https://github.com/tc39/proposal-observable

其它注意点

createStore.js函数体里有这样一行代码。

dispatch({ type: ActionTypes.INIT })

其所处位置如下,在返回值的上面。

// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })

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

可以根据INIT看出来,刚开始要初始化一下。

为什么createStore.js返回前,要调用dispatch()初始化一下呢?

原因很简单,假设没有这行代码,此时currentState就是undefined的,也就常说的没有默认值。

如果currentState是undefined,当dispatch一个action的时候,就无法在currentState基础上做更新。

所以需要拿到所有reducer默认的state,这样后续的dispatch一个action的时候,才可以更新state。

combineReducers.js文件

combineReducers也是使用非常广泛的API,当我们应用越来越复杂,如果将所有逻辑都写在一个reducer里面,最终这个文件可能会有成千上万行,所以Redux提供了combineReducers,可以让我们为不同的模块写自己的reducer,最终将他们组合起来。

combineReducers使用示例

下面定义了三个reducer

function todos(state = [], action{
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completedfalse
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function visibilityFilter(state = SHOW_ALL, action{
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todoApp(state = {}, action{
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

Redux 提供了 combineReducers() 工具类来做上面 todoApp 做的事情,这样就能消灭一些样板代码了。有了它,可以这样重构 todoApp

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

注意上面的写法和下面完全等价:

export default function todoApp(state = {}, action{
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

也可以给它们设置不同的 key,或者调用不同的函数。下面两种合成 reducer 方法完全等价:

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})
function reducer(state = {}, action{
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}

combineReducers() 所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。


导入的模块

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

导出内容

//reducers  Object类型  每个属性对应的值都要是function
export default function combineReducers(reducers{
 ......
}

这个js对应着redux里的combineReducers方法,主要作用就是合并多个reducer。

参数很好理解,就是reducers。


combineReducers()分析

第一步:浅拷贝reducers(拷贝深度为1)

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)
  
  // 。。。。。。省略后面代码
}

这里定义了一个finalReducersfinalReducerKeys,分别用来拷贝reducers和其属性。

先用Object.keys方法拿到reducers所有的属性,然后进行for循环,每一项可根据其属性拿到对应的reducer,并浅拷贝到finalReducers中,但是前提条件是每个reducer的类型必须是Function,不然会直接跳过不拷贝。

  • 第二步:检测finalReducers里的每个reducer是否都有默认返回值
function assertReducerShape(reducers{

    // 开始遍历
    Object.keys(reducers).forEach(key => {
        const reducer = reducers[key]

        // 初始状态
        const initialState = reducer(undefined, { type: ActionTypes.INIT })

        // 不能占用<redux/*>的命名空间
        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.`
            )
        }

        // 如果遇到未知的action的类型,不需要要用默认返回值
        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.`
            )
        }
    })
}

export default function combineReducers(reducers{
    //省略第一步的代码
    ......
    // This is used to make sure we don't warn about the same
    // keys multiple times.
    let unexpectedKeyCache
    if (process.env.NODE_ENV !== 'production') {
        unexpectedKeyCache = {}
    }

    // 检测finalReducers里的每个reducer是否都有默认返回值
    let shapeAssertionError
    try {
        assertReducerShape(finalReducers)
    } catch (e) {
        // 如果不是每个reducer都有默认返回值,就抛出异常
        shapeAssertionError = e
    }

    //省略后面步骤的代码...
}

assertReducerShape方法主要检测两点:

  • 不能占用<redux/*>的命名空间
  • 如果遇到未知的action的类型,不需要要用默认返回值

如果传入type为 @@redux/INIT<随机值> 的action,返回undefined,说明没有对未知的action的类型做响应,需要加默认值。

如果对应type为 @@redux/INIT<随机值> 的action返回不为undefined,但是却对应type为 @@redux/PROBE_UNKNOWN_ACTION_<随机值> 返回为undefined,说明占用了 <redux/*> 命名空间。


  • 第三步:返回一个函数,用于代理所有的reducer

后面的代码部分如下,先看对于生产环境的处理以及waringMessage的定义逻辑。

export default function combineReducers(reducers{
    //省略第一步和第二步的代码
    ......
    
    let unexpectedKeyCache
        if (process.env.NODE_ENV !== 'production') {
        unexpectedKeyCache = {}
    }
    
    // 返回一个combination函数
    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)
            }
        }

      // ...省略后面的代码
    }    
}

首先对传入的state用getUnexpectedStateShapeWarningMessage做了一个异常检测。这个函数有什么作用呢?

getUnexpectedStateShapeWarningMessage接收四个参数 inputState(state)、reducers(finalReducers)、action(action)、unexpectedKeyCache(unexpectedKeyCache)。

很明显,它主要就是为了找出state里面没有对应reducer的key,并提示生产环境下的开发者做调整。

完整代码如下:

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

unexpectedKeyCache是上一次检测inputState得到的其里面没有对应的reducer集合里的异常key的集合。整个逻辑如下:

  1. 前置条件判断,保证reducers集合不为{}以及inputState为简单对象
  2. 找出inputState里有的key但是 reducers集合里没有key
  3. 如果是替换reducer的action,跳过第四步,不打印异常信息
  4. 将所有异常的key打印出来

接下来往下走,后面的全部代码都在这里了。

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
}
hasChanged =
    hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state

首先定义了一个hasChanged变量用来表示state是否发生变化,遍历reducers集合,将每个reducer对应的原state传入其中,得出其对应的新的state。

紧接着后面调用了getUndefinedStateErrorMessage对新的state做了一层未定义的校验。

函数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.`
  )
}

逻辑比较简单,仅仅做了一下错误信息的拼接。未定义校验完了之后,会跟原state作对比,得出其是否发生变化。最后发生变化返回nextState,否则返回state。combinerReduces()那么到这里也就结束了。


compose.js文件

这个文件比较简单,只导出这么一个函数。

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */


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

大致分为这么几步:

  1. 新建一个新数组funcs,将arguments里面的每一项一一拷贝到funcs中去
  2. 当funcs的长度为0时,返回一个传入什么就返回什么的函数
  3. 当funcs的长度为1时,返回funcs第0项对应的函数
  4. 当funcs的长度大于1时,调用Array.prototype.reduce方法进行整合

reduce这里就不赘述其用法了,可以看我的另一篇博文《一文带你彻底搞懂js的Array.prototype.reduce()方法!》。地址:https://blog.csdn.net/qq_41996454/article/details/108541886

这里的compose有个特点,它不是从左到右执行的,而是从右到左执行的,下面看个例子:

const value=compose(function(value){
  return value+1;
},function(value){
  return value*2;
},function(value){
  return value-3;
})(2);
console.log(value);  //(2-3)*2+1=-1

如果想要其从左向右执行也很简单,做一下顺序的颠倒即可。

转换前 return funcs.reduce((a, b) => (...args) => a(b(...args)))
转换后 return funcs.reduce((a, b) => (...args) => b(a(...args)))

为什么要设置成从右向左执行呢?可能是模仿栈的顺序吧,从后面加入,从后面弹出,后进后出(瞎猜的)。

这个compose可能比较让人困惑,具体应用场景是怎么样的呢?比如有三个函数,这三个函数都是我们前面接收dispatch返回新dispatch的方法:

const fun1 = dispatch => newDispatch1;
const fun2 = dispatch => newDispatch2;
const fun3 = dispatch => newDispatch3;

当使用了compose(fun1, fun2, fun3)后执行顺序是什么样的呢?

// 第一次其实执行的是
(func1, func2) => (...args) => func1(fun2(...args))

// 这次执行完的返回值是下面这个,用个变量存起来吧
const temp = (...args) => func1(fun2(...args))

// 我们下次再循环的时候其实执行的是
(temp, func3) => (...args) => temp(func3(...args));


// 这个返回值是下面这个,也就是最终的返回值,其实就是从func3开始从右往左执行完了所有函数
// 前面的返回值会作为后面参数
(...args) => temp(func3(...args));

// 再看看上面这个方法,如果把dispatch作为参数传进去会是什么效果
(dispatch) => temp(func3(dispatch));

// 然后func3(dispatch)返回的是newDispatch3,这个又传给了temp(newDispatch3),也就是下面这个会执行
(newDispatch3) => func1(fun2(newDispatch3))

// 上面这个里面用newDispatch3执行fun2(newDispatch3)会得到newDispatch2
// 然后func1(newDispatch2)会得到newDispatch1
// 注意这时候的newDispatch1其实已经包含了newDispatch3和newDispatch2的逻辑了,将它拿出来执行这三个方法就都执行了

applyMiddleware.js文件

这个文件的结构也比较简单,只有一个函数applyMiddleware,完整代码如下。

import compose from './compose'

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */

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

正如本文前面所说的,enhancer经常和applyMiddleware搭配出现,其本质就是使用的洋葱模型,其实我感觉和装饰器模式是差不多的。下面是一个enhancer+applyMiddleware洋葱模型的简单示例。

function middleware1(store{
  return function(next{
    return function(action{
      console.log('A middleware1 开始');
      next(action)
      console.log('B middleware1 结束');
    };
  };
}

function middleware2(store{
  return function(next{
    return function(action{
      console.log('C middleware2 开始');
      next(action)
      console.log('D middleware2 结束');
    };
  };
}

function middleware3(store{
  return function(next{
    return function(action{
      console.log('E middleware3 开始');
      next(action)
      console.log('F middleware3 结束');
    };
  };
}

function reducer(state, action{
  if (action.type === 'MIDDLEWARE_TEST') {
    console.log('======= G =======');  
  }
  return {};
}

var store = Redux.createStore(
  reducer,
  Redux.applyMiddleware(
    middleware1,
    middleware2,
    middleware3
  )
);

store.dispatch({ type'MIDDLEWARE_TEST' });

// output:
A middleware1 开始
C middleware2 开始
E middleware3 开始
======= G =======
F middleware3 结束
D middleware2 结束
B middleware1 结束

enhancerapplyMiddleware二者的格式对比看一下。

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

下面看applyMiddleWare的具体实现逻辑:

  1. 通过createStore方法创建出一个store

    const store = createStore(...args)
  2. 定一个dispatch,如果在中间件构造过程中调用,抛出错误提示

    let dispatch = () => {
        throw new Error(
            'Dispatching while constructing your middleware is not allowed. ' +
            'Other middleware would not be applied to this dispatch.'
        )
    }
  3. 定义middlewareAPI,有两个方法,一个是getState,另一个是dispatch,将其作为中间件调用的store的桥接

    const middlewareAPI = {
        getState: store.getState,
        dispatch(...args) => dispatch(...args)
    }
  4. middlewares调用Array.prototype.map进行改造,存放在chain

    const chain = middlewares.map(middleware => middleware(middlewareAPI)) 
  5. 用compose整合chain数组,并赋值给dispatch

    dispatch = compose(...chain)(store.dispatch)
  6. 将新的dispatch替换原先的store.dispatch,并返回结果

    return {
        ...store,
        dispatch
    }

举个简单的例子,来看一下middleware的运作流程。

假设我们有以下middlewares:

const logger = middlewareAPI => next => action => {
 console.log('start dispatch: ', action)
  let result = next(action)
  console.log('next state: ', store.getState())
  return result
}
const errorHandler = middlewareAPI => next => action => {
  try {
   return next(action)
  } catch (err) {
   // handle error
  }
}
// dummy reducer
function reducer({}
const store = createStore(reducer, applyMiddleware(errorHandler, logger))
store.dispatch({ type'test' })

以上我们定义了两个middleware,分别是logger和errorHandler。代码执行流程如下:

  1. 执行applyMiddleware,首先会调用每个middleware函数,然后将middlewareAPI作为参数传入。middleware返回一个函数,这里才是真正的middleware的执行体,也就是:next => action => {}的部分。然后代码会通过compose将所有middleware组成一条调用链,依次执行。

这里有个问题需要注意一下,就是原始的dispatch在什么时候执行?根据applyMiddleware.js中的代码:

compose(...chain)(store.dispatch)

在我们上面的例子中,这句代码相当于:

errorHandler(logger(store.dispatch))

这句代码的执行顺序是:首先执行logger,然后遇到next(action)的代码,接着执行errorHandler,最后执行原始的store.dispatch

  1. 执行createStore,由于我们传递了enhancer参数,会返回带有中间件的store(替换dispatch函数)。
  2. 我们在代码中调用store.dispatch,这时拿到的dispatch函数是被修改过的,串联了middleware的。所以会按照第一步中所描述的过程,依次执行所有middleware,最后执行原始的store.dispatch函数。


接下来再来看看另一个例子。redux-thunk是redux常用的一个enhancer。主要的功能就是可以让我们dispatch一个函数,而不只是普通的 Object。接下来以redux-thunk为例,模拟一下整个过程。

由于redux-thunk的代码量非常少,直接把它的代码贴上来看一下。这里看的是v2.3.0的代码:

// 就这几行代码,很厉害
function createThunkMiddleware(extraArgument{
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

根据redux-thunk的源码,拿到的thunk应该是这样子的:

 const thunk = ({ dispatch, getState })=>{
    return next => action => {
        if (typeof action === 'function') {
            return action(dispatch, getState);
        }
        return next(action);
    };
 }  

经过applyMiddleware处理一下,到第四步的时候,chain数组应该是这样子的:

const newDispatch;
const middlewareAPI={
  getState:store.getState,
  dispatch(...args) => newDispatch(...args)
}
const { dispatch, getState } = middlewareAPI;
const  fun1 = (next)=>{
  return action => {
    if (typeof action === 'function') {
        return action(dispatch, getState);
    }
    return next(action);
  }
}
const chain = [fun1]

compose整合完chain数组之后得到的新的dispatch的应该是这样子:

const newDispatch;
const middlewareAPI={
  getState:store.getState,
  dispatch(...args) => newDispatch(...args)
}
const { dispatch, getState } = middlewareAPI;
const next = store.dispatch;
newDispatch = action =>{
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  return next(action);
}

bindActionCreators.js文件

这个文件其实对于redux的理解没那么重要,bindActionCreators主要是需要结合react-redux一起使用的。只要知道bindActionCreators大概干了啥就行。下面是该文件的源代码。

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

/**
 * Turns an object whose values are action creators, into an object with the
 * same keys, but with every function wrapped into a `dispatch` call so they
 * may be invoked directly. This is just a convenience method, as you can call
 * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
 *
 * For convenience, you can also pass an action creator as the first argument,
 * and get a dispatch wrapped function in return.
 *
 * @param {Function|Object} actionCreators An object whose values are action
 * creator functions. One handy way to obtain it is to use ES6 `import * as`
 * syntax. You may also pass a single function.
 *
 * @param {Function} dispatch The `dispatch` function available on your Redux
 * store.
 *
 * @returns {Function|Object} The object mimicking the original object, but with
 * every action creator wrapped into the `dispatch` call. If you passed a
 * function as `actionCreators`, the return value will also be a single
 * function.
 */

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 boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

bindActionCreators针对于三种情况有三种返回值,下面根据每种情况的返回值去分析。(为了方便理解,假定是在无集成中间件的情况下进行)

  • 情况一:typeof actionCreators === ‘function’
function bindActionCreator(actionCreator, dispatch{
    return function({
        return dispatch(actionCreator.apply(thisarguments))
    }
}

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

    // ... 忽略后面的代码
}


// 所以第一种情况可以概括成以下语句
const fun1 = actionCreator;
const dispatch= store.dispatch;
const actionFun=function ({
    return dispatch(fun1.apply(thisarguments))
}

根据上面的推导,当变量actionCreators的类型为Function时,actionCreators必须返回一个action。

  • 情况二: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"?`
)

提示开发者actionCreators类型错误,应该是一个非空对象或者是函数。


  • 情况三:默认
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

通过和第一种情况对比发现,当actionCreators的每一项都执行一次第一种情况的操作。换句话说,默认情况是第一种情况的集合。


小结

  1. 单纯的Redux只是一个状态机,store里面存了所有的状态state,要改变里面的状态state,只能dispatch action
  2. 对于发出来的action需要用reducer来处理,reducer会计算新的state来替代老的state
  3. subscribe方法可以注册回调方法,当dispatch action的时候会执行里面的回调。
  4. Redux其实就是一个发布订阅模式!
  5. Redux还支持enhancerenhancer其实就是一个装饰者模式,传入当前的createStore,返回一个增强的createStore
  6. Redux使用applyMiddleware支持中间件,applyMiddleware的返回值其实就是一个enhancer
  7. Redux的中间件也是一个装饰者模式,传入当前的dispatch,返回一个增强了的dispatch
  8. 单纯的Redux是没有View层的,所以他可以跟各种UI库结合使用,比如react-redux

参考

《理解redux-thunk》https://zhuanlan.zhihu.com/p/85403048

《我的源码阅读之路:redux源码剖析》https://segmentfault.com/a/1190000016460366

《手写一个Redux,深入理解其原理》https://juejin.cn/post/6845166891682512909#heading-13


原文始发于微信公众号(豆子前端):[万字长文]redux@4.0.5源代码浅析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56865.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!