手把手教你擼一套Redux(Redux源碼解讀)

Redux 版本:3.7.2redux

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。設計模式

說白了Redux就是一個數據存儲工具,因此數據基礎模型有get方法,set方法以及數據改變後通知的對象subscribe訂閱者。數組

  • getState: getter(取)
  • dispatch: setter(存)
  • subscribe: 訂閱

Redux 提供了五個方法app

接下來咱們來一一解析。函數


createStore

建立一個 Redux store 來以存放應用中全部的 state。應用中應有且僅有一個 store。工具

參數:spa

  • reducer (Function): 接收兩個參數,分別是當前的 state 樹和要處理的 action,返回新的 state 樹。
  • [ reloadedState ] (any):初始時的 state。
  • enhancer (Function):後面再講。

返回值:設計

  • getState:獲取store方法
  • dispatch:修改store方法
  • subscribe:訂閱store變化方法
  • replaceReducer:重置reducer方法

先來寫一個基礎的 createStore 以下:code

function createStore() {
  function getState() { }              // 取
  function dispatch() { }              // 存
  function subscribe() { }             // 訂閱
  function replaceReducer() { }        // 重置reducer
  return { getState, dispatch, subscribe, replaceReducer }
}

getState

getState 實現很簡單,直接返回 currentState。對象

function createStore() {
  let currentState = {};               // 數據
  function getState() {                // 取
    return currentState;
  }
  function dispatch() { }              // 存
  function subscribe() { }             // 訂閱
  function replaceReducer() { }        // 重置reducer
  return { getState, dispatch, subscribe, replaceReducer }
}

dispatch

dispatch 傳入 action,經過 action.type 區別操做。

function createStore() {
  let currentState = {};
  function getState() {                // 取
    return currentState;
  }
  function dispatch(action) {          // 存
    switch (action.type) {
      case 'PLUS':
        currentState = {
          ...currentState,
          count: currentState.count + 1,
        };
    }
    return action;
  }
  function subscribe() { }             // 訂閱
  function replaceReducer() { }        // 重置reducer
  return { getState, dispatch, subscribe, replaceReducer }
}

由於 Redux 要通用,因此 dispatch 內和業務相關的代碼要提取出來,Redux 給它起了個名字,叫 reducer。

提取reducer,

const initialState = {
  count: 0,
}
export default (state = initialState, action) => {
  switch (action.type) {
    case 'PLUS':
      return {
        ...state,
        count: state.count + 1,
      }
    case 'MINUS':
      return {
        ...state,
        count: state.count - 1,
      }
    default:
      return state
  }
}

給 createStore 添加兩個參數 reducer, preloadedState。

preloadedState非必傳,若是不傳,currentState 默認值就是 undefined。

在 createStore 中添加初始化方法 dispatch({ type: '@@redux/INIT' }) ; 初始化的 action.type 必須是 reducer 中沒有使用過的,Redux 源碼中使用了 '@@redux/INIT'。初始化方法會執行一次 dispatch。

初始化時,若是 currentState 是 undefined, 那麼在 reducer 中, state = initialState 會把 initialState 賦值給 state,而後經過 default return 出去, 最後修改 currentState。至關於 currentState = initialState。

最後 createStore 以下

function createStore(reducer, preloadedState) {
  let currentState = preloadedState;
  function getState() {                // 取
    return currentState;
  }
  function dispatch(action) {          // 存
    currentState = reducer(currentState, action);
    return action;
  }
  function subscribe() { }             // 訂閱
  function replaceReducer() { }        // 重置reducer
  dispatch({ type: '@@redux/INIT' });  // 初始化
  return { getState, dispatch, subscribe, replaceReducer }
}

根據代碼能夠看出,reducer 和 action 都是開發者自定義的,Redux 只是把 reducer 返回的 state 賦值給了 currentState,那麼開發者自定義其餘格式的action ,而且在 reducer 中做出對應的解析,而後返回 state,固然也是徹底能夠的。只是 Redux 統一了這種寫法,下降了個性化帶來的開發成本。

實際上 createStore 還有第三個參數 enhancer,目前用不到,後面再講。

subscribe

subscribe 有一個參數 listener (Function): 每當 dispatch action 的時候都會執行的回調。

subscribe 使用了設計模式中的 發佈-訂閱模式,又叫 觀察者模式。

實現:

  • 在 createStore 中添加一個儲存 變化監聽器 的數組 currentListeners;
  • subscribe 將 變化監聽器 放入 currentListeners;
  • 每次 dispatch 時, 循環執行 currentListeners 中的 變化監聽器。
function createStore(reducer, preloadedState) {
  let currentState = preloadedState;
  let currentListeners = [];
  function getState() {                // 取
    return currentState;
  }
  function dispatch(action) {          // 存
    currentState = reducer(currentState, action);
    currentListeners.forEach(fn => fn());
    return action;
  }
  function subscribe(listener) {       // 訂閱
    currentListeners.push(listener);
  }
  function replaceReducer() { }        // 重置reducer
  dispatch({ type: '@@redux/INIT' });  // 初始化
  return { getState, dispatch, subscribe, replaceReducer }
}

