阅读 231

Redux源码分析(2) - createStore

1、前言

  下边章节中将详细分析源码,源码分析中对于一些边界的判断、类型判断等不做重点分析,主要将分析的重点放在主流程方向上。

2、createStore

   createStore 作为 Redux 的核心 api 之一,其作用是通过 reducer 和 中间件 middleware 构造一个为 store 的数据结构。关于 createStore 的使用可参考上一章节 Redux源码分析(1) - Redux介绍及使用 和 官方文档 createStore

  createStore 的源码结构图如下图所示。根据是否传入enhancer,分别做不同的逻辑判断

image

2.1 存在enhancer

   当存在enhancer的时候,实际执行语句如下:

//createStore.js

return enhancer(createStore)(reducer, preloadedState);  //记为语句(1)
复制代码

   在上一章节中,我们介绍过 Redux 的如何使用:

//demo

let store = createStore(rootReducer, applyMiddleware(Logger, Test));   //记为语句(2)
复制代码

   由语句(2)可知:enhancer 其实就是 applyMiddleware 作用中间件(此处为Logger, Test)的结果。查看 applyMiddleware 源码,大致结构如下:

//applyMiddleware.js

function applyMiddleware(...middlewares){
    //createStore中对于的enhancer
    return createStore => (...args) => {
    //...
      return {
        ...store,
        dispatch  // 覆盖store中的dispatch
      };
    }
}

复制代码

   由 applyMiddleware 的源码可知。语句(1)执行的就是 applyMiddleware 返回高阶函数的完整执行,最终返回的结果是包含 store 所有属性 和 dispatch属性的一个对象,这也与没有enhancer时,createStore输出的结果保持之一。因为 store 对象中本身就存在 dispatch 属性,由此可知 :

   applyMiddleware 作用中间件的结果就是更改 store 对象的dispatch。这是前文提到过的,applyMiddleware 本质是对 dispatch 的增强。至于是如何增强的,在下文会详细分析。

2.2 不存在enhancer

   首先看下此时对应的返回结果

//createStore.js

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable
};
复制代码
2.2.1 方法内的局部变量
let currentReducer = reducer;  // 临时reducer
let currentState = preloadedState; //当前的state值,默认为初始化preloadedState
let currentListeners = [];  // 监听队列,用于存放监听事件, 发布-订阅模式
let nextListeners = currentListeners; // 浅拷贝下这个队列
let isDispatching = false; // 标志位,用来判断是否有存在正在执行的dispatch
复制代码

   各个变量的作用,注释中已经详细注明,不再赘述。

2.2.2 getState

   根据 Redux api 可知:getState 方法返回当前的 state 树。currentState 默认值 为 preloadedState,具体的 currentState 取决于 dispatch(action) 时 reducer 执行后返回的结果。其中如果 isDispatching 为 true 时,表示有 dispatch 正在执行,此时获取 state 的值会导致获取不到正确的 state。


function getState() {
    if (isDispatching) {
        .....
    }
    
    return currentState;
}
复制代码
2.2.3 subscribe

   先来看看subscriber的使用,其中 listener 表示监听触发时,需要做的一些操作。


let unsubscribe = store.subscribe(listener)
unsubscribe()
复制代码

   subscribe 的源码如下:


function subscribe(listener) {
    // 类型判断
    if (typeof listener !== 'function') {
        throw new Error('Expected the listener to be a function.');
    }

    // 同理不可以dispatch中
    if (isDispatching) {
        //……
    }

    // 用来表示订阅标记,用于避免取消订阅后再次取消
    let isSubscribed = true;
    // ensureCanMutateNextListeners干啥的,点击去看一下
    ensureCanMutateNextListeners();
    // 将 listener 存在在 发布-订阅模式的监听队列 nextListeners 中
    nextListeners.push(listener);
    // 返回取消的function(unsubscribe)
    return function unsubscribe() {
        // 如果已经取消订阅 直接直接return
        if (!isSubscribed) {
            return;
        }

        // 同理
        if (isDispatching) {
            //……
        }

        // 这里标记为 已经取消订阅
        isSubscribed = false;
        // 保存订阅快照
        ensureCanMutateNextListeners();
        // 根据索引 在监听队列里删除监听
        const index = nextListeners.indexOf(listener);
        nextListeners.splice(index, 1);
    };
}
复制代码

   通过源码的解读可知。listener 必须传入一个 function 类型,否则就会报错。这里用到了发布-订阅模式,执行 subscribe 方法时,将传入的 listener 存放在 监听队列 nextListeners 里,currentListeners 和 nextListeners 都是引用类型,都是指向的一个内存地址,可以理解为是一个东西。

   返回值是一个 unsubscribe 函数。执行该函数,就能够取消订阅。具体来看首先判断 isSubscribed 是否为 false,如果是则代表已经取消了该订阅,再次执行改订阅则直接忽视。如果 isSubscribed 为 ture 则表示该订阅还存在,则通过 indexOf 方法找到索引后,通过 splice 方法,将该订阅从订阅队列中取消,同时不要忘记将 isSubscribed 设置为已经 false (表示已经取消)。

