99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Redux Middleware

2021-09-16 10:06 更新

Middleware

我們已經(jīng)在異步Actions一節(jié)的示例中看到了一些 middleware 的使用。如果你使用過(guò) Express 或者 Koa 等服務(wù)端框架, 那么應(yīng)該對(duì) middleware 的概念不會(huì)陌生。 在這類框架中,middleware 是指可以被嵌入在框架接收請(qǐng)求到產(chǎn)生響應(yīng)過(guò)程之中的代碼。例如,Express 或者 Koa 的 middleware 可以完成添加 CORS headers,記錄日志,內(nèi)容壓縮等工作。middleware 最優(yōu)秀的特性就是可以被鏈?zhǔn)浇M合。你可以在一個(gè)項(xiàng)目中使用多個(gè)獨(dú)立的第三方 middleware。

相對(duì)于 Express 或者 Koa 的 middleware,Redux middleware 被用于解決不同的問(wèn)題,但其中的概念是相類似的。它提供的是位于 action 被發(fā)起之后,到達(dá) reducer 之前的擴(kuò)展點(diǎn)。 你可以利用 Redux middleware 來(lái)進(jìn)行日志記錄、創(chuàng)建崩潰報(bào)告、調(diào)用異步接口或者路由等等。

這個(gè)章節(jié)分為兩個(gè)部分,前面是幫助你理解相關(guān)概念的深度介紹,而后半部分則通過(guò)一些實(shí)例來(lái)體現(xiàn) middleware 的強(qiáng)大能力。對(duì)文章前后內(nèi)容進(jìn)行結(jié)合通讀,會(huì)幫助你更好的理解枯燥的概念,并從中獲得啟發(fā)。

理解 Middleware

正因?yàn)?middleware 可以完成包括異步 API 調(diào)用在內(nèi)的各種事情,了解它的演化過(guò)程是一件相當(dāng)重要的事。我們將以記錄日志和創(chuàng)建崩潰報(bào)告為例,引導(dǎo)你體會(huì)從分析問(wèn)題到通過(guò)構(gòu)建 middleware 解決問(wèn)題的思維過(guò)程。

問(wèn)題: 記錄日志

使用 Redux 的一個(gè)益處就是它讓 state 的變化過(guò)程變的可預(yù)知和透明。每當(dāng)一個(gè) action 發(fā)起完成,新的 state 就會(huì)被計(jì)算并保存下來(lái)。State 不能被自身修改,只能由特定的 action 引起變化。

試想一下,當(dāng)我們的應(yīng)用中每一個(gè) action 被發(fā)起以及每次新的 state 被計(jì)算完成時(shí)都將它們記錄下來(lái),豈不是很好?當(dāng)程序出現(xiàn)問(wèn)題時(shí),我們可以通過(guò)查閱日志找出是哪個(gè) action 導(dǎo)致了 state 不正確。

我們?nèi)绾瓮ㄟ^(guò) Redux 實(shí)現(xiàn)它呢?

嘗試 #1: 手動(dòng)記錄

最直接的解決方案就是在每次調(diào)用 store.dispatch(action) 前后手動(dòng)記錄被發(fā)起的 action 和新的 state。這稱不上一個(gè)真正的解決方案,僅僅是我們理解這個(gè)問(wèn)題的第一步。

注意

如果你使用 react-redux 或者類似的綁定庫(kù),最好不要直接在你的組件中操作 store 的實(shí)例。在接下來(lái)的內(nèi)容中,僅僅是假設(shè)你會(huì)顯式的向下傳遞 store。

假設(shè),你在創(chuàng)建一個(gè) Todo 時(shí)這樣調(diào)用:

store.dispatch(addTodo('Use Redux'));

為了記錄這個(gè) action 一句產(chǎn)生的新的 state,你可以通過(guò)這種方式記錄日志:

let action = addTodo('Use Redux');

console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());

這樣做達(dá)到了想要的效果,但是你一定不想每次都這么干。

嘗試 #2: 封裝 Dispatch

你可以將上面的操作抽取成一個(gè)函數(shù):

function dispatchAndLog(store, action) {
  console.log('dispatching', action);
  store.dispatch(action);
  console.log('next state', store.getState());
}

然后用它替換 store.dispatch():

dispatchAndLog(store, addTodo('Use Redux'));

你可以選擇到此為止,但是每次都要導(dǎo)入一個(gè)外部方法總歸還是不太方便。

嘗試 #3: Monkeypatching Dispatch