replaceReducer

重置 reducer, 並不會重置 currentState。

實現:

  • 添加變量 currentReducer;
  • dispatch 使用 currentReducer;
  • replaceReducer 方法將 nextReducer 賦值給 replaceReducer, 而後執行 dispatch({ type: '@@redux/INIT' })

注意:實際上,replaceReducer 中的 dispatch({ type: '@@redux/INIT' }),只有此時 currentState 是 undefined 時,纔有做用,會把新的 initialState 賦值給 currentState。

function createStore(reducer, preloadedState) {
  let currentReducer = reducer
  let currentState = preloadedState;
  let currentListeners = [];
  function getState() {  // 取
    return currentState;
  }
  function dispatch(action) {  // 存
    currentState = currentReducer(currentState, action);
    currentListeners.forEach(fn => fn());
    return action;
  }
  function subscribe(listener) {  // 發佈訂閱
    currentListeners.push(listener);
  }
  function replaceReducer(nextReducer) {  // 重置reducer
    currentReducer = nextReducer;
    dispatch({ type: '@@redux/INIT' });  // 重置
  }
  dispatch({ type: '@@redux/INIT' });  // 初始化
  return { getState, dispatch, subscribe, replaceReducer }
}

createStore 的實現到這裏已經完成,Redux 源碼除此以外還作了大量的錯誤校驗。


combineReducers

隨着項目愈來愈大,把 reducer 放在一個文件裏寫會愈來愈臃腫,因而 Redux 提供了 combineReducers 方法。

先來看下如何使用

rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// rootReducer 將返回以下的 state 對象
{
  potato: {
    // ... potatoes, 和一些其餘由 potatoReducer 管理的 state 對象 ...
  },
  tomato: {
    // ... tomatoes, 和一些其餘由 tomatoReducer 管理的 state 對象,好比說 sauce 屬性 ...
  }
}

combineReducers 參數是 reducers 對象,返回一個合成後的 reducer。

實現邏輯比較簡單,循環把 reducers 裏的每個 reducer 都執行, 執行結果放在 nextState 裏,若是數據改變了就返回 nextState,若是數據沒有改變就返回傳入的 state。

注意:若是數據沒有改變,返回的是傳入的 state,雖然此時和 nextState 數據是同樣的,可是實際地址並不同。爲了區分,Redux 特地用了 hasChanged 變量來記錄。

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);  // key[]
  return function combination(state = {}, action) {
    let hasChanged = false;  // state 是否改變
    const nextState = {};  // 改變後的 state

    // 循環 reducers
    reducerKeys.forEach(key => {
      const reducer = reducers[key];  // 當前 reducer
      const previousStateForKey = state[key];  // 當前 state
      const nextStateForKey = reducer(previousStateForKey, action);  // 若是 沒有匹配到action.type,會在 reducer 中的 switch default 返回傳入的 state,即 previousStateForKey
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    })

    return hasChanged ? nextState : state;
  }
}

bindActionCreators

bindActionCreators(actionCreators, dispatch) 把一個 value 爲不一樣 action creator 的對象,轉成擁有同名 key 的對象。

action 生成器名字叫作叫 action creator, 以下

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

修改數據須要這樣寫

dispatch(addTodo('Use Redux'))

若是咱們多個 action creator,寫起來會比較繁瑣,

dispatch(addTodo('Use Redux'))
dispatch(plusTodo())
dispatch(setDataTodo({ id: 1 }))

因此 Redux 提供了 bindActionCreators 函數,傳入 action creators 和 dispatch, 返回綁定了 dispatch 的 action creators。

實現也很簡單,遍歷 actionCreators, 把每一個元素用 dispatch 處理後生成新的函數,返回新函數的集合。

actionCreators 參數是 action creator 的集合對象,如 { addTodo, addTodo1 }。實現代碼以下:

function bindActionCreators(actionCreators, dispatch) {
  const boundActionCreators = {};
  Object.keys(actionCreators).forEach(key => {
    const actionCreator = actionCreators[key];
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = (...args) => dispatch(actionCreator(...args));
    }
  })
  return boundActionCreators;
}

使用 bindActionCreators 寫起來就會方便不少

const boundActionCreators = bindActionCreators({
  addTodo,
  plusTodo,
  setDataTodo,
}, dispatch);

// 寫入數據
boundActionCreators.addTodo('Use Redux')
boundActionCreators.plusTodo()
boundActionCreators.addTodo({ id: 1 })

Redux 支持 actionCreators 是一個單個 action creator 的函數,因此提取公共方法。改造以下:

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args));
}

function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators = {};
  Object.keys(actionCreators).forEach(key => {
    const actionCreator = actionCreators[key];
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    }
  })
  return boundActionCreators;
}

