真香~,咱也擼一個redux

從大學到如今用 React 開發差很少有1年多了,雖然仍是一個小菜鳥,不過 react 虐我千百遍,我仍待她如初戀嘻嘻。html

Redux 是咱使用的第一個狀態管理器,在學習的時候總以爲它是依賴於 react 的,還和 react-redux 搞混了。瞭解了官網以及查閱不少關於 redux 才知道其實 reduxreact 以及 react-redux 沒有什麼關係,由於它是原生 js 編寫的,因此任何框架均可以使用它。而react-reduxreactredux提供了更爲便捷的關聯方式,方便咱們維護;react

開始進入咱們的真香環節,若是有不怎麼熟悉 redux 建議先閱讀中文文檔redux

createStore

createStore 函數用於建立而且返回一個狀態操做對象,如下是對 store 對象的部分類型定義;api

interface action {
  type: string;
  [payload: string]: any;
}

type subscribe = (lister: Function) => Function

interface Store {
  dispatch: action;
  getState: Function;
  subscribe: subscribe;
}

store 對象主要有有getState,dispatchsubscribe這幾個 API。數組

  • getState 用於獲取最新的狀態
  • dispatch 用於派發action對象進行狀態更新
  • subscribe 用於監聽狀態變動

知道這幾個 API 的功能後咱們之一來實現吧~安全

/**
 * createStore
 * 建立 store 對象,並將對象返回
 * @param {(state:{[key:string]:any},action:{type: string,[key:string]:any}) => {[key:string]:any}} reducer
 * @param {Function} [middleware] 中間件
 * @returns {{dispatch: Function,getState: Function,subscribe: Function}}  state 操做對象
 */
function createStore(reducer) {
  // store 狀態
  let state;
  //監聽隊列
  let listers = [];

  /**
   * 獲取最新的 state
   * @returns {store} state
   */
  function getState() {
    const { parse, stringify } = JSON;
    // 爲了安全起見,返回深拷貝後的 state 對象,防止組件隨意增長屬性形成數據污染
    return parse(stringify(state));
  }

  /**
   * 發佈函數
   * 接受一個 action 對象
   * @param {{type: string,[key:string]:any}} action
   * @returns {{[key:string]: any}} action
   */
  function dispatch(action) {
    // 將 action 派發給 reducer 函數進行狀態處理,而且更新最新的 state
    state = reducer(state, action);

    // 狀態更新後還得執行如下咱們的監聽隊列,告訴他們咱們的 state 更新啦
    listers.forEach(observer => typeof observer === 'function' && observer());

    // 將這次分發的 action 返回出去
    return action;
  }

  /**
   * 訂閱函數
   * @param {Function} lister 監聽函數
   * @returns {Function} disconnect 註銷監聽
   */
  function subscribe(lister) {
    if (typeof lister !== 'function') {
      console.warn(
        'The Lister parameter of the subscribe function must be a function'
      );

      // 返回一個匿名函數,防止報錯
      return () => {
        // 順便在多提示幾下  ̄ω ̄=
        console.warn(
          'The Lister parameter of the subscribe function must be a function'
        );
      };
    }


    return function () {
    // 將監聽的數組從 listers (監聽隊列)移除掉
      listers = listers.filter(observer => observer !== lister);
    };
  }

  // 初始化 state ,派發一個私有的 action,避免重名影響到狀態誤改
  dispatch({ type: `CODE_ROOKIE_262@@${Date.now().toString(16)}` });

  return {
    dispatch,
    getState,
    subscribe
  };
}

以上基本實現了 createStoreapp

基本用法
// 定義一個 計數器 reducer
function reducer(state,action){
    switch(action.type){
        case 'add':
            return {...state,count: state.count + 1};
        case 'minus':
            return {...state,count: state.count - 1};
        default: 
            return state;
    }
}

// 建立 Store
const store = createStore(reducer);

// 訂閱狀態監聽
let observer = store.subscribe(function(){
    console.log('new state',store.getState())
});

// 派發 action 更改狀態
store.dispatch({
    type: 'add'
})

// 註銷監聽
observer()

combineReducers

有的時候咱們的狀態是有模塊區分的,就和 Vuex 同樣有多個 module 進行分開管理,避免多個狀態一同處於同一級形成數據會形成難以維護,因此咱們也須要對不一樣的狀態進行劃分,須要將reducer函數拆分紅多個單獨的函數對獨自的狀態狀態管控。可是上文中的 createStore 只接受一個 reducer,如何將多個 reducer 同時傳進入呢?框架

