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

概述:redux是利用閉包,來達到狀態的共享操做(我的認爲)。
爲何先講createStore呢,根據代碼書寫邏輯,通常都把建立store這一部放在入口文件裏(一步一步反推)。
首先npm裝下redux包,進入src/index.js目錄
//就暴露了5個對外接口,開不開心。只有這麼點api

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}複製代碼
今天咱們首先先分析createStore.js(ps:若是你看到index.js的話,必定一眼就看到createStore.js)
向外暴露了

一、export const ActionTypes = {
  INIT: '@@redux/INIT'
}複製代碼

二、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. 獲取當前狀態的 getStatevue

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 }),會調用
node

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節,詳細講解
相關文章
相關標籤/搜索