Redux 實現過程的推演

這是一篇淺入淺出的 Redux 實現過程的推演筆記!正常來講應該是要從源碼下手開始解析,這裏是逆向推演,假若有需求是要這麼一個東西,那麼該如何從零開始實現?

 

經過該筆記,更多的是但願本身可以多熟悉從無到有的開發思惟,而非源碼解析這種從有到有的輪子思惟。
 
Rudex 介紹
首先確認目標,要寫個什麼樣的東西。

官宣:A predictable state container for JavaScript apps. JavaScript 應用程序中可預測的狀態容器。

經過這句話,挖掘一些關鍵點:
  - 1. 要有個狀態:state
  - 2. 要有個(狀態)容器:store
  - 3. 在狀態改變的流程中,狀態是可預測的,即:
    - 3.1 什麼時候觸發狀態進行改變? dispatch,觸發 state 的改變
    - 3.2 觸發了什麼改變? action,描述 state 該作什麼修改
    - 3.3 狀態作什麼樣改變? reducer,描述 state 的改變過程,傳入改變前的 state,返回改變後的 state

reducer 是個純函數很重要,可以消除其餘影響讓 state 的變化真正是可預測的。

有了這些關鍵點,接下來就是實現了
 
Redux 實現
被控對象(state)被包含在整個鏈路中,咱們關心鏈路便可
 
既然是(狀態)容器,那就得先有個容器,先實現個函數去建立容器,而且容器拋出方法以支持對容器內狀態進行操做。
/*
 * createStore 狀態容器
 * @param reducers 容器總得須要知道是作什麼樣的改變
 * @param initialState 初始化(預置)的 state
 */

const createStore = (reducers, initialState) => {
  // 經過 reducer 取到改變後的值,從新存儲
  let currentReducer = reducers;

  // 存
  let currentState = initialState;

  // 取
  const getState = () => {
    return currentState;
  };

  // 改
  const dispatch = action => {
    currentState = currentReducer(currentState, action);
    return action;
  };

  // 這裏可能還須要可被觀察的,留坑、不實現,有興趣的看文章後的源碼閱讀連接
  return {
    getState,
    dispatch
  };
};
至此,容器部分完成。
 
接下來看改變流程可預測的實現:
action 描述 state 該作什麼修改,這僅僅是個對象而已,咱們僅須要定義好格式,以下例子(好比數字的重置)
/*
 * action
 * @property type 描述須要作什麼操做
 * @property preload 預加載數據,包含 state 的新值
 */

const RESET = "RESET";
const RESET_ACTION = {
  type: RESET,
  preload: {
    count: 0
  }
};

 

reducer 描述狀態作了什麼改變,或者說是改變的流程。
/*
 * reducer
 * @currentState 當前的 state,舊值
 * @action 該作什麼修改的類型描述
 */

const reducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case RESET: {
      return {
        ...state,
        ...action.preload
      };
    }
    default: {
      return state;
    }
  }
};
 
好了,先將上面三份代碼合一塊兒,試一試
const store = createStore(reducer);
store.dispatch({ type: RESET, preload: { count: 10 } });
store.getState(); // output { count: 10}
store.dispatch(RESET_ACTION);
store.getState(); // output { count: 0}

流程是正常了,但狀況稍微有點不對,若是在 dispatch 以前去 getState,那麼 state 是 {},而不是給的初始值{count:0}。對於這點,只要在 createStore 時候的默認執行一次 dispatch,用以生成初始的 state tree。
 
在 createStore 的 return 以前加入 dispatch
dispatch({ type: "@redux/INIT" });
 
ok,至此已經有個簡版 redux 了(observable 和 subscribe 的話,加個 listeners,這裏很少作介紹,有興趣的點擊文末連接),可是 redux 還有 middleware 的功能(並且這部分代碼會比這裏好玩一點),繼續實現 middleware
 
Middleware 實現
首先,仍是先來明確一下但願 middleware 可以幫助咱們作什麼?
官宣:
Middleware is the suggested way to extend Redux with custom functionality. Middleware lets you wrap the store's dispatch method for fun and profit. The key feature of middleware is that it is composable. Multiple middleware can be combined together, where each middleware requires no knowledge of what comes before or after it in the chain.
翻譯一下:
Middleware 是經過自定義功能來擴展 redux 的推薦方法,它可以讓你有效的包裹 store 的 dispatch 方法已達到所需的目的,其關鍵特徵在於組合,多個 middleware 可以進行組合,每一個 middleware 都是獨立的,它們不須要知道在流程的以前或以後會發生什麼。
從上面的描述中,得出結論:
  - 首先,middleware 要是個 function 且該函數對 dispatch 的執行作包裹;
  - 而後,每一個 middleware 互不相干且可組合的;
  - 最後,值得注意的是 middleware 內部可以訪問及操做 state,否則只能作些和 state 不相干的事情,那這個擴展的意義就不大了。
 
接下來是推演實現的過程:
 
首先,咱們須要考慮的是怎麼處理每一個函數,讓其既是獨立的,又是可組合的,並且內部還得包裹 dispatch。看下面的思考過程:
 
好比我有個函數 a 和函數 dispatch 咱們但願執行的過程是 dispatch 被包裹在 a 內部執行,首先想到的確定是 callback 形式,沒毛病,看代碼
var a = function(next) {
  console.log("a-before");
  next();
  console.log("a-after");
};
var dispatch = function(action) {
  console.log("do ", action);
  return action;
};