如果我們直接替換 store 實(shí)例中的 dispatch 函數(shù)會(huì)怎么樣呢?Redux store 只是一個(gè)包含一些方法的普通對(duì)象,同時(shí)我們使用的是 JavaScript,因此我們可以這樣實(shí)現(xiàn) dispatch 的 monkeypatch:

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
};

這離我們想要的已經(jīng)非常接近了!無(wú)論我們?cè)谀睦锇l(fā)起 action,保證都會(huì)被記錄。Monkeypatching 令人感覺還是不太舒服,但是利用它我們做到了我們想要的。

問(wèn)題: 崩潰報(bào)告

如果我們想對(duì) dispatch 附加超過(guò)一個(gè)的變換,又會(huì)怎么樣呢?

我腦海中出現(xiàn)的另一個(gè)常用的變換就是在生產(chǎn)過(guò)程中報(bào)告 JavaScript 的錯(cuò)誤。全局的 window.onerror 并不可靠,因?yàn)樗谝恍┡f的瀏覽器中無(wú)法提供錯(cuò)誤堆棧,而這是排查錯(cuò)誤所需的至關(guān)重要信息。

試想當(dāng)發(fā)起一個(gè) action 的結(jié)果是一個(gè)異常時(shí),我們將包含調(diào)用堆棧,引起錯(cuò)誤的 action 以及當(dāng)前的 state 等錯(cuò)誤信息通通發(fā)到類似于 Sentry 這樣的報(bào)告服務(wù)中,不是很好嗎?這樣我們可以更容易的在開發(fā)環(huán)境中重現(xiàn)這個(gè)錯(cuò)誤。

然而,將日志記錄和崩潰報(bào)告分離是很重要的。理想情況下,我們希望他們是兩個(gè)不同的模塊,也可能在不同的包中。否則我們無(wú)法構(gòu)建一個(gè)由這些工具組成的生態(tài)系統(tǒng)。(提示:我們正在慢慢了解 middleware 的本質(zhì)到底是什么?。?/p>

如果按照我們的想法,日志記錄和崩潰報(bào)告是分離的工具,他們看起來(lái)應(yīng)該像這樣:

function patchStoreToAddLogging(store) {
  let next = store.dispatch;
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action);
    let result = next(actionconsole.log('next state', store.getState()););
    console.log('next state', store.getState());
    return result;
  };
}

function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch;
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action);
    } catch (err) {
      console.error('捕獲一個(gè)異常!', err);
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      });
      throw err;
    }
  };
}

如果這些功能以不同的模塊發(fā)布,我們可以在 store 中像這樣使用它們:

patchStoreToAddLogging(store);
patchStoreToAddCrashReporting(store);

盡管如此,這種方式看起來(lái)還是不是夠令人滿意。

嘗試 #4: 隱藏 Monkeypatching

Monkeypatching 本質(zhì)上是一種 hack?!皩⑷我獾姆椒ㄌ鎿Q成你想要的”,那是 API 會(huì)是什么樣的呢?現(xiàn)在,讓我們來(lái)看看這種替換的本質(zhì)。 在之前,我們用自己的函數(shù)替換掉了 store.dispatch。如果我們不這樣做,而是在函數(shù)中返回新的 dispatch 呢?

function logger(store) {
  let next = store.dispatch;

  // 我們之前的做法:
  // store.dispatch = function dispatchAndLog(action) {

  return function dispatchAndLog(action) {
    console.log('dispatching', action);
    let result = next(action);
    console.log('next state', store.getState());
    return result;
  };
}

我們可以在 Redux 內(nèi)部提供一個(gè)可以將實(shí)際的 monkeypatching 應(yīng)用到 store.dispatch 中的輔助方法:

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();

  // Transform dispatch function with each middleware.
  middlewares.forEach(middleware =>
    store.dispatch = middleware(store)
  );
}

然后像這樣應(yīng)用多個(gè) middleware:

applyMiddlewareByMonkeypatching(store, [logger, crashReporter]);

盡管我們做了很多,實(shí)現(xiàn)方式依舊是 monkeypatching。因?yàn)槲覀儍H僅是將它隱藏在我們的框架內(nèi)部,并沒(méi)有改變這個(gè)事實(shí)。

嘗試 #5: 移除 Monkeypatching

為什么我們要替換原來(lái)的 dispatch 呢?當(dāng)然,這樣我們就可以在后面直接調(diào)用它,但是還有另一個(gè)原因:就是每一個(gè) middleware 都可以操作(或者直接調(diào)用)前一個(gè) middleware 包裝過(guò)的 store.dispatch

function logger(store) {
  // 這里的 next 必須指向前一個(gè) middleware 返回的函數(shù):
  let next = store.dispatch;

  return function dispatchAndLog(action) {
    console.log('dispatching', action);
    let result = next(action);
    console.log('next state', store.getState());
    return result;
  };
}