2.2.4 dispatch

   dispatch的使用方式如下。分发 action是触发 state 变化的惟一途径。匹配到对应的 reducer 执行之后,会返回一个新的 state。

store.dispatch(action)
复制代码

   dispatch 的源码如下:

function dispatch(action) {
        // acticon必须是由Object构造的函数, 否则throw Error
        if (!isPlainObject(action)) {
            // 抛出错误
        }

        // 判断action, 不存在type throw Error
        if (typeof action.type === 'undefined') {
           // 抛出错误 'Have you misspelled a constant?'
            );
        }

        // dispatch中不可以有进行的dispatch
        if (isDispatching) {
           // 抛出错误
        }

        try {
            // 执行时标记为true
            isDispatching = true;
            // reducer形式如下:(state,action)=>{} , reducer本身就是个函数,由此可见dispatch(action), 就是执行reducer方法,并将currentState,action作为参数
            currentState = currentReducer(currentState, action);
        } finally {
            // 最终执行, isDispatching标记为false, 即完成状态·
            isDispatching = false;
        }

        // 循环监听队列,并执行每一个监听事件
        const listeners = (currentListeners = nextListeners);
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i];
            // 执行每一个监听函数
            listener();
        }
        // 返回传入的action
        return action;
    }
复制代码

   通过源码的解读可知。action 必须是一个纯粹的对象且必须包含type属性,否则就出抛出异常。dispatch 方法干了两件事情:

  • 1、找到对应的 reducer 根据 action.type 执行对应的分支,并返回最新的 state 。 为什么说对应的 reducer,因为 reducer 有可能通过 combineReducers 组合的,此时 action.type 只会更改对应的 reducer 的返回值。
currentReducer(currentState, action);

如果 reducer 不是组合生成,以 reducer/todos 为例。则  currentReducer 就是如下:

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO': //……

    case 'TOGGLE_TODO': //……
     
    default:
      return state
  }
}

如果 reducer 组合生成,也即本例中 rootReducer (通过 combineReducers 组合)。查看 combineReducers 源码可知,  combineReducers 组合各个 reducer 后返回的是一个名为 combination 的高阶函数,  也就是currentReducer ,有如下形式:

function combination(state = {}, action) {
    // ……
    return state
}

二者具有相同的形式

(state , action ) =>{ // 更改 state的逻辑}
复制代码

   通过上边的伪代码分析可以,不管是否通过 combineReducers 组合生成, currentReducer 都具有相同的形式, 本身作为一个函数接受参数 state 和 action ,并根据action,改变state的值。因此可以认为,dispatch(action) 时,本质就是 reducer 方法的执行,并将当前的 stare 值 currentState 和 action 作为参数,并返回一个新的 state。

  • 2、执行通过 subscribe 方法添加的监听, 通过 subscribe 方法添加的监听都被记录在监听队列 currentListeners 中,在dispatch 方法会循环遍历监听队列,并以此执行各个队列元素。
2.2.5 replaceReducer

   在 createStore 方法中,还要其他一些我们不常用的 api

  • replaceReducer : 更改 reducer

这是一个高级 API。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到observable:

3、reducer 的初始化。

dispatch({ type: ActionTypes.INIT });


const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  //……
}
复制代码

   由上边的代码可知,初始化 reducer 时是通过 dispatch 一个随机产生的 action 。根据 reducer 的特性可知,当前初始化的 dispatch 会执行对应的 default 分支,也即会输出 reducer 中默认的 state 的值。

   与 createStore 参数 preloadedState 的对比:在 createStore 方法的定义中可以接受一个preloadedState 参数,该参数会默认为当前的 state。

let currentState = preloadedState; //当前的state值,默认为初始化preloadedState
复制代码

   通过 dispatch 的流程可知,初始化dispatch时,preloadedState 会作为 reducer 方法执行的参数传入。当 preloadedState 不存在时,此时 reducer 的入参为 undefined。通常做法如下, 通过 es6 的默认参数给state 复制初始值,则能起到 preloadedState 的效果。猜测 Redux 这些写应该是为了兼容 es5 的默认值处理吧。

const todos = (state = [], action) => {//.......}
复制代码

   但是不管 preloadedState 指定与否,初始化dispatch 执行后,currentState 的值即为default 分支对应的值。由此可知,我们定义的 reducer 都要包含 default 分支,否则初始化后 state 的值就会出现异常。


Redux源码分析(1) - Redux介绍及使用

redux源码分析(2) - createStore

Redux源码分析(3) - applyMiddleware

Redux源码分析(4) - combineReducers和bindActionCreators

文章分类
前端
文章标签