redux源码解读--createStore源码解析

createStore源码解析

createStoreredux最核心的模块。这个模块就是用于建立一个store对象,同时,对外暴露出dispatch,getState,subscribereplaceReducer方法。(源码中关于observable的部分能够忽略,这个是redux内部使用的。咱们在开发中几乎几乎用不到)javascript

先看一下这个模块的基本结构:java

图片描述

依赖git

  • lodash/isPlainObject
  • symbol-observable

对外输出github

  • dispatch
  • getState
  • subscribe
  • replaceReducer
  • [$$observable](几乎不用)

redux源码中使用的lodash/isPlainObject依赖。在IE6-8中性能不好,其实现方式和jQuery3.x的实现类似,在旧版本的IE中支持不了。最后会和你们一块儿探讨。redux

源码注释数组

// 判断是否是纯粹对象的模块({})
import isPlainObject from 'lodash/isPlainObject'
// 引入observable支持
import $$observable from 'symbol-observable'
export const ActionTypes = {
  INIT: '@@redux/INIT'
}

上面这个是redux内部使用的一个action。主要用于内部测试和渲染初始的state。记住,咱们本身编写action的时候,action.type不能为@@redux/INIT。由于,这个action会在redux的内部自动调用。好比,下面的捣蛋代码:浏览器

import {createStore, combineReducers, applyMiddleware} from '../src'
import logger from 'redux-logger'

const actionTypes = '@@redux/INIT'
const reducers = (state = {}, action) => {
  switch(action.type) {
    case actionTypes:
      console.log('hello @@redux/INIT')
      return {
        'type': actionTypes
      }
    default:
      return state
  }
}
const store = createStore(reducers, applyMiddleware(logger))
console.log('*************************************')
console.log(store.getState()) //会渲染为 {'type': '@@redux/INIT'}
console.log('*************************************')

下面就是createStore方法的实现:app

export default function createStore(reducer, preloadedState, enhancer){
  //...初始条件的判断和设定
  function getState() {
    // getState方法的实现
  }
  function subscribe() {
    // subscribe方法的实现
  }
  function dispatch() {
    // dispatch方法的实现
  }
  function replaceReducer() {
    // replaceReducer方法的实现
  }
  function observable() {
    // observable方法的实现
  }
  // store被建立后,自动分发一个'INIT' action。渲染出初始化的state树。
  dispatch({ type: ActionTypes.INIT })
}

下面就来一点点分析每一行代码到底作什么:async

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

在调用createStore的方法的时候,能够传递三个参数createStore(reducer, preloadedState, enhancer)。其中,各参数属性以下:函数

  • reducer必需参数,function类型
  • preloadedState可选参数,object类型
  • enhancer可选参数,function类型

在日常的使用中,咱们通常会省略第二个参数。好比,当咱们须要使用redux中间件的时候,就会像第三个参数传递一个applyMiddleware()[返回值是一个function]。若是,咱们没有初始状态,则会省略第二个参数。这个时候,咱们的函数调用形式为:

const store = createStore(reducer, applyMiddleware(...))

这个时候就会执行上面源码中的代码,使函数调用知足最基本的形式调用。也就是函数在传递两个或者三个参数的状况下,其内部处理逻辑都是同样的。

// 若是咱们指定了reducer加强器enhancer
if (typeof enhancer !== 'undefined') {
  // enhancer必须是一个函数
  if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 这个函数接收createStore做为参数,而且返回一个函数,这个函数接收的参数是reducer,preloadedState
    // 直接返回通过enhancer包装的对象
    return enhancer(createStore)(reducer, preloadedState)
  }

想更好的理解这段代码,能够参考applyMiddleware内部的实现。

// 要求传递给createStore的第一个参数必须是一个函数
if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
 }
// 保存初始的reducer
let currentReducer = reducer
// 保存初始的state
let currentState = preloadedState
// 保存全部的事件监听器
let currentListeners = []
// 获取当前监听器的一个副本(相同的引用)
let nextListeners = currentListeners
// 是否正在派发action
let isDispatching = false

function ensureCanMutateNextListeners() {
  // 若是nextListeners和currentListeners具备相同的引用,则获取一份当前事件监听器集合的一个副本保存到nextListeners中
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}

上面就是createStore方法中的一些初始参数,这里有一个地方值得思考:为何要维护两份事件监听器列表(nextListeners,currentListeners)?。下面,咱们会解释。