將 middleware 串連起來(lái)的必要性是顯而易見的。

如果 applyMiddlewareByMonkeypatching 方法中沒(méi)有在第一個(gè) middleware 執(zhí)行時(shí)立即替換掉 store.dispatch,那么 store.dispatch 將會(huì)一直指向原始的 dispatch 方法。也就是說(shuō),第二個(gè) middleware 依舊會(huì)作用在原始的 dispatch 方法。

但是,還有另一種方式來(lái)實(shí)現(xiàn)這種鏈?zhǔn)秸{(diào)用的效果??梢宰?middleware 以方法參數(shù)的形式接受一個(gè) next() 方法來(lái),而不是通過(guò) store 的實(shí)例去獲取。

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action);
      let result = next(action);
      console.log('next state', store.getState());
      return result;
    };
  }
}

現(xiàn)在是“我們?cè)摳M(jìn)一步”的時(shí)刻了,所以可能會(huì)多花一點(diǎn)時(shí)間來(lái)讓它變的更為合理一些。這些層疊的函數(shù)讓人很恐慌。ES6 的 arrow functions 可以讓 currying 看起來(lái)更舒服一些:

const logger = store => next => action => {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
};

const crashReporter = store => next => action => {
  try {
    return next(action);
  } catch (err) {
    console.error('Caught an exception!', err);
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    });
    throw err;
  }
}

這已經(jīng)是真實(shí)的 Redux middleware 的樣子了。

Middleware 接受一個(gè) next() 發(fā)起函數(shù),并返回一個(gè)發(fā)起函數(shù),返回的函數(shù)會(huì)被作為下一個(gè) middleware 的 next(),以此類推。由于 store 中類似 getState() 的方法依舊非常有用,我們將 store 作為頂層的參數(shù),使得它可以在所有 middleware 中被使用。

嘗試 #6: “青澀”地使用 Middleware

我們可以寫一個(gè) applyMiddleware() 方法替換掉原來(lái)的 applyMiddlewareByMonkeypatching()。在新的 applyMiddleware() 中,我們?nèi)〉米罱K完整的被包裝過(guò)的 dispatch() 函數(shù),并返回一個(gè) store 的副本:

// 警告:這只是一種樸素的實(shí)現(xiàn)
// 這 *并不是* Redux 的 API.

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();

  let dispatch = store.dispatch;
  middlewares.forEach(middleware =>
    dispatch = middleware(store)(dispatch)
  );

  return Object.assign({}, store, { dispatch });
}

這與 Redux 中 applyMiddleware() 的實(shí)現(xiàn)已經(jīng)很接近了,但是有三個(gè)重要的不同之處:

  • 它只暴露一個(gè) store API 的子集給 middleware:dispatch(action) and getState().

  • 它用了一個(gè)非常巧妙的方式來(lái)保證你的 middleware 調(diào)用的是 store.dispatch(action) 而不是 next(action),從而使這個(gè) action 會(huì)在包括當(dāng)前 middleware 在內(nèi)的整個(gè) middleware 鏈中被正確的傳遞。這對(duì)異步的 middleware 非常有用,正如我們?cè)?a href="#">之前的章節(jié)中看到的。

  • 為了保證你只能應(yīng)用 middleware 一次,它作用在 createStore() 上而不是 store 本身。因此它的簽名不是 (store, middlewares) => store, 而是 (...middlewares) => (createStore) => createStore

最終的方法

這是我們剛剛所寫的 middleware:

const logger = store => next => action => {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
};

const crashReporter = store => next => action => {
  try {
    return next(action);
  } catch (err) {
    console.error('Caught an exception!', err);
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    });
    throw err;
  }
}

然后是將它們引用到 Redux store 中:

import { createStore, combineReducers, applyMiddleware } from 'redux';

// applyMiddleware 接收 createStore() 
// 并返回一個(gè)包含兼容 API 的函數(shù)。
let createStoreWithMiddleware = applyMiddleware(logger, crashReporter)(createStore);

// 像使用 createStore() 一樣使用它。
let todoApp = combineReducers(reducers);
let store = createStoreWithMiddleware(todoApp);

就是這樣!現(xiàn)在任何被發(fā)送到 store 的 action 都會(huì)經(jīng)過(guò) logger and crashReporter

// 將經(jīng)過(guò) logger 和 crashReporter 兩個(gè) middleware!
store.dispatch(addTodo('Use Redux'));

7個(gè)示例

