redux源码解析之五部曲(why:对外暴露了5个api)——第一部曲createStore.js

847 阅读4分钟
概述:redux是利用闭包,来达到状态的共享操作(个人认为)。
为什么先讲createStore呢,根据代码书写逻辑,一般都把创建store这一部放在入口文件里(一步一步反推)。
首先npm装下redux包,进入src/index.js目录
//就暴露了5个对外接口,开不开心。只有这么点api

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}
今天我们首先先分析createStore.js(ps:如果你看到index.js的话,一定一眼就看到createStore.js)
向外暴露了

1、export const ActionTypes = {
  INIT: '@@redux/INIT'
}

2、export default function createStore(reducer, preloadedState, enhancer) {
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
很惊奇ActionTypes定义、暴露这个干嘛?何用?其实用处很大,初始化的state状态仓库就是dispatch(ActionTypes),之前很惊奇好奇,redux怎么初始化状态(ps:用惯了vuex),
习惯很思维很难改。。。
再看第二个暴露,又return出dispatch,subscribe,getState,replaceReducer,[?observable]: observable 前三个api 很熟悉
那我们就看看源码createStore怎么定义的。
进入正题
1.参数,依次是reducer,初始预加载的状态,增强中间件插件
2.代码逻辑

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

三段if 无非判断传入参数,实现函数重载
第一个if,判断如果preloadedState(第二个参数)传人是函数类型,并且enhancer(第三个参数)没传,则将传入的第二个参数函数传递给第三个参数.

第二个if,如果有enhancer,确定enhancer是函数类型,不是,则抛错。如果是函数,则返回一个增强的中间件处理的dispatch。关于enhancer这一块详解,具体解释在,applymiddlewares时候再详细解释。

第三个if,reducer必须是一个函数,不是则抛错。

接下来,代码定义了一些初始变量,应该一看变量名就一清二楚了

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []   
  let nextListeners = currentListeners
  let isDispatching = false

接下来

//主要处理copy currentListeners的值,但nextListeners和currentListeners指向不一样,不是同一个数组
//这个函数,用在subscribe(订阅函数)内

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

剩下的代码,都是createStore暴露出来的api。

1. 获取当前状态的 getState

function getState() {
    return currentState
  }
2.订阅函数,参数是listener回掉函数。将listener函数放进nextListener数组内
在用户触发dispatch时候,会依次触发nextListeners调用
注意,调用函数后,返回一个取消该listener函数的方法,如果你的订阅是一次性的,可以继续调用,取消订阅

function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected listener to be a function.')
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}
3.派发用户传递的动作

function dispatch(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?'
    )
  }

  //一次只能执行一个,防止同时触发。
  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
  }

  try {
    isDispatching = true

    //currentReducer是用户传进来的reducer,其实就是调用reducer
    //两个参数是currentState,当前状态,行动。
    //返回一个新的state,并存入currentState变量内

    currentState = currentReducer(currentState, action)
  } finally {
    //try执行结束后,重置isDispatching状态
    isDispatching = false
  }

  //触发subscribe订阅函数
  const listeners = currentListeners = nextListeners
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  //返回action,很重要,对于中间件来说
  return action
}
至此,常用api 都已经定义完了。最重要的,初始化state

dispatch({ type: ActionTypes.INIT })
源码结束。

那现在就捋一捋dispatch({ type: ActionTypes.INIT })做了什么。
首先 肯定需要调用下createStore,代码才能执行。
以下是node环境写的,通常函数用es6的import来写

const reduxLib = require("redux")
const createStore = reduxLib.createStore
//定义了个reducer
function testReducer(state={total:0},action){
    if(action.type == 'add'){       
        var total = state.total + 1
        return Object.assign({},state,{total:total})
    }else{
        return state
    }
}
//当我们不需要预知state,和enhancer时,一个reducer就够了

let store = createStore(testReducer)
//基本代码写完了,不知道源码里dispatch({ type: ActionTypes.INIT })这句还有没有忘记//呢?当创建仓库时,源码手动触发了dispatch,这里面做什么了?看上文dispatch定义。//testReducer(undefined,{ type: ActionTypes.INIT })返回值赋值给currenState.

var initState = store.getState();//拿到是撒?{total:0},是传入reducer参数state的默认值;
console.log('initState',initState)

//再尝试调用订阅函数

store.subscribe(function(){
  var state = store.getState()
  console.log(state)
})

//再手动触发dispatch,写个循环吧,😂

for(var i = 0 ; i< 10 ; i++){
  store.dispatch({
    type:'add'
  })
}
//控台截图,是不是恍然大雾。。。


createStore 第二个参数时,是一个对象,最好和reducer里的state数据模型保持一致。依我上例,继续说,源码dispatch({ type: ActionTypes.INIT }),会调用

testReducer(preloadedState,{ type: ActionTypes.INIT });//此时匹配到默认的就是传下来的preloadedState。//以下是createStore第二个参数传递对象的情况。

let preloadedState = {
  total:0
}
let store = createStore(testReducer,preloadedState);
console.log(store.getState()) //拿到的是preloadedState

剩下的关于createStore暴露的api,本系列暂时不讲(一般真的用不上。。。,经过我的梳理,相信你看下代码,一定能看的懂)。关于createStore第三个参数,在applyMiddleware节,详细讲解