這個時候就是咱們大哥 combineReducers 的工做了,他接受一個對象,經過{reducer: reducerFun}形式的參數傳遞給 combineReducers,combineReducers接收到參數後會返回一個reducer函數,將這個函數傳遞給 createStore 便可。函數

/**
 * 合併多個 reducer 函數
 * @param   reducers {reducer1: reducer1,reducer2: reducer2,...}
 * @returns reducer
 */
function combineReducers(reducers) {
  return function reducer(state = {}, action) {
    let newState = {};
    // 更新每一個模塊的 state,而且將其最新狀態返回
    for (var key in reducers) {
      newState[key] = reducers[key](state[key], action);
    }

    return newState;
  };
}
使用方法
const count = function countReducer(state,action){...};
const list = function countReducer(state,action){...};

const reducer = combineReducers({
    count: count,
    list: list
})

const store = createStore(reducer);

applyMiddleware

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

以上是引用 redux 中文網學習

能夠看出 applyMiddleware 主要是經過 一個或者多個 middleware 包裝 store.dispatch

中間件的用法
// redux-logger 是打印狀態變動的一箇中間件
import logger from 'redux-logger';
import {createStore,applyMiddleware} from 'redux'

function reducer(state,action){}

let store = createStore(reducer,applyMiddleware(logger))

這個時候咱們得給個人上面的 createStore 函數進行一些參數的調整。

function createStore(reducer,middleware){
    //...other code
    // 安裝 middleware
    if(typeof middleware === 'function'){
        return middleware(createStore)(reducer)
    }
    
    return {
        getState: getState,
        dispatch: dispatch,
        subscribe: subscribe
    }
}

咱們先從單箇中間件middleware的思路入手更容易理解吧~

function applyMiddleware(middleware){
    return function(createStore){
        return function(reducer){
            // 建立 store
            let store = createStore(reducer);
            // 將 store 傳入中間件
            let middle = middleware(store);
            // 將 dispatch 傳入中間件返回的函數進行修飾,並返回
            let middleDispatch = middle(store.dispatch);
            return {
                ...store,
                dispatch: middleDispatch
            }
        }
    }
}

基本的中間件注入是解決了,可是applyMiddleware是支持多箇中間件的,也就是說最後返回的dispatch只有一個,而且是通過了多箇中間件修飾過的了。那麼就有一個難點,就是如何獲取最終的的dispatch呢?

例若有 middleware1middleware2,每一個函數都接受原有的dispatch後返回新的dispatch
能夠分解爲:

const dispatch1 = middleware1(dispatch);
const dispatch2 = middleware(dispatch11);

因此咱們是否是能夠這樣寫呢 => middleware2(middleware2(dispatch));

那咱們能夠封裝一函數 compose 將多個 middleware 組合成一個函數,這個函數接受一個的 dispatch並返回最終的 dispatch;

在此以前咱們能夠看下 redux 源碼中的處理,咱們能夠在 compose.js 中看到這樣一句代碼,很是巧妙。

funcs.reduce((a, b) => (...args) => a(b(...args)));

能夠看看簡化後的代碼

function compose(...funs) {
  return arg => {
    let res = arg;
    for (var i = 0, fl = funs.length; i < fl; i++) {
      // 接受返回值,而且將上一個函數的返回值傳遞給當前函數
      res = funs[i](res);
    }
    return res;
  };
}

看完後是否是煥然大悟了哈哈哈,固然用 數組的 reduce 還有另外的寫法喲´( ̄▽ ̄)~*

function compose(...funs) {
  return dispatch => funs.reduce((res, fun) => fun(res), dispatch);
}

關於插件合併的問題咱們基本解決了,接下來咱們再次修改一下咱們的 applyMiddleware 函數。

function applyMiddleware(...middleware){
    return function(createStore){
        return function(reducer){
            // 建立 store
            let store = createStore(reducer);
            // 將 store 傳入中間件
+            let middles = middleware.map(middle => middle(store));
            // 將 dispatch 傳入中間件返回的函數進行修飾,並返回
+            let middleDispatch = compose(...middles)(store.dispatch);
            return {
                ...store,
                dispatch: middleDispatch
            }
        }
    }
}

這樣就實現多個插件同時注入的問題了,也基本實現了redux的功能,在實現的過程當中雖然會遇到不少挫折,不過最終實現完成更讓人充滿動力哈哈哈Y(^o^)Y

相關文章
相關標籤/搜索