天天看點

Redux源碼分析之createStore

Redux源碼分析之基本概念 Redux源碼分析之createStore Redux源碼分析之bindActionCreators Redux源碼分析之combineReducers Redux源碼分析之compose Redux源碼分析之applyMiddleware 

接着前面的,我們繼續,打開createStore.js, 直接看最後, createStore傳回的就是一個帶着5個方法的對象。

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

 同樣的,我先删除一些不需要的代碼,簡化成如下, 注意看備注。(注:這裡先無視中間件和enhancer,後篇再說)

export const ActionTypes = {
    INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState, enhancer) {
    // 初始化參數
    let currentReducer = reducer      // 整個reducer
    let currentState = preloadedState //目前的state, getState傳回的值就是他,
    let currentListeners = []         // 目前的訂閱,搭配 nextListeners
    let nextListeners = currentListeners  //下一次的訂閱 ,搭配currentListeners
    let isDispatching = false  //是否處于 dispatch action 狀态中
    
    // 内部方法
    function ensureCanMutateNextListeners() { }  // 確定currentListeners 和 nextListeners 是不同的引用
    function getState() { }    // 獲得目前的狀态,傳回的就是currentState
    function subscribe(listener) { }  //訂閱監聽,傳回一個函數,執行該函數,取消監聽
    function dispatch(action) { }    // dispacth action
    function replaceReducer(nextReducer) { }  // 替換 reducer
    function observable() { }   //不知道哈哈
    
    //初始化state
    dispatch({ type: ActionTypes.INIT })
   
    //傳回方法
    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
    }
}
      

 

ensureCanMutateNextListeners

這個方法主要用在 subscribe裡面,

  • 在每次訂閱和取消訂閱的時候,會讓 nextListeners 和 currentListeners 不是同一個引用,
  • 在每次 dispatch的時候,當 reducer執行完畢,訂閱執行前,讓 nextListeners 和 currentListeners 是一個引用  
function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
      

  為什麼這麼設計,在subscribe方法上有很詳細的注解,我的了解是假如訂閱在執行過程中,這裡說的是訂閱執行過程,不是reducer執行過程

       有新加的訂閱添加的時候,新的訂閱是不會被執行的,因為是一份拷貝

       有新的訂閱删除的時候,被删除的還是會執行的。

       簡單說,就是新的删除和添加,下次生效。

getState

就是傳回利用閉包存的currentState

/**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */
  function getState() {
    return currentState
  }
      

 subscribe

   添加訂閱

  • 每次添加前,如果 nextListeners 和 currentListeners 是一個引用,重新複制一個
  • 并存入 nextListeners 
  • 傳回一個函數,執行該函數取消訂閱,
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) // 從nextListeners裡面删除,下次dispatch會生效
    }
  }
      

dispatch

派發一個action,讓reducer更新資料,下面都有注釋了,為啥可說的。

  •  如果上一次派發沒完畢,接着派發是會出異常的,對于單線程的js來說倒是沒啥大問題
function dispatch(action) {
    if (!isPlainObject(action)) { // action 必須是對象
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {  // 必須有type屬性
      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  // 标記,正在派發
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false  //标記派發完畢
    }
     
    const listeners = currentListeners = nextListeners  // 讓nextListeners生效
    for (let i = 0; i < listeners.length; i++) {  // 挨個執行訂閱
      const listener = listeners[i]  
      listener()
    }

    return action // 傳回action
  }
      

  

replaceReducer

function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {  // 不是函數,抛出異常
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer  // 替換reducer
    dispatch({ type: ActionTypes.INIT }) // 重新初始化
  }
      

observable 還沒啥研究,暫不說了。

最後的代碼為, 

  • 初始化 state
  • 傳回相關方法
dispatch({ type: ActionTypes.INIT })

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

  這裡說一下 dispatch({ type: ActionTypes.INIT }) 是怎麼達到初始化state的,

  我們再回頭看一下disptach中的一段代碼

try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }      

      這裡先講非合成的reducer,帶合成的後面說。

   createStore的第一個參數為 reducer,第二個為初始化state的預設值,

  •  如果你傳入了第二個參數,currentState就等于你傳入的值,而執行一次 dispatch的時候,系統定義的 type 為@@redux/INIT的action,你肯定是沒有定義的額,看下面代碼,就會直接傳回state,   那麼執行 currentReducer(currentState, action) 得到的結果還是 currentState
  • 如果你沒有傳入第二個參數,在reducer的第一個參數指定了預設值,那麼reducer處理type為 @@redux/INIT的action的時候,傳回的就是reducer第一個參數 state的預設值,然後被指派給了currentState
  • 如果沒有傳入第二個參數,同時reducer的state也沒指定值,那麼,你的dispatch一般都會報錯,因為你的state從開始就是undefined
  • 如果recuder函數和createStore都設定了預設了,那麼reducer的預設值是不會生效的
let todoReducer = function (state = todoList, action) {
    switch (action.type) {
        case 'add':
            return [...state, action.todo]
        case 'delete':
            return state.filter(todo => todo.id !== action.id)
        default:
            return state
    }
}
      

  這裡相對特别的是 合成recuder,後面再說。

 到此為止,你隻用redux的createStore方法,就能完成資料控制了,combineReducers,bindActionCreators,applyMiddleware,compose 都隻是對redux的增強。

再回頭看看我們第一篇提到的代碼:(雲淡風輕)

  • 初始化的state在recuder指派,和在createStore指派是等價的,都指派的話,createStore的指派會生效。 (createStore用的是顯示指派, reducer:預設參數) 
/* 簡單示例 */
let { createStore } = self.Redux

//預設state
let todoList = []
// reducer
let todoReducer = function (state, action) {
    switch (action.type) {
        case 'add':
            return [...state, action.todo]
        case 'delete':
            return state.filter(todo => todo.id !== action.id)
        default:
            return state
    }
}

//建立store
let store = createStore(todoReducer,todoList)

//訂閱
function subscribe1Fn() {
    console.log(store.getState())
}
let sub = store.subscribe(subscribe1Fn)

store.dispatch({
    type: 'add',
    todo: {
        id: 1,
        content: '學習redux'
    }
})

store.dispatch({
    type: 'add',
    todo: {
        id: 2,
        content: '吃飯睡覺'
    }
})

store.dispatch({
    type: 'delete',
    id: 2
})

// 取消訂閱
sub()

console.log('取消訂閱後:')
store.dispatch({
    type: 'add',
    todo: {
        id: 3,
        content: '打遊戲'
    }
})
      

   

繼續閱讀