<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【重學React】從零開始手寫redux

          共 36108字,需瀏覽 73分鐘

           ·

          2021-09-16 02:19


          寫在開始之前:

          作為一個記性不太好的人,學完的東西總是很容易忘記,因此需要反反復復得去學習和理解,堅持記錄是一個很好的習慣,也有助于后期自己回顧。

          因水平有限,文章中部分內容可能存在錯誤,如果發(fā)現錯誤,歡迎在留言區(qū)指出。

          本篇文章對應的代碼在: https://github.com/YvetteLau/Blog/tree/master  建議先 clone 代碼,然后對照代碼閱讀本文。

          1. Redux 是什么?

          ReduxJavaScript 狀態(tài)容器,提供可預測化的狀態(tài)管理。Redux 除了和 React 一起用外,還支持其它界面庫。Redux 體小精悍,僅有 2KB。這里我們需要明確一點:ReduxReact 之間,沒有強綁定的關系。本文旨在理解和實現一個 Redux,但是不會涉及 react-redux(一次深入理解一個知識點即可,react-redux 將出現在下一篇文章中)。

          2. 從零開始實現一個 `Redux`

          我們先忘記 Redux 的概念,從一個例子入手,使用 create-react-app 創(chuàng)建一個項目: toredux。

          代碼地址: myredux/to-redux 中。

          public/index.htmlbody 修改為如下:

          <div id="app">
              <div id="header">
                  前端宇宙
              </div>
              <div id="main">
                  <div id="content">大家好,我是前端宇宙作者劉小夕</div>
                  <button class="change-theme" id="to-blue">Blue</button>
                  <button class="change-theme" id="to-pink">Pink</button>
              </div>
          </div>

          我們要實現的功能如上圖所示,在點擊按鈕時,能夠修改整個應用的字體的顏色。

          修改 src/index.js 如下(代碼: to-redux/src/index1.js):

          let state = {
              color'blue'
          }
          //渲染應用
          function renderApp({
              renderHeader();
              renderContent();
          }
          //渲染 title 部分
          function renderHeader({
              const header = document.getElementById('header');
              header.style.color = state.color;
          }
          //渲染內容部分
          function renderContent({
              const content = document.getElementById('content');
              content.style.color = state.color;
          }

          renderApp();

          //點擊按鈕,更改字體顏色
          document.getElementById('to-blue').onclick = function ({
              state.color = 'rgb(0, 51, 254)';
              renderApp();
          }
          document.getElementById('to-pink').onclick = function ({
              state.color = 'rgb(247, 109, 132)'
              renderApp();
          }

          這個應用非常簡單,但是它有一個問題:state 是共享狀態(tài),但是任何人都可以修改它,一旦我們隨意修改了這個狀態(tài),就可以導致出錯,例如,在 renderHeader 里面,設置 state = {}, 容易造成難以預料的錯誤。

          不過很多時候,我們又的確需要共享狀態(tài),因此我們可以考慮設置一些門檻,比如,我們約定,不能直接修改全局狀態(tài),必須要通過某個途徑才能修改。為此我們定義一個 changeState 函數,全局狀態(tài)的修改均由它負責。

          //在 index.js 中繼續(xù)追加代碼
          function changeState(action{
              switch(action.type) {
                  case 'CHANGE_COLOR':
                      return {
                          ...state,
                          color: action.color
                      }
                  default:
                      return state;
              }
          }

          我們約定只能通過 changeState 去修改狀態(tài),它接受一個參數 action,包含 type 字段的普通對象,type 字段用于識別你的操作類型(即如何修改狀態(tài))。

          我們希望點擊按鈕,可以修改整個應用的字體顏色。

          //在 index.js 中繼續(xù)追加代碼
          document.getElementById('to-blue').onclick = function({
              let state = changeState({
                  type'CHANGE_COLOR',
                  color'rgb(0, 51, 254)'
              });
              //狀態(tài)修改完之后,需要重新渲染頁面
              renderApp(state);
          }

          document.getElementById('to-pink').onclick = function({
              let state = changeState({
                  type'CHANGE_COLOR',
                  color'rgb(247, 109, 132)'
              });
              renderApp(state);
          }

          抽離 store

          盡管現在我們約定了如何修改狀態(tài),但是 state 是一個全局變量,我們很容易就可以修改它,因此我們可以考慮將其變成局部變量,將其定義在一個函數內部(createStore),但是在外部還需要使用 state,因此我們需要提供一個方法 getState(),以便我們在 createStore 獲取到 state。

          function createStore (state{
              const getState = () => state;
              return {
                  getState
              }
          }

          現在,我們可以通過 store.getState() 方法去獲取狀態(tài)(這里需要說明的是,state 通常是一個對象,因此這個對象在外部其實是可以被直接修改的,但是如果深拷貝 state 返回,那么在外部就一定修改不了,鑒于 redux 源碼中就是直接返回了 state,此處我們也不進行深拷貝,畢竟耗費性能)。

          僅僅獲取狀態(tài)是遠遠不夠的,我們還需要有修改狀態(tài)的方法,現在狀態(tài)是私有變量,我們必須要將修改狀態(tài)的方法也放到 createStore 中,并將其暴露給外部使用。

          function createStore (state{
              const getState = () => state;
              const changeState = () => {
                  //...changeState 中的 code
              }
              return {
                  getState,
                  changeState
              }
          }

          現在,index.js 中代碼變成下面這樣(to-redux/src/index2.js):

          function createStore({
              let state = {
                  color'blue'
              }
              const getState = () => state;
              function changeState(action{
                  switch (action.type) {
                      case 'CHANGE_COLOR':
                          state = {
                              ...state,
                              color: action.color
                          }
                          return state;
                      default:
                          return state;
                  }
              }
              return {
                  getState,
                  changeState
              }
          }

          function renderApp(state{
              renderHeader(state);
              renderContent(state);
          }
          function renderHeader(state{
              const header = document.getElementById('header');
              header.style.color = state.color;
          }
          function renderContent(state{
              const content = document.getElementById('content');
              content.style.color = state.color;
          }

          document.getElementById('to-blue').onclick = function ({
              store.changeState({
                  type'CHANGE_COLOR',
                  color'rgb(0, 51, 254)'
              });
              renderApp(store.getState());
          }
          document.getElementById('to-pink').onclick = function ({
              store.changeState({
                  type'CHANGE_COLOR',
                  color'rgb(247, 109, 132)'
              });
              renderApp(store.getState());
          }
          const store = createStore();
          renderApp(store.getState());

          盡管,我們現在抽離了 createStore 方法,但是顯然這個方法一點都不通用,statechangeState 方法都定義在了 createStore 中。這種情況下,其它應用無法復用此模式。

          changeState 的邏輯理應在外部定義,因為每個應用修改狀態(tài)的邏輯定然是不同的。我們將這部分邏輯剝離到外部,并將其重命名為 reducer (憋問為什么叫 reducer,問就是為了和 redux 保持一致)。reducer 是干嘛的呢,說白了就是根據 action 的類型,計算出新狀態(tài)。因為它不是在 createStore 內部定義的,無法直接訪問 state,因此我們需要將當前狀態(tài)作為參數傳遞給它。如下:

          function reducer(state, action{
              switch(action.type) {
                  case 'CHANGE_COLOR':
                      return {
                          ...state,
                          color: action.color
                      }
                  default:
                      return state;
              }
          }

          createStore 進化版

          function createStore(reducer{
              let state = {
                  color'blue'
              }
              const getState = () => state;
              //將此處的 changeState 更名為 `dispatch`
              const dispatch = (action) => {
                  //reducer 接收老狀態(tài)和action,返回一個新狀態(tài)
                  state = reducer(state, action);
              }
              return {
                  getState,
                  dispatch
              }
          }

          不同應用的 state 定然是不同的,我們將 state 的值定義在 createStore 內部必然是不合理的。

          function createStore(reducer{
              let state;
              const getState = () => state;
              const dispatch = (action) => {
                  //reducer(state, action) 返回一個新狀態(tài)
                  state = reducer(state, action);
              }
              return {
                  getState,
                  dispatch
              }
          }

          大家注意 reducer 的定義,在碰到不能識別的動作時,是直接返回舊狀態(tài)的,現在,我們利用這一點來返回初始狀態(tài)。

          要想 state 有初始狀態(tài),其實很簡單,咱們將初始的 state 的初始化值作為 reducer 的參數的默認值,然后在 createStore 中派發(fā)一個 reducer 看不懂的動作就可以了。這樣 getState 首次調用時,可以獲取到狀態(tài)的默認值。

          createStore 進化版2.0

          function createStore(reducer{
              let state;
              const getState = () => state;
              //每當 `dispatch` 一個動作的時候,我們需要調用 `reducer` 以返回一個新狀態(tài)
              const dispatch = (action) => {
                  //reducer(state, action) 返回一個新狀態(tài)
                  state = reducer(state, action);
              }
              //你要是有個 action 的 type 的值是 `@@redux/__INIT__${Math.random()}`,我敬你是個狠人
              dispatch({ type`@@redux/__INIT__${Math.random()}` });

              return {
                  getState,
                  dispatch
              }
          }

          現在這個 createStore 已經可以到處使用了, 但是你有沒有覺得每次 dispatch 后,都手動 renderApp() 顯得很蠢,當前應用中是調用兩次,如果需要修改1000次 state 呢,難道手動調用 1000次 renderApp() ?

          能不能簡化一下呢?每次數據變化的時候,自動調用 renderApp()。當然我們不可能將 renderApp() 寫在 createStore()dispatch 中,因為其它的應用中,函數名未必叫 renderApp(),而且有可能不止要觸發(fā) renderApp()。這里可以引入 發(fā)布訂閱模式,當狀態(tài)變化時,通知所有的訂閱者。

          createStore 進化版3.0

          function createStore(reducer{
              let state;
              let listeners = [];
              const getState = () => state;
              //subscribe 每次調用,都會返回一個取消訂閱的方法
              const subscribe = (ln) => { 
                  listeners.push(ln);
                  //訂閱之后,也要允許取消訂閱。
                  //難道我訂了某本雜志之后,就不允許我退訂嗎?可怕~
                  const unsubscribe = () => {
                      listeners = listeners.filter(listener => ln !== listener);
                  }
                  return unsubscribe;
              };
              const dispatch = (action) => {
                  //reducer(state, action) 返回一個新狀態(tài)
                  state = reducer(state, action);
                  listeners.forEach(ln => ln());

              }
              //你要是有個 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是個狠人
              dispatch({ type`@@redux/__INIT__${Math.random()}` });

              return {
                  getState,
                  dispatch,
                  subscribe
              }
          }

          至此,一個最為簡單的 redux 已經創(chuàng)建好了,createStoreredux 的核心。我們來使用這個精簡版的 redux 重寫我們的代碼,index.js 文件內容更新如下(to-redux/src/index.js):

          function createStore({
              //code(自行將上面createStore的代碼拷貝至此處)
          }
          const initialState = {
              color'blue'
          }

          function reducer(state = initialState, action{
              switch (action.type) {
                  case 'CHANGE_COLOR':
                      return {
                          ...state,
                          color: action.color
                      }
                  default:
                      return state;
              }
          }
          const store = createStore(reducer);

          function renderApp(state{
              renderHeader(state);
              renderContent(state);
          }
          function renderHeader(state{
              const header = document.getElementById('header');
              header.style.color = state.color;
          }
          function renderContent(state{
              const content = document.getElementById('content');
              content.style.color = state.color;
          }

          document.getElementById('to-blue').onclick = function ({
              store.dispatch({
                  type'CHANGE_COLOR',
                  color'rgb(0, 51, 254)'
              });
          }
          document.getElementById('to-pink').onclick = function ({
              store.dispatch({
                  type'CHANGE_COLOR',
                  color'rgb(247, 109, 132)'
              });
          }

          renderApp(store.getState());
          //每次state發(fā)生改變時,都重新渲染
          store.subscribe(() => renderApp(store.getState()));

          如果現在我們現在希望在點擊完 Pink 之后,字體色不允許修改,那么我們還可以取消訂閱:

          const unsub = store.subscribe(() => renderApp(store.getState()));
          document.getElementById('to-pink').onclick = function ({
              //code...
              unsub(); //取消訂閱
          }

          順便說一句: reducer 是一個純函數(純函數的概念如果不了解的話,自行查閱資料),它接收先前的 stateaction,并返回新的 state。不要問為什么 action 中一定要有 type 字段,這僅僅是一個約定而已(redux 就是這么設計的)

          遺留問題:為什么 reducer 一定要返回一個新的 state,而不是直接修改 state 呢。歡迎在評論區(qū)留下你的答案。

          前面我們一步一步推演了 redux 的核心代碼,現在我們來回顧一下 redux 的設計思想:

          Redux 設計思想

          • Redux 將整個應用狀態(tài)(state)存儲到一個地方(通常我們稱其為 store)

          • 當我們需要修改狀態(tài)時,必須派發(fā)(dispatch)一個 action( action 是一個帶有 type 字段的對象)

          • 專門的狀態(tài)處理函數 reducer 接收舊的 stateaction ,并會返回一個新的 state

          • 通過 subscribe 設置訂閱,每次派發(fā)動作時,通知所有的訂閱者。

          咱們現在已經有一個基礎版本的 redux 了,但是它還不能滿足我們的需求。我們平時的業(yè)務開發(fā)不會像上面所寫的示例那樣簡單,那么就會有一個問題: reducer 函數可能會非常長,因為 action 的類型會非常多。這樣肯定是不利于代碼的編寫和閱讀的。

          試想一下,你的業(yè)務中有一百種 action 需要處理,把這一百種情況編寫在一個 reducer 中,不僅寫得人惡心,后期維護代碼的同事更是想殺人。

          因此,我們最好單獨編寫 reducer,然后對 reducer 進行合并。有請我們的 combineReducers(和 redux 庫的命名保持一致) 閃亮登場~

          combineReducers

          首先我們需要明確一點:combineReducers 只是一個工具函數,正如我們前面所說,它將多個 reducer 合并為一個 reducercombineReducers 返回的是 reducer,也就是說它是一個高階函數。

          我們還是以一個示例來說明,盡管 redux 不是非得和 react 配合,不過鑒于其與 react 配合最為適合,此處,以 react 代碼為例:

          這一次除了上面的展示以外,我們新增了一個計數器功能( 使用 React 重構 ===> to-redux2):

          //現在我們的 state 結構如下:
          let state = {
              theme: {
                  color'blue'
              },
              counter: {
                  number0
              }
          }

          顯然,修改主題和計數器是可以分割開的,由不同的 reducer 去處理是一個更好的選擇。

          store/reducers/counter.js

          負責處理計數器的state。

          import { INCRENENT, DECREMENT } from '../action-types';

          export default counter(state = {number0}, action) {
              switch (action.type) {
                  case INCRENENT:
                      return {
                          ...state,
                          number: state.number + action.number
                      }
                  case DECREMENT:
                      return {
                          ...state,
                          number: state.number - action.number
                      }
                  default:
                      return state;
              }
          }

          store/reducers/theme.js

          負責處理修改主題色的state。

          import { CHANGE_COLOR } from '../action-types';

          export default function theme(state = {color: 'blue'}, action{
              switch (action.type) {
                  case CHANGE_COLOR:
                      return {
                          ...state,
                          color: action.color
                      }
                  default:
                      return state;
              }
          }

          每個 reducer 只負責管理全局 state 中它負責的一部分。每個 reducerstate 參數都不同,分別對應它管理的那部分 state 數據。

          import counter from './counter';
          import theme from './theme';

          export default function appReducer(state={}, action{
              return {
                  theme: theme(state.theme, action),
                  counter: counter(state.counter, action)
              }
          }

          appReducer 即是合并之后的 reducer,但是當 reducer 較多時,這樣寫也顯得繁瑣,因此我們編寫一個工具函數來生成這樣的 appReducer,我們把這個工具函數命名為 combineReducers

          我們來嘗試一下編寫這個工具函數 combineReducers:

          思路:

          1. combineReducers 返回 reducer

          2. combineReducers 的入參是多個 reducer 組成的對象

          3. 每個 reducer 只處理全局 state 中自己負責的部分

          //reducers 是一個對象,屬性值是每一個拆分的 reducer
          export default function combineReducers(reducers{
              return function combination(state={}, action{
                  //reducer 的返回值是新的 state
                  let newState = {};
                  for(var key in reducers) {
                      newState[key] = reducers[key](state[key], action);
                  }
                  return newState;
              }
          }

          reducer 將負責返回 state 的默認值。比如本例中,createStore 中 dispatch({type:@@redux/__INIT__${Math.random()}}),而傳遞給 createStore 的是 combineReducers(reducers) 返回的函數 combination。

          根據 state=reducer(state,action),newState.theme=theme(undefined, action), newState.counter=counter(undefined, action),countertheme 兩個子 reducer 分別返回 newState.themenewState.counter 的初始值。

          利用此 combineReducers 可以重寫 store/reducers/index.js

          import counter from './counter';
          import theme from './theme';
          import { combineReducers } from '../redux';
          //明顯簡潔了許多~
          export default combineReducers({
              counter,
              theme
          });

          我們寫的 combineReducers 雖然看起來已經能夠滿足我們的需求,但是其有一個缺點,即每次都會返回一個新的 state 對象,這會導致在數據沒有變化時進行無意義的重新渲染。因此我們可以對數據進行判斷,在數據沒有變化時,返回原本的 state 即可。

          combineReducers 進化版

          //代碼中省略了一些判斷,默認傳遞的參數均是符合要求的,有興趣可以查看源碼中對參數合法性的判斷及處理
          export default function combineReducers(reducers{
              return function combination(state={}, action{
                  let nextState = {};
                  let hasChanged = false//狀態(tài)是否改變
                  for(let key in reducers) {
                      const previousStateForKey = state[key];
                      const nextStateForKey = reducers[key](previousStateForKey, action);
                      nextState[key] = nextStateForKey;
                      //只有所有的 nextStateForKey 均與 previousStateForKey 相等時,hasChanged 的值才是 false
                      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
                  }
                  //state 沒有改變時,返回原對象
                  return hasChanged ? nextState : state;
              }
          }

          applyMiddleware

          官方文檔中,關于 applyMiddleware 的解釋很清楚,下面的內容也參考了官方文檔的內容:

          日志記錄

          考慮一個小小的問題,如果我們希望每次狀態(tài)改變前能夠在控制臺中打印出 state,那么我們要怎么做呢?

          最簡單的即是:

          //...
          <button onClick={() => {
              console.log(store.getState());
              store.dispatch(actions.add(2));
          }}>+</button>
          //...

          當然,這種方式肯定是不可取的,如果我們代碼中派發(fā)100次,我們不可能這樣寫一百次。既然是狀態(tài)改變時打印 state,也是說是在 dispatch 之前打印 state, 那么我們可以重寫 store.dispatch 方法,在派發(fā)前打印 state 即可。

          let store = createStore(reducer);
          const next = store.dispatch; //next 的命令是為了和中間件的源碼一致
          store.dispatch = action => {
              console.log(store.getState());
              next(action);
          }

          崩潰信息

          假設我們不僅僅需要打印 state,還需要在派發(fā)異常出錯時,打印出錯誤信息。

          const next = store.dispatch; //next 的命名是為了和中間件的源碼一致
          store.dispatch = action => {
              try{
                  console.log(store.getState());
                  next(action);
              } catct(err) {
                  console.error(err);
              }
          }

          而如果我們還有其他的需求,那么就需要不停的修改 store.dispatch 方法,最后導致這個這部分代碼難以維護。

          因此我們需要分離 loggerMiddlewareexceptionMiddleware.

          let store = createStore(reducer);
          const next = store.dispatch; //next 的命名是為了和中間件的源碼一致
          const loggerMiddleware = action => {
              console.log(store.getState());
              next(action);
          }
          const exceptionMiddleware = action => {
              try{
                  loggerMiddleware(action);
              }catch(err) {
                  console.error(err);
              }
          }
          store.dispatch = exceptionMiddleware;

          我們知道,很多 middleware 都是第三方提供的,那么 store 肯定是需要作為參數傳遞給 middleware ,進一步改寫:

          const loggerMiddleware = store => action => {
              const next = store.dispatch;
              console.log(store.getState());
              next(action);
          }
          const exceptionMiddleware = store => action => {
              try{
                  loggerMiddleware(store)(action);
              }catch(err) {
                  console.error(err);
              }
          }

          //使用
          store.dispatch = exceptionMiddleware(store)(action);

          現在還有一個小小的問題,exceptionMiddleware 中的 loggerMiddleware 是寫死的,這肯定是不合理的,我們希望這是一個參數,這樣使用起來才靈活,沒道理只有 exceptionMiddleware 需要靈活,而不管 loggerMiddleware,進一步改寫如下:

          const loggerMiddleware = store => next => action => {
              console.log(store.getState());
              return next(action);
          }
          const exceptionMiddleware = store => next => action => {
              try{
                  return next(action);
              }catch(err) {
                  console.error(err);
              }
          }
          //使用
          const next = store.dispatch;
          const logger = loggerMiddleware(store);
          store.dispatch = exceptionMiddleware(store)(logger(next));

          現在,我們已經有了通用 middleware 的編寫格式了。

          middleware 接收了一個 next()dispatch 函數,并返回一個 dispatch 函數,返回的函數會被作為下一個 middlewarenext()

          但是有一個小小的問題,當中間件很多的時候,使用中間件的代碼會變得很繁瑣。為此,redux 提供了一個 applyMiddleware 的工具函數。

          上面我們能夠看出,其實我們最終要改變的就是 dispatch,因此我們需要重寫 store,返回修改了 dispatch 方法之后的 store.

          所以,我們可以明確以下幾點:

          1. applyMiddleware 返回值是 store

          2. applyMiddleware 肯定要接受 middleware 作為參數

          3. applyMiddleware 要接受 store 作為入參,不過 redux 源碼中入參是 createStorecreateStore 的入參,想想也是,沒有必要在外部創(chuàng)建出 store,畢竟在外部創(chuàng)建出的 store 除了作為參數傳遞進函數,也沒有其它作用,不如把 createStorecreateStore 需要使用的參數傳遞進來。

          //applyMiddleWare 返回 store.
          const applyMiddleware = middleware => createStore => (...args) => {
              let store = createStore(...args);
              let middle = loggerMiddleware(store);
              let dispatch = middle(store.dispatch); //新的dispatch方法
              //返回一個新的store---重寫了dispatch方法
              return {
                  ...store,
                  dispatch
              }
          }

          以上是一個 middleware 的情況,但是我們知道,middleware 可能是一個或者是多個,而且我們主要是要解決多個 middleware 的問題,進一步改寫。

          //applyMiddleware 返回 store.
          const applyMiddleware = (...middlewares) => createStore => (...args) => {
              let store = createStore(...args);
              //順便說一句: redux 源碼中沒有直接把 store 傳遞過去,而是把 getState 和 dispatch 傳遞給了 middleware
              let middles = middlewares.map(middleware => middleware(store));
              //現在我們有多個 middleware,需要多次增強 dispatch
              let dispatch = middles.reduceRight((prev, current) => prev(current), store.dispatch);
              return {
                  ...store,
                  dispatch
              }
          }

          不知道大家是不是理解了上面的 middles.reduceRight,下面為大家細致說明一下:

          /*三個中間件*/
          let logger1 = store => dispatch => action => {
              console.log('111');
              dispatch(action);
              console.log('444');
          }
          let logger2 = store => dispatch => action => {
              console.log('222');
              dispatch(action);
              console.log('555')
          }
          let logger3 = store => dispatch => action => {
              console.log('333');
              dispatch(action);
              console.log('666');
          }
          let middle1 = logger1(store);
          let middle2 = logger2(store);
          let middle3 = logger3(store);

          //applyMiddleware(logger1,logger3,logger3)(createStore)(reducer)
          //如果直接替換
          store.dispatch = middle1(middle2(middle3(store.dispatch)));

          觀察上面的 middle1(middle2(middle3(store.dispatch))),如果我們把 middle1,middle2,middle3 看成是數組的每一項,如果對數組的API比較熟悉的話,可以想到 reduce,如果你還不熟悉 reduce,可以查看MDN文檔。

          //applyMiddleware(logger1,logger3,logger3)(createStore)(reducer)

          //reduceRight 從右到左執(zhí)行
          middles.reduceRight((prev, current) => current(prev), store.dispatch);
          //第一次 prev: store.dispatch    current: middle3  
          //第二次 prev: middle3(store.dispatch) current: middle2
          //第三次 prev: middle2(middle3(store.dispatch))  current: middle1
          //結果 middle1(middle2(middle3(store.dispatch)))

          閱讀過 redux 的源碼的同學,可能知道源碼中是提供了一個 compose 函數,而 compose 函數中沒有使用 reduceRight,而是使用的 reduce,因而代碼稍微有點不同。但是分析過程還是一樣的。

          compose.js

          export default function compose(...funcs{
              //如果沒有中間件
              if (funcs.length === 0) {
                  return arg => arg
              }
              //中間件長度為1
              if (funcs.length === 1) {
                  return funcs[0]
              }

              return funcs.reduce((prev, current) => (...args) => prev(current(...args)));
          }

          關于 reduce 的寫法,建議像上面的 reduceRight 一樣,進行一次分析

          使用 compose 工具函數重寫 applyMiddleware。

          const applyMiddleware = (...middlewares) => createStore => (...args) => {
              let store = createStore(...args);
              let middles = middlewares.map(middleware => middleware(store));
              let dispatch = compose(...middles)(store.dispatch);
              return {
                  ...store,
                  dispatch
              }
          }

          bindActionCreators

          redux 還為我們提供了 bindActionCreators 工具函數,這個工具函數代碼很簡單,我們很少直接在代碼中使用它,react-redux 中會使用到。此處,簡單說明一下:

          //通常我們會這樣編寫我們的 actionCreator
          import { INCRENENT, DECREMENT } from '../action-types';

          const counter = {
              add(number) {
                  return {
                      type: INCRENENT,
                      number
                  }
              },
              minus(number) {
                  return {
                      type: DECREMENT,
                      number
                  }
              }
          }

          export default counter;

          在派發(fā)的時候,我們需要這樣寫:

          import counter from 'xx/xx';
          import store from 'xx/xx';

          store.dispatch(counter.add());

          當然,我們也可以像下面這樣編寫我們的 actionCreator:

          function add(number{
              return {
                  type: INCRENENT,
                  number
              }
          }

          派發(fā)時,需要這樣編寫:

          store.dispatch(add(number));

          以上代碼有一個共同點,就是都是 store.dispatch 派發(fā)一個動作。因此我們可以考慮編寫一個函數,將 store.dispatchactionCreator 綁定起來。

          function bindActionCreator(actionCreator, dispatch{
              return  (...args) => dispatch(actionCreator(...args));
          }
          function bindActionCreators(actionCreator, dispatch{
              //actionCreators 可以是一個普通函數或者是一個對象
              if(typeof actionCreator === 'function') {
                  //如果是函數,返回一個函數,調用時,dispatch 這個函數的返回值
                  bindActionCreator(actionCreator, dispatch);
              }else if(typeof actionCreator === 'object') {
                  //如果是一個對象,那么對象的每一項都要都要返回 bindActionCreator
                  const boundActionCreators = {}
                  for(let key in actionCreator) {
                      boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch);
                  }
                  return boundActionCreators;
              }
          }

          在使用時:

          let counter = bindActionCreators(counter, store.dispatch);
          //派發(fā)時
          counter.add(number);
          counter.minus(number);

          這里看起來并沒有精簡太多,后面在分析 react-redux 時,會說明為什么需要這個工具函數。

          至此,我的 redux 基本已經編寫完畢。與 redux 的源碼相比,還相差一些內容,例如 createStore 提供的 replaceReducer 方法,以及 createStore 的第二個參數和第三個參數沒有提及,稍微看一下代碼就能懂,此處不再一一展開。

          參考鏈接

          1. React.js 小書:http://huziketang.mangojuice.top/books/react/lesson28)

          2. redux中文文檔:https://www.redux.org.cn/docs/advanced/Middleware.html)

          3. 完全理解 redux(從零實現一個 redux):https://github.com/brickspert/blog/issues/22


            好文章,我在看??

          瀏覽 62
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  51妺妺嘿嘿午夜成人A片 | 成人午夜无码影院 | 国内免费毛片 | 青青青青青操爽 | 丁香五月婷婷激情网 |