// 直接返回当前store的state
function getState() {
  return currentState
}
function subscribe(listener) {
    // 事件监听器必须是函数,不然会抛出异常
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }
    // 这个事件监听器是否已经被取消的标志(我的感受:这个初始值应该被设置为false,语意化更好一些。)
    let isSubscribed = true
    // 调用这个函数的结果就是生成一份当前事件监听器的一个副本保存到nextListeners中
    ensureCanMutateNextListeners()
    // 将新的事件监听器添加到nextListeners中
    nextListeners.push(listener)
    
    // 返回一个取消监听的函数
    return function unsubscribe() {
      // 若是这个监听器已经被取消了,则直接return
      if (!isSubscribed) {
        return
      }
      // 将监听器是否取消的标志设置为false
      isSubscribed = false
      // 再次生成一份事件监听器集合的副本
      ensureCanMutateNextListeners()
      // 获取到须要取消的事件监听器的索引
      const index = nextListeners.indexOf(listener)
      // 从事件监听器集合中删除这个事件监听器
      nextListeners.splice(index, 1)
    }
  }

subscribe方法的源码中能够看出,每次在进行监听器的添加/删除以前,都会基于当前的监听器集合生成一个副本保存到nextListeners中。这个时候仍是不能准确的回答上面的问题,下面咱们继续研究dispatch的源码:

function dispatch(action) {
    // dispatch的参数就是咱们须要派发的action,必定要保证这个action是一个纯粹的对象
    // 若是不是一个纯粹的对象,则会抛出异常。
    if (!isPlainObject(action)) {
      // 这个方法有坑,在低版本的IE浏览器中性能不好,最后咱们会研究这个方法的源码。
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    // 所派发的action必须有一个type属性(咱们能够将这个属性认为就是action的身份证,这样redux才知道你派发的是哪一个action,你须要作什么,该怎么为你作)
    // 若是没有这个属性则会抛出异常
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
    
    // 若是redux正在派发action,则抛出异常?何时会出现这种状况???
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 派发action
      // 实质就是将当前的state和你须要派发的action传递给reducer函数病返回一个新的state
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 这一块也是一个十分关键的地方,哈哈哈哈哈,又多了一份事件监听器的列表,简单的说一下这三份列表的做用
      // nextListeners: 保存此次dispatch后,须要触发的全部事件监听器的列表
    // currentListeners: 保存一份nextListeners列表的副本
      // listeners: 须要执行的列表
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      // 调用全部的事件监听器
      listener()
    }
    //  dispatch的返回值也是十分重要的,若是没有这个返回值,就不可能引入强大的中间件机制。
    return action
  }

到这里,咱们就能够回答这个问题了:为何要维护两份事件监听器列表(nextListeners,currentListeners)?

首先,咱们必需要知道的事情就是:咱们的监听器在何时会执行?在咱们的调用dispatch派发action以后。ok,看下面的这个图:

图片描述

这个图表示,当dispatch方法执行到这行代码的时候,listenerscurrentListenersnextListeners这三个变量引用内存中的同一份数组,只要其中一个发生变化,另外两个立马改变。和下面的这个例子同样的含义:

图片描述

因此,在这种状况下。若是我在某个事件监听器函数中调用了取消了某个监听器,那么在此次dispatch后,被取消的这个事件监听器就不会被执行了(?????是吗????)。

import {createStore, combineReducers, applyMiddleware} from '../src'
import logger from 'redux-logger'

const actionTypes = '@@redux/INIT'
const reducers = (state = {}, action) => {
  switch(action.type) {
    case actionTypes:
      return {
        'type': actionTypes
      }
    default:
      return state
  }
}
const store = createStore(reducers, applyMiddleware(logger))

const listener1 = store.subscribe(() => {
    console.log('listener1')
})


const listener2 = store.subscribe(() => {
    // 取消listener3
    listener3()
    console.log('listener2')
})


const listener3 = store.subscribe(() => {
    console.log('listener3')
})

store.dispatch({type: actionTypes})

结果是:

listener1
listener2
listener3

结果,就是:即便你在某个事件监听器中,取消了其它的事件监听器,那么被取消的这个事件监听器,在此次dispatch后仍然会执行。也就是说。redux会保证在某个dispatch后,会保证在这个dispatch以前的全部事件监听器所有执行。

这是个bug仍是个feature。无从而知,可是从redux源码中,能够知道,这是一个bug。因此,redux做者就利用上面的方法很巧妙的避免了这种状况。其实实现的方法很简单:切断nextListeners和currentListener,listeners相同的引用关系

图片描述

下面接着扯:

// 提换reducer的方法。(动态加载reducers的时候才用)
function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    // 替换结束后,从新初始化
    dispatch({ type: ActionTypes.INIT })
  }
// 触发预设action,主要就是为了生成初始的state tree的结构
dispatch({ type: ActionTypes.INIT })
// 这就很熟悉了吧
return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
      // 尼玛 忽略这个
    [$$observable]: observable
  }

这就是对createStore源码的一个总体解读,水平有限,欢迎拍砖。后续的源码解读和测试例子能够关注:redux源码解读仓库