a(dispatch);
// output:
// a-before
// do undefined
// a-after
可是沒有可以把 dispatch 的參數傳進去呀,因而對於內部函數的傳參,不陌生的,咱們又想到,外包再套一層 function,閉包存參(用 jqyery 的時候綁定事件沒少這麼幹吧),看代碼:
var a = function(next) {
  return function(action) {
    console.log("a-before");
    next(action);
    console.log("a-after");
  };
};
var dispatch = function(action) {
  console.log("do ", action);
  return action;
};

a(dispatch)("test action");
// output:
// a-before
// do test action
// a-after
但若是 a 的這種類型的包裹函數是多個的,試下加個函數 b,由於要嵌套的是函數,因此將 action 做爲第二次執行的參數
var a = function(next) {
  return function(action) {
    console.log("a-before");
    next(action);
    console.log("a-after");
  };
};
var b = function(next) {
  return function(action) {
    console.log("b-before");
    next(action);
    console.log("b-after");
  };
};
var dispatch = function(action) {
  console.log("do ", action);
  return action;
};

a(b(dispatch))("test action");
// output:
// a-before
// b-before
// do test action
// b-after
// a-after
 
而後問題又來了 ,咱們要再加個函數 c,難道讓我寫 a(b(c(dispacth)))(action)?
既然函數 a, b, c 都是一種類型的東西,能夠格式化成數組,回想一下什麼方法可以依次組合數組的每一項。沒錯,是 reduce,繼續看代碼:
var a = function(next) {
  return function(action) {
    console.log("a-before");
    next(action);
    console.log("a-after");
  };
};
var b = function(next) {
  return function(action) {
    console.log("b-before");
    next(action);
    console.log("c-after");
  };
};
var c = function(next) {
  return function(action) {
    console.log("c-before");
    next(action);
    console.log("c-after");
  };
};
var dispatch = function(action) {
  console.log("do ", action);
  return action;
};

var d = [a, b, c].reduce((pre, now) => (...args) => pre(now(...args)));

d(dispatch)("test action");
// output:
// a-before
// b-before
// c-before
// do test action
// c-after
// b-after
// a-after
 
好了,想到了如何將 middleware 串起來和如何將 dispatch 封裝的方法後,集成到 redux 的代碼裏試試

單獨抽一個 compose 函數用以處理一個或多個 middleware,代碼以下:
const 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)));
};
 
修改 createStore 裏的 dispatch 方法,讓其支持當存在 middleware 的時候,dispatch 須要被重寫。代碼以下:
/*
 * createStore 狀態容器
 * @param reducers 容器總得須要知道是作什麼樣的改變
 * @param initialState 初始化(預置)的 state
 * @param enhancer 擴展的 middlewares
 */

const createStore = (reducers, initialState, enhancer) => {
  // 參數互換 若是 initialState 是個函數,enhancer = undefined 則 enhancer 和 initialState 互換
  if (typeof initialState === "function" && typeof enhancer === "undefined") {
    enhancer = initialState;
    initialState = undefined;
  }

  // 若是有 middleware 的時候,則 createStore 稍後處理,處理詳情參照 applyMiddleware 函數
  if (typeof enhancer !== "undefined" && typeof enhancer === "function") {
    // 爲何是這樣寫? 繼續往下看
    return enhancer(createStore)(reducer, initialState);
  }

  // ...
  // 以前的代碼
};
 
結合 createStore,注意到前文提出的,middleware 內部支持訪問和操做 state,咱們須要實現 createStore 裏面的 enhancer 函數,就是函數 applyMiddleware,因而給出代碼:
/*
 * applyMiddleware 實現中間件的應用
 * @param ...middlewares 插入的 state 處理流程的中間件
 */
const applyMiddleware = (...middlewares) => {
  // 傳入 middlewares
  return createStore => (...args) => {
    const store = createStore(...args);
    // middleware 內部能作的 state 操做
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    };
    // 將 middleware 處理,以 middlewareAPI 做爲參數執行而且取到 middleware 的內部函數
    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    // 進行 compose 組合
    // 如存在 3 個 middleware A(ABefore,AAfter) B(BBefore,BAfter) C(CBefore,CAfter)
    // 則執行順序是 ABefore - BBefore - CBefore - (真實的操做) - CAfter - BAfter - AAfter
    dispatch = compose(...chain)(store.dispatch);

    return {
      ...store,
      dispatch
    };
  };
};
 
核心實現的代碼寫完了,而後測試一下?(注意:測試的時候須要在 createStore 的 dispatch 里加個 "do dispatch" 的 log,方便看執行流程。)
const logger = ({ getState }) => {
  return next => action => {
    console.log("will dispatch", action);

    const returnValue = next(action);

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

    return returnValue;
  };
};

const store = createStore(reducer, applyMiddleware(logger));

store.dispatch(RESET_ACTION);
// output
// will dispatch {type: "RESET", preload: {…}}
// do dispatch
// state after dispatch {count: 0}
 
好了,來回顧一下實現過程,首先有個目標:作一個可預測的狀態容器;而後分析目標,挖掘關鍵點,依次實現。實現的過程更多在於"語言運用的規範",技術知識點方面好像確實都是一些基礎的運用哦。

最後,本文僅解析 redux 的實現思路,代碼與源碼並不徹底相同。redux 不止這些代碼(但其實也沒多少其他代碼),還有一些斷言、錯誤提示、開發提示、bindActionCreators 啥的操做等等...
 
相關文章
相關標籤/搜索