如果讀完上面的章節(jié)你已經(jīng)覺得頭都要爆了,那就想象以下它寫出來(lái)之后的樣子。下面的內(nèi)容會(huì)讓我們放松一下,并讓你的思路延續(xù)。

下面的每個(gè)函數(shù)都是一個(gè)有效的 Redux middleware。它們并不都一樣有用,但是至少他們一樣有趣。

/**
 * 記錄所有被發(fā)起的 action 以及產(chǎn)生的新的 state。
 */
const logger = store => next => action => {
  console.group(action.type);
  console.info('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  console.groupEnd(action.type);
  return result;
};

/**
 * 在 state 更新完成和 listener 被通知之后發(fā)送崩潰報(bào)告。
 */
const crashReporter = store => next => action => {
  try {
    return next(action);
  } catch (err) {
    console.error('Caught an exception!', err);
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    });
    throw err;
  }
}

/**
 * 用 { meta: { delay: N } } 來(lái)讓 action 延遲 N 毫秒。
 * 在這個(gè)案例中,讓 `dispatch` 返回一個(gè)取消 timeout 的函數(shù)。
 */
const timeoutScheduler = store => next => action => {
  if (!action.meta || !action.meta.delay) {
    return next(action);
  }

  let timeoutId = setTimeout(
    () => next(action),
    action.meta.delay
  );

  return function cancel() {
    clearTimeout(timeoutId);
  };
};

/**
 * 通過(guò) { meta: { raf: true } } 讓 action 在一個(gè) rAF 循環(huán)幀中被發(fā)起。
 * 在這個(gè)案例中,讓 `dispatch` 返回一個(gè)從隊(duì)列中移除該 action 的函數(shù)。
 */
const rafScheduler = store => next => {
  let queuedActions = [];
  let frame = null;

  function loop() {
    frame = null;
    try {
      if (queuedActions.length) {
        next(queuedActions.shift());
      }
    } finally {
      maybeRaf();
    }
  }

  function maybeRaf() {
    if (queuedActions.length && !frame) {
      frame = requestAnimationFrame(loop);
    }
  }

  return action => {
    if (!action.meta || !action.meta.raf) {
      return next(action);
    }

    queuedActions.push(action);
    maybeRaf();

    return function cancel() {
      queuedActions = queuedActions.filter(a => a !== action)
    };
  };
};

/**
 * 使你除了 action 之外還可以發(fā)起 promise。
 * 如果這個(gè) promise 被 resolved,他的結(jié)果將被作為 action 發(fā)起。
 * 這個(gè) promise 會(huì)被 `dispatch` 返回,因此調(diào)用者可以處理 rejection。
 */
const vanillaPromise = store => next => action => {
  if (typeof action.then !== 'function') {
    return next(action);
  }

  return Promise.resolve(action).then(store.dispatch);
};

/**
 * 讓你可以發(fā)起帶有一個(gè) { promise } 屬性的特殊 action。
 *
 * 這個(gè) middleware 會(huì)在開始時(shí)發(fā)起一個(gè) action,并在這個(gè) `promise` resolve 時(shí)發(fā)起另一個(gè)成功(或失?。┑?action。
 *
 * 為了方便起見,`dispatch` 會(huì)返回這個(gè) promise 讓調(diào)用者可以等待。
 */
const readyStatePromise = store => next => action => {
  if (!action.promise) {
    return next(action)
  }

  function makeAction(ready, data) {
    let newAction = Object.assign({}, action, { ready }, data);
    delete newAction.promise;
    return newAction;
  }

  next(makeAction(false));
  return action.promise.then(
    result => next(makeAction(true, { result })),
    error => next(makeAction(true, { error }))
  );
};

/**
 * 讓你可以發(fā)起一個(gè)函數(shù)來(lái)替代 action。
 * 這個(gè)函數(shù)接收 `dispatch` 和 `getState` 作為參數(shù)。
 *
 * 對(duì)于(根據(jù) `getState()` 的情況)提前退出,或者異步控制流( `dispatch()` 一些其他東西)來(lái)說(shuō),這非常有用。
 *
 * `dispatch` 會(huì)返回被發(fā)起函數(shù)的返回值。
 */
const thunk = store => next => action =>
  typeof action === 'function' ?
    action(store.dispatch, store.getState) :
    next(action);

// 你可以使用以上全部的 middleware?。ó?dāng)然,這不意味著你必須全都使用。)
let createStoreWithMiddleware = applyMiddleware(
  rafScheduler,
  timeoutScheduler,
  thunk,
  vanillaPromise,
  readyStatePromise,
  logger,
  crashReporter
)(createStore);
let todoApp = combineReducers(reducers);
let store = createStoreWithMiddleware(todoApp);
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)