<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      手寫一個Redux,深入理解其原理

      Redux可是一個大名鼎鼎的庫,很多地方都在用,我也用了幾年了,今天這篇文章就是自己來實現一個Redux,以便于深入理解他的原理。我們還是老套路,從基本的用法入手,然后自己實現一個Redux來替代源碼的NPM包,但是功能保持不變。本文只會實現Redux的核心庫,跟其他庫的配合使用,比如React-Redux準備后面單獨寫一篇文章來講。有時候我們過于關注使用,只記住了各種使用方式,反而忽略了他們的核心原理,但是如果我們想真正的提高技術,最好還是一個一個搞清楚,比如Redux和React-Redux看起來很像,但是他們的核心理念和關注點是不同的,Redux其實只是一個單純狀態管理庫,沒有任何界面相關的東西,React-Redux關注的是怎么將Redux跟React結合起來,用到了一些React的API。

      本文全部代碼已經上傳到GitHub,大家可以拿下來玩下:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/redux

      基本概念

      Redux的概念有很多文章都講過,想必大家都看過很多了,我這里不再展開,只是簡單提一下。Redux基本概念主要有以下幾個:

      Store

      人如其名,Store就是一個倉庫,它存儲了所有的狀態(State),還提供了一些操作他的API,我們后續的操作其實都是在操作這個倉庫。假如我們的倉庫是用來放牛奶的,初始情況下,我們的倉庫里面一箱牛奶都沒有,那Store的狀態(State)就是:

      {
      	milk: 0
      }
      

      Actions

      一個Action就是一個動作,這個動作的目的是更改Store中的某個狀態,Store還是上面的那個倉庫,現在我想往倉庫放一箱牛奶,那"我想往倉庫放一箱牛奶"就是一個Action,代碼就是這樣:

      {
        type: "PUT_MILK",
        count: 1
      }
      

      Reducers

      前面"我想往倉庫放一箱牛奶"只是想了,還沒操作,具體操作要靠Reducer,Reducer就是根據接收的Action來改變Store中的狀態,比如我接收了一個PUT_MILK,同時數量count是1,那放進去的結果就是milk增加了1,從0變成了1,代碼就是這樣:

      const initState = {
        milk: 0
      }
      
      function reducer(state = initState, action) {
        switch (action.type) {
          case 'PUT_MILK':
            return {...state, milk: state.milk + action.count}
          default:
            return state
        }
      }
      

      可以看到Redux本身就是一個單純的狀態機,Store存放了所有的狀態,Action是一個改變狀態的通知,Reducer接收到通知就更改Store中對應的狀態。

      簡單例子

      下面我們來看一個簡單的例子,包含了前面提到的Store,Action和Reducer這幾個概念:

      import { createStore } from 'redux';
      
      const initState = {
        milk: 0
      };
      
      function reducer(state = initState, action) {
        switch (action.type) {
          case 'PUT_MILK':
            return {...state, milk: state.milk + action.count};
          case 'TAKE_MILK':
            return {...state, milk: state.milk - action.count};
          default:
            return state;
        }
      }
      
      let store = createStore(reducer);
      
      // subscribe其實就是訂閱store的變化,一旦store發生了變化,傳入的回調函數就會被調用
      // 如果是結合頁面更新,更新的操作就是在這里執行
      store.subscribe(() => console.log(store.getState()));
      
      // 將action發出去要用dispatch
      store.dispatch({ type: 'PUT_MILK' });    // milk: 1
      store.dispatch({ type: 'PUT_MILK' });    // milk: 2
      store.dispatch({ type: 'TAKE_MILK' });   // milk: 1
      

      自己實現

      前面我們那個例子雖然短小,但是已經包含了Redux的核心功能了,所以我們手寫的第一個目標就是替換這個例子中的Redux。要替換這個Redux,我們得先知道他里面都有什么東西,仔細一看,我們好像只用到了他的一個API:

      createStore:這個API接受reducer方法作為參數,返回一個store,主要功能都在這個store上。

      看看store上我們都用到了啥:

      store.subscribe: 訂閱state的變化,當state變化的時候執行回調,可以有多個subscribe,里面的回調會依次執行。

      store.dispatch: 發出action的方法,每次dispatch action都會執行reducer生成新的state,然后執行subscribe注冊的回調。

      store.getState:一個簡單的方法,返回當前的state

      看到subscribe注冊回調,dispatch觸發回調,想到了什么,這不就是發布訂閱模式嗎?我之前有一篇文章詳細講過發布訂閱模式了,這里直接仿寫一個。

      function createStore() {
        let state;              // state記錄所有狀態
        let listeners = [];     // 保存所有注冊的回調
      
        function subscribe(callback) {
          listeners.push(callback);       // subscribe就是將回調保存下來
        }
      
        // dispatch就是將所有的回調拿出來依次執行就行
        function dispatch() {
          for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i];
            listener();
          }
        }
      
        // getState直接返回state
        function getState() {
          return state;
        }
      
        // store包裝一下前面的方法直接返回
        const store = {
          subscribe,
          dispatch,
          getState
        }
      
        return store;
      }
      

      上述代碼是不是很簡單嘛,Redux核心也是一個發布訂閱模式,就是這么簡單!等等,好像漏了啥,reducer呢?reducer的作用是在發布事件的時候改變state,所以我們的dispatch在執行回調前應該先執行reducer,用reducer的返回值重新給state賦值,dispatch改寫如下:

      function dispatch(action) {
        state = reducer(state, action);
      
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i];
          listener();
        }
      }
      

      到這里,前面例子用到的所有API我們都自己實現了,我們用自己的Redux來替換下官方的Redux試試:

      // import { createStore } from 'redux';
      import { createStore } from './myRedux';
      

      可以看到輸出結果是一樣的,說明我們自己寫的Redux沒有問題:

      image-20200630152344176

      了解了Redux的核心原理,我們再去看他的源碼應該就沒有問題了,createStore的源碼傳送門。

      最后我們再來梳理下Redux的核心流程,注意單純的Redux只是個狀態機,是沒有View層的哦。

      image-20200630154356840

      除了這個核心邏輯外,Redux里面還有些API也很有意思,我們也來手寫下。

      手寫combineReducers

      combineReducers也是使用非常廣泛的API,當我們應用越來越復雜,如果將所有邏輯都寫在一個reducer里面,最終這個文件可能會有成千上萬行,所以Redux提供了combineReducers,可以讓我們為不同的模塊寫自己的reducer,最終將他們組合起來。比如我們最開始那個牛奶倉庫,由于我們的業務發展很好,我們又增加了一個放大米的倉庫,我們可以為這兩個倉庫創建自己的reducer,然后將他們組合起來,使用方法如下:

      import { createStore, combineReducers } from 'redux';
      
      const initMilkState = {
        milk: 0
      };
      function milkReducer(state = initMilkState, action) {
        switch (action.type) {
          case 'PUT_MILK':
            return {...state, milk: state.milk + action.count};
          case 'TAKE_MILK':
            return {...state, milk: state.milk - action.count};
          default:
            return state;
        }
      }
      
      const initRiceState = {
        rice: 0
      };
      function riceReducer(state = initRiceState, action) {
        switch (action.type) {
          case 'PUT_RICE':
            return {...state, rice: state.rice + action.count};
          case 'TAKE_RICE':
            return {...state, rice: state.rice - action.count};
          default:
            return state;
        }
      }
      
      // 使用combineReducers組合兩個reducer
      const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});
      
      let store = createStore(reducer);
      
      store.subscribe(() => console.log(store.getState()));
      
      // 操作??的action
      store.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 1
      store.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 2
      store.dispatch({ type: 'TAKE_MILK', count: 1 });   // milk: 1
      
      // 操作大米的action
      store.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 1
      store.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 2
      store.dispatch({ type: 'TAKE_RICE', count: 1 });   // rice: 1
      

      上面代碼我們將大的state分成了兩個小的milkStatericeState,最終運行結果如下:

      image-20200630162957760

      知道了用法,我們嘗試自己來寫下呢!要手寫combineReducers,我們先來分析下他干了啥,首先它的返回值是一個reducer,這個reducer同樣會作為createStore的參數傳進去,說明這個返回值是一個跟我們之前普通reducer結構一樣的函數。這個函數同樣接收stateaction然后返回新的state,只是這個新的state要符合combineReducers參數的數據結構。我們嘗試來寫下:

      function combineReducers(reducerMap) {
        const reducerKeys = Object.keys(reducerMap);    // 先把參數里面所有的鍵值拿出來
        
        // 返回值是一個普通結構的reducer函數
        const reducer = (state = {}, action) => {
          const newState = {};
          
          for(let i = 0; i < reducerKeys.length; i++) {
            // reducerMap里面每個鍵的值都是一個reducer,我們把它拿出來運行下就可以得到對應鍵新的state值
            // 然后將所有reducer返回的state按照參數里面的key組裝好
            // 最后再返回組裝好的newState就行
            const key = reducerKeys[i];
            const currentReducer = reducerMap[key];
            const prevState = state[key];
            newState[key] = currentReducer(prevState, action);
          }
          
          return newState;
        };
        
        return reducer;
      }
      

      官方源碼的實現原理跟我們的一樣,只是他有更多的錯誤處理,大家可以對照著看下。

      手寫applyMiddleware

      middleware是Redux里面很重要的一個概念,Redux的生態主要靠這個API接入,比如我們想寫一個logger的中間件可以這樣寫(這個中間件來自于官方文檔):

      // logger是一個中間件,注意返回值嵌了好幾層函數
      // 我們后面來看看為什么這么設計
      function logger(store) {
        return function(next) {
          return function(action) {
            console.group(action.type);
            console.info('dispatching', action);
            let result = next(action);
            console.log('next state', store.getState());
            console.groupEnd();
            return result
          }
        }
      }
      
      // 在createStore的時候將applyMiddleware作為第二個參數傳進去
      const store = createStore(
        reducer,
        applyMiddleware(logger)
      )
      

      可以看到上述代碼為了支持中間件,createStore支持了第二個參數,這個參數官方稱為enhancer,顧名思義他是一個增強器,用來增強store的能力的。官方對于enhancer的定義如下:

      type StoreEnhancer = (next: StoreCreator) => StoreCreator
      

      上面的結構的意思是說enhancer作為一個函數,他接收StoreCreator函數作為參數,同時返回的也必須是一個StoreCreator函數。注意他的返回值也是一個StoreCreator函數,也就是我們把他的返回值拿出來繼續執行應該得到跟之前的createStore一樣的返回結構,也就是說我們之前的createStore返回啥結構,他也必須返回結構,也就是這個store

      {
        subscribe,
        dispatch,
        getState
      }
      

      createStore支持enhancer

      根據他關于enhancer的定義,我們來改寫下自己的createStore,讓他支持enhancer

      function createStore(reducer, enhancer) {   // 接收第二個參數enhancer
        // 先處理enhancer
        // 如果enhancer存在并且是函數
        // 我們將createStore作為參數傳給他
        // 他應該返回一個新的createStore給我
        // 我再拿這個新的createStore執行,應該得到一個store
        // 直接返回這個store就行
        if(enhancer && typeof enhancer === 'function'){
          const newCreateStore = enhancer(createStore);
          const newStore = newCreateStore(reducer);
          return newStore;
        }
        
        // 如果沒有enhancer或者enhancer不是函數,直接執行之前的邏輯
        // 下面這些代碼都是之前那版
        // 省略n行代碼
      	// .......
        const store = {
          subscribe,
          dispatch,
          getState
        }
      
        return store;
      }
      

      這部分對應的源碼看這里。

      applyMiddleware返回值是一個enhancer

      前面我們已經有了enhancer的基本結構,applyMiddleware是作為第二個參數傳給createStore的,也就是說他是一個enhancer,準確的說是applyMiddleware的返回值是一個enhancer,因為我們傳給createStore的是他的執行結果applyMiddleware()

      function applyMiddleware(middleware) {
        // applyMiddleware的返回值應該是一個enhancer
        // 按照我們前面說的enhancer的參數是createStore
        function enhancer(createStore) {
          // enhancer應該返回一個新的createStore
          function newCreateStore(reducer) {
            // 我們先寫個空的newCreateStore,直接返回createStore的結果
            const store = createStore(reducer);
            return store
          }
          
          return newCreateStore;
        }
        
        return enhancer;
      }
      

      實現applyMiddleware

      上面我們已經有了applyMiddleware的基本結構了,但是功能還沒實現,要實現他的功能,我們必須先搞清楚一個中間件到底有什么功能,還是以前面的logger中間件為例:

      function logger(store) {
        return function(next) {
          return function(action) {
            console.group(action.type);
            console.info('dispatching', action);
            let result = next(action);
            console.log('next state', store.getState());
            console.groupEnd();
            return result
          }
        }
      }
      

      這個中間件運行效果如下:

      image-20200701160700945

      可以看到我們let result = next(action);這行執行之后state改變了,前面我們說了要改變state只能dispatch(action),所以這里的next(action)就是dispatch(action),只是換了一個名字而已。而且注意最后一層返回值return function(action)的結構,他的參數是action,是不是很像dispatch(action),其實他就是一個新的dispatch(action),這個新的dispatch(action)會調用原始的dispatch,并且在調用的前后加上自己的邏輯。所以到這里一個中間件的結構也清楚了:

      1. 一個中間件接收store作為參數,會返回一個函數
      2. 返回的這個函數接收老的dispatch函數作為參數,會返回一個新的函數
      3. 返回的新函數就是新的dispatch函數,這個函數里面可以拿到外面兩層傳進來的store和老dispatch函數

      所以說白了,中間件就是加強dispatch的功能,用新的dispatch替換老的dispatch,這不就是個裝飾者模式嗎?其實前面enhancer也是一個裝飾者模式,傳入一個createStore,在createStore執行前后加上些代碼,最后又返回一個增強版的createStore可見設計模式在這些優秀的框架中還真是廣泛存在,如果你對裝飾者模式還不太熟悉,可以看我之前這篇文章。

      遵循這個思路,我們的applyMiddleware就可以寫出來了:

      // 直接把前面的結構拿過來
      function applyMiddleware(middleware) {
        function enhancer(createStore) {
          function newCreateStore(reducer) {
            const store = createStore(reducer);
            
            // 將middleware拿過來執行下,傳入store
            // 得到第一層函數
            const func = middleware(store);
            
            // 解構出原始的dispatch
            const { dispatch } = store;
            
            // 將原始的dispatch函數傳給func執行
            // 得到增強版的dispatch
            const newDispatch = func(dispatch);
            
            // 返回的時候用增強版的newDispatch替換原始的dispatch
            return {...store, dispatch: newDispatch}
          }
          
          return newCreateStore;
        }
        
        return enhancer;
      }
      

      照例用我們自己的applyMiddleware替換老的,跑起來是一樣的效果,說明我們寫的沒問題,哈哈~

      image-20200701162841414

      支持多個middleware

      我們的applyMiddleware還差一個功能,就是支持多個middleware,比如像這樣:

      applyMiddleware(
        rafScheduler,
        timeoutScheduler,
        thunk,
        vanillaPromise,
        readyStatePromise,
        logger,
        crashReporter
      )
      

      其實要支持這個也簡單,我們返回的newDispatch里面依次的將傳入的middleware拿出來執行就行,多個函數的串行執行可以使用輔助函數compose,這個函數定義如下。只是需要注意的是我們這里的compose不能把方法拿來執行就完了,應該返回一個包裹了所有方法的方法。

      function compose(...func){
        return funcs.reduce((a, b) => (...args) => a(b(...args)));
      }
      

      這個compose可能比較讓人困惑,我這里還是講解下,比如我們有三個函數,這三個函數都是我們前面接收dispatch返回新dispatch的方法:

      const fun1 = dispatch => newDispatch1;
      const fun2 = dispatch => newDispatch2;
      const fun3 = dispatch => newDispatch3;
      

      當我們使用了compose(fun1, fun2, fun3)后執行順序是什么樣的呢?

      // 第一次其實執行的是
      (func1, func2) => (...args) => func1(fun2(...args))
      // 這次執行完的返回值是下面這個,用個變量存起來吧
      const temp = (...args) => func1(fun2(...args))
      
      // 我們下次再循環的時候其實執行的是
      (temp, func3) => (...args) => temp(func3(...args));
      // 這個返回值是下面這個,也就是最終的返回值,其實就是從func3開始從右往左執行完了所有函數
      // 前面的返回值會作為后面參數
      (...args) => temp(func3(...args));
      
      // 再看看上面這個方法,如果把dispatch作為參數傳進去會是什么效果
      (dispatch) => temp(func3(dispatch));
      
      // 然后func3(dispatch)返回的是newDispatch3,這個又傳給了temp(newDispatch3),也就是下面這個會執行
      (newDispatch3) => func1(fun2(newDispatch3))
      
      // 上面這個里面用newDispatch3執行fun2(newDispatch3)會得到newDispatch2
      // 然后func1(newDispatch2)會得到newDispatch1
      // 注意這時候的newDispatch1其實已經包含了newDispatch3和newDispatch2的邏輯了,將它拿出來執行這三個方法就都執行了
      

      更多關于compose原理的細節可以看我之前這篇文章。

      所以我們支持多個middleware的代碼就是這樣:

      // 參數支持多個中間件
      function applyMiddleware(...middlewares) {
        function enhancer(createStore) {
          function newCreateStore(reducer) {
            const store = createStore(reducer);
            
            // 多個middleware,先解構出dispatch => newDispatch的結構
            const chain = middlewares.map(middleware => middleware(store));
            const { dispatch } = store;
            
            // 用compose得到一個組合了所有newDispatch的函數
            const newDispatchGen = compose(...chain);
            // 執行這個函數得到newDispatch
            const newDispatch = newDispatchGen(dispatch);
      
            return {...store, dispatch: newDispatch}
          }
          
          return newCreateStore;
        }
        
        return enhancer;
      }
      

      最后我們再加一個logger2中間件實現效果:

      function logger2(store) {
        return function(next) {
          return function(action) {
            let result = next(action);
            console.log('logger2');
            return result
          }
        }
      }
      
      let store = createStore(reducer, applyMiddleware(logger, logger2));
      

      可以看到logger2也已經打印出來了,大功告成。

      image-20200701173615349

      現在我們也可以知道他的中間件為什么要包裹幾層函數了:

      第一層:目的是傳入store參數

      第二層:第二層的結構是dispatch => newDispatch,多個中間件的這層函數可以compose起來,形成一個大的dispatch => newDispatch

      第三層:這層就是最終的返回值了,其實就是newDispatch,是增強過的dispatch,是中間件的真正邏輯所在。

      到這里我們的applyMiddleware就寫完了,對應的源碼可以看這里,相信看了本文再去看源碼就沒啥問題了!

      本文所有代碼已經傳到GitHub,大家可以去拿下來玩一下:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/redux

      總結

      1. 單純的Redux只是一個狀態機,store里面存了所有的狀態state,要改變里面的狀態state,只能dispatch action
      2. 對于發出來的action需要用reducer來處理,reducer會計算新的state來替代老的state
      3. subscribe方法可以注冊回調方法,當dispatch action的時候會執行里面的回調。
      4. Redux其實就是一個發布訂閱模式!
      5. Redux還支持enhancerenhancer其實就是一個裝飾者模式,傳入當前的createStore,返回一個增強的createStore
      6. Redux使用applyMiddleware支持中間件,applyMiddleware的返回值其實就是一個enhancer
      7. Redux的中間件也是一個裝飾者模式,傳入當前的dispatch,返回一個增強了的dispatch
      8. 單純的Redux是沒有View層的,所以他可以跟各種UI庫結合使用,比如react-redux,計劃下一篇文章就是手寫react-redux

      參考資料

      官方文檔:https://redux.js.org/

      GitHub源碼:https://github.com/reduxjs/redux

      文章的最后,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝嗇你的贊和GitHub小星星,你的支持是作者持續創作的動力。

      歡迎關注我的公眾號進擊的大前端第一時間獲取高質量原創~

      “前端進階知識”系列文章:https://juejin.im/post/5e3ffc85518825494e2772fd

      “前端進階知識”系列文章源碼GitHub地址: https://github.com/dennis-jiang/Front-End-Knowledges

      QR1270

      posted @ 2020-07-03 14:03  _蔣鵬飛  閱讀(1802)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 性色在线视频精品| 狠狠躁日日躁夜夜躁欧美老妇| 亚洲欧洲一区二区三区久久| 国产国语一级毛片| 一区二区三区精品偷拍| 妓女妓女一区二区三区在线观看| 国语精品自产拍在线观看网站| 亚洲精品国产成人| 日本无产久久99精品久久| 国产精品播放一区二区三区| 亚洲欧美色综合影院| 免费看欧美日韩一区二区三区| 日本不卡不二三区在线看| 国产在线视频www色| 亚洲av无码专区在线亚| 少妇粗大进出白浆嘿嘿视频| 国产96在线 | 亚洲| 国产亚洲精品一区二区无| 欧洲免费一区二区三区视频| 中文字幕有码日韩精品| 香港日本三级亚洲三级| 无码欧亚熟妇人妻AV在线外遇| 动漫av网站免费观看| 国产一区二区三区麻豆视频| 高潮潮喷奶水飞溅视频无码| 国产v亚洲v天堂无码久久久| 亚洲伊人久久大香线蕉| 成人精品日韩专区在线观看| 亚洲综合色婷婷中文字幕| 国产精品毛片av999999| 精品国产久一区二区三区| 人妻丝袜无码专区视频网站| 国产午夜福利精品视频| 亚洲国产成人无码av在线播放| 国产av第一次处破| 在线一区二区中文字幕| 国产福利片无码区在线观看 | 国产日韩一区二区天美麻豆| 亚洲综合精品第一页| 成人午夜免费无码视频在线观看| 亚洲色大成网站WWW国产|