compose

從右到左來組合多個函數。

先來看看源碼:

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

最後一行很難理解,把它換成function寫法以下

funcs.reduce(function (a, b) {
  return function (...args) {
    return a(b(...args))
  }
})

先看下reduce方法

reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
// 從左到右爲每一個數組元素執行一次回調函數,並把上次回調函數的返回值放在一個暫存器中傳給下次回調函數,並返回最後一次回調函數的返回值。

previousValue 上次循環的返回值

currentValue 當前循環item

因此第二次循環過程以下

// 第一次循環返回值爲
function (...args) {
  return a(b(...args))
}

// 第二次循環時,第一個參數爲:第一次循環的返回值,第二個參數爲:funcs 內第三個元素,用c來表示
// 第二次循環返回值爲
function (...args) {
  return (function (...args) {
    return a(b(...args))
  })(c(...args))
}
// 整理後
function (...args) {
  return a(b(c(...args)))
}

因此 [a, b, c, d, e] 的執行結果是 (...args) => a(b(c(d(e(...args)))))

因此能看出來,funcs 內函數須要知足 函數參數和函數返回值結構一致。


applyMiddleware

applyMiddleware 是把 dispatch 一層一層包裝。洋蔥圈模型。

先看看 createStore 的第三個參數 enhancer

function createStore(reducer, preloadedState, enhancer) {
  // 實現了 preloadedState 參數能夠省略
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    // 看起來 enhancer 是個高階函數,返回值仍是 store creator
    // 能夠看出 enhancer 的大概結構爲
    // (createStore) => (reducer, preloadedState) => createStore(educer, preloadedState)
    return enhancer(createStore)(reducer, preloadedState)
  }

  // 這裏是其餘代碼
  // ...
}

再看看官網給的 applyMiddleware 使用例子

let store = createStore(
  todos,
  [ 'Use Redux' ],
  applyMiddleware(logger)
)

因此 applyMiddleware 的結構應該是

(...middlewares) => (createStore) => (reducer, preloadedState) => createStore(educer, preloadedState)

因此猜出來了 applyMiddleware 的參數是函數,返回值執行屢次後仍是 createStore(educer, preloadedState)。

因此再來看官方定義就比較好理解

Middleware 可讓你包裝 store 的 dispatch 方法來達到你想要的目的。同時, middleware 還擁有「可組合」這一關鍵特性。多個 middleware 能夠被組合到一塊兒使用,造成 middleware 鏈。其中,每一個 middleware 都不須要關心鏈中它先後的 middleware 的任何信息。

來看 applyMiddleware 看源碼, 跟着 序號看會稍微清晰點:

applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }

    // 二、chain內元素結構爲 (store.dispatch) => store.dispatch
    //    因此 middleware(middlewareAPI) 結果爲 (store.dispatch) => store.dispatch
    //    因此 middleware 結構爲 (middlewareAPI) => (store.dispatch) => store.dispatch
    //    即 參數 middlewares 內元素結構爲 (middlewareAPI) => (store.dispatch) => store.dispatch
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    // 一、上面解釋過 compose 的返回值是 (...arg) => a(b(c(...arg))),
    //    因此下面 dispatch = ((...arg) => a(b(c(...arg))))(store.dispatch)
    //    即 dispatch = a(b(c(store.dispatch)))
    //    因此 a、b、c 即 chain內元素 的結構須要爲 (store.dispatch) => store.dispatch
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch  // 這裏能夠看出,applyMiddleware 只包裝替換了 createStore 的 dispatch
    }
  }
}

如今咱們知道了 applyMiddleware 的參數結構是 (middlewareAPI) => (store.dispatch) => store.dispatch,而後咱們來寫個簡單的 middleware

// 原始長這個樣子
function logger(middlewareAPI) {
  return (dispatch) => dispatch;
}

// 而後 給 dispatch 包裝如下,而且換個名字叫 next
function logger(middlewareAPI) {
  return (next) => (action) => {
    let value = next(action);
    return value;
  };
}

// 而後 加入功能
function logger(middlewareAPI) {
  return (next) => (action) => {
    // 這裏的 dispatch 是 createStore 建立的。通常不用。
    const { getState, dispatch } = middlewareAPI;

    console.log('will dispatch', action);

    let value = next(action);

    console.log('state after dispatch', getState());

    // createStore 裏實現的 dispatch 返回 action,
    // 通常會是 action 自己,除非
    // 後面的 middleware 修改了它。
    return value;
  };
}

最後再來回味下 applyMiddleware 的這幾個結構

// compose
([a, b, c, d, e]) => (...args) => a(b(c(d(e(...args)))))

// applyMiddleware
(...middlewares) => (createStore) => (reducer, preloadedState) => createStore(educer, preloadedState)

// middleware
(middlewareAPI) => (dispatch) => dispatch
相關文章
相關標籤/搜索