大道至簡之redux原始碼分析

hellosean發表於2019-03-03

知乎:https://zhuanlan.zhihu.com/p/31025366

1. 背景

對於大型複雜應用程式來說,我們經常需要共享某些元件的狀態或者一個元件需要改變另外一個元件的狀態。 如果沒有一個合適的管理工具管理狀態,很快就會變成一團亂。使用 redux 的開發者越來越多,但背後 redux 原理和為什麼用 redux 很多人其實並不清楚。如果我們不能完全理解 redux 的思想,可能在實際的應用中,會盲目使用。本文主要結合 redux 原始碼分析 redux 原理,幫忙大家更好的使用 redux。

2. Redux 核心概念

大道至簡之redux原始碼分析

2.1 Store

Store 在中文裡是儲備的意思,Store 其實就是一個儲存資料的倉庫,按照 redux 設計理念,Store 只能有一個。下面是經過簡化的 createStore 原始碼,方便大家理解。

// reducer 是純函式
export default function createStore(reducer) {
  //當前Reducer
  let currentReducer = reducer
  //當前state
  let currentState

  //獲取當前state
  function getState() {
    return currentState
  }

  /**
   * 用來觸發狀態改變
   *
   * @param {Object} action 
   *
   * @returns {Object} 原來action
   */
  function dispatch(action) {
    currentState = currentReducer(currentState, action)
    return action
  }

  // 初始化 state tree.
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState
  }
}

複製程式碼

Store 是一個由 createStore 函式建立生成的物件,包括了 dispatch, subscribe, getState等屬性。createStore 第一個引數是純函式 reducer,經過 dispatch 初始化,呼叫 reducer 函式生成了新的 State。

2.2 State

Store 包括了所有的資料,如果要得到某個時點的資料,就要對 Store 生成快照。這個時點的資料集合,就叫 State。根據上文的原始碼,我們知道通過呼叫 store.getState() 就能獲取到當前的 state 物件。

2.3 Action

使用者不能修改 view, 不能直接修改 state,於是就有了 action 的概念。action 是一個物件,其中 type 屬性是必須的。

 /**
   * 用來觸發狀態改變
   *
   * @param {Object} action 
   *
   * @returns {Object} 原來action
   */
  function dispatch(action) {
    currentState = currentReducer(currentState, action)
    return action
  }
複製程式碼

dispatch(action) 後,會呼叫當前的 reducer 根據 state 和 action ,生成新的 state;

2.4 Action Creator

如果每一個Action都需要手寫,會非常麻煩,可以定義一個函式生成 Action,這個函式就叫 Action Creator,下面是用法示例。

const ADD_TODO = '新增 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');

複製程式碼

2.5 bindActionCreator

這個函式作用是簡化 store.dispatch 呼叫, 將一個或一組actionCreator跟 dispatch 繫結

//將dispatch 繫結到 actionCreator
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 如果actionCreators是一個函式,直接返回 bindActionCreator處理後的函式
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  // 遍歷物件,然後對每個遍歷項的 actionCreator 生成函式,將函式按照原來的 key 值放到一個物件中,最後返回這個物件
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    } else {
      warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
    }
  }
  return boundActionCreators
}

複製程式碼

2.6 Store.dispatch

定義了 Action,那麼如何執行 Action呢?dispatch 是 Redux 執行 action 唯一方法。下面是 dispatch 函式全部原始碼,整個邏輯非常簡單。使用 reducer 函式處理當前 state,處理完成後,執行訂閱的 listeners。

  function dispatch(action) {
    //檢查是否是純粹的object
    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?'
      )
    }
    
    //如果正在dispath,丟擲錯誤
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      //呼叫currentReducer傳給curentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    //將下一個監聽器傳給當前監聽器,使用for迴圈依次執行listener
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

複製程式碼

2.7 Reducer

Store 收到 Action 之後,需要計算一個新的 Action, 這種計算 State 變化的函式,就叫 Reducer。


const chatReducer = (state = defaultState, action = {}) => {
  const { type, payload } = action;
  switch (type) {
    case ADD_CHAT:
      return Object.assign({}, state, {
        chatLog: state.chatLog.concat(payload)
      });
    case CHANGE_STATUS:
      return Object.assign({}, state, {
        statusMessage: payload
      });
    case CHANGE_USERNAME:
      return Object.assign({}, state, {
        userName: payload
      });
    default: return state;
  }
};

複製程式碼

2.8 combineReducers

一個大型應用是由很多元件組成的,如果全部寫到一個 reducers 方法,不易維護。通過 redux 提供的 combineReducers 方法可以將多個 reducers 合併成一個 reducers。

/**
 * 這個函式可以組合一組 reducers(物件) ,然後返回一個新的 reducer 函式給 createStore 使用。
 *
 * @param {Object} reducers reducers 組成的物件
 *
 * @returns {Function} 返回一個組合後的reducer .
 */
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    //如果reducers[key]是函式,儲存到finalReducers
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  //返回新合併的Reducer

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

    let hasChanged = false
    const nextState = {}
    //用for迴圈遍歷,根據key取出的reducer和舊的state,然後執行reduder函式獲取新的state。如果state有變化,返回新的state
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

複製程式碼

3. 流程圖:

image

最後打個小廣告

歡迎使用作者開發的介面管理平臺: http://yapi.qunar.com

參考

  • https://redux.js.org/docs/basics/Store.html
  • http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html

相關文章