<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>

          Redux + Hooks 工程實踐

          共 8877字,需瀏覽 18分鐘

           ·

          2021-08-07 10:29

          點擊上方關注 前端技術江湖,一起學習,天天進步


          “都 1202 年了怎么還有人在用 Redux”——這大概不少人看到這篇文章的第一反應。首先先表明一下,這篇文章并不討論是不是應該使用 Redux,這是一個比較大的話題,應該單獨水一篇。而且社區(qū)已經存在許許多多的討論了,你總能從幾篇高贊的文章中找到一些優(yōu)缺點的對比圖,然后結合你項目的場景最終作出決定。我們來隨便舉幾個團隊使用 Redux 的原因。首先是易懂,Redux 被人吐槽很多的可能是寫法繁瑣,但是在繁瑣寫法的背后就沒有那么多黑科技了,非常容易排查問題。另外,Redux 本質是對邏輯處理方式提出了標準范式,并且搭配得給到了一組實踐規(guī)范,有助于保持項目代碼書寫風格與組織方式的一致性,這點在多人合作開發(fā)的項目里面尤為重要。其他的優(yōu)點就不在此贅述啦。

          這時候就有同學可能要問了,你講 Redux,那和 hooks 又有啥子關系呢。眾所周知,在 React 團隊推出 Hooks 這個概念后不久,Redux 也更新了對應的 API 來支持。Hooks 的本質是對邏輯的封裝以及邏輯與 UI 代碼的解耦。有了 Hooks 的加持能夠讓我們的 Redux React 項目更加簡潔、易懂、擴展性更強。而且 Hooks API 在 Redux 的最佳實踐建議中目前是 Level 2 的強烈推薦使用級別。他擁有更簡潔的表達方式,更干凈的 React 節(jié)點數(shù),更友好的 typescript 支持。

          具體 Redux 相關的 API 怎么用,這里不做介紹,可以直接跳轉官方文檔進行了解。下面我們會從一個應用場景來具體講一講,他們是怎么幫助我們更好地組織代碼的。其中的部分工程級別代碼來自于 react-boilerplate 的項目模版,它在動態(tài)加載問題上提供了不少幫助。

          封裝案例

          在開發(fā)大型 React 應用的時候,動態(tài)懶加載代碼永遠是我們項目架構中的必選項。代碼的拆分、動態(tài)引用等,工程化工具都已經幫我們完成了。我們更需要關注的是,動態(tài)引入與解除掛載等操作時額外要做什么,以及這個工作如何盡量少的暴露給項目開發(fā)者。前面說過了,Hooks 最強大的能力在于邏輯的封裝,這里當然也就要借助他的力量了。

          這里我們以 Reducer 作為例子來講,其他中間件,例如 Saga 等都可以類推,如果需要可以后續(xù)再把相應的代碼一并貼出來。我們把整個封裝分為三層:核心實現(xiàn)、可組合封裝、對開發(fā)者暴露封裝。下面我們按順序一一講解。(具體實現(xiàn)中我都會默認帶上包含 connected router 的實現(xiàn),方便需要抄代碼的可以直接用)

          核心實現(xiàn)

          這里的代碼實現(xiàn)的是如何為一個 store 掛載與解除掛載拆分后的各個 Reducer 的邏輯。

          // 本段代碼完全來自于 react-boilerplate 項目
          import { combineReducers } from 'redux';
          import { connectRouter } from 'connected-react-router';
          import invariant from 'invariant';
          import { isEmpty, isFunction, isString } from 'lodash';

          import history from '@/utils/history';
          import checkStore from './checkStore'// 做類型安全檢測的,不用關心

          function createReducer(injectedReducers = {}{
            return history => combineReducers({
              router: connectRouter(history),
              ...injectedReducers,
            });
          }

          export function injectReducerFactory(store, isValid{
            return function injectReducer(key, reducer{
              if (!isValid) checkStore(store);

              invariant(
                isString(key) && !isEmpty(key) && isFunction(reducer),
                '(src/utils...) injectReducer: Expected `reducer` to be a reducer function',
              );

              if (
                Reflect.has(store.injectedReducers, key)
                && store.injectedReducers[key] === reducer
              ) return;

              store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
              store.replaceReducer(createReducer(store.injectedReducers)(history));
            };
          }

          export default function getInjectors(store{
            checkStore(store);

            return {
              injectReducer: injectReducerFactory(store, true),
            };
          }

          這段有個點比較特殊,需要講一下。你可能會發(fā)現(xiàn),這里面根本沒有解除掛載的部分。這是因為 reducer 比較特殊,他并不會產生副作用,并且因為目前提供的方法是通過整個替換的方式去掛載新的 Reducer,所以并沒有什么必要去單獨做解除掛載。在處理其他中間件的掛載時,特別是那些存在副作用的(例如 redux-saga),我們需要對應地實現(xiàn)一個解除掛載的 eject 方法。

          OK,那么現(xiàn)在我們已經可以通過 getInjectors 方法為整個項目提供一個 injectReducer 注入 Reducer 的能力了(同時可能包含 eject 方法)。下一步就是怎么調度這個能力。

          可組合的封裝

          這里,我們希望通過一個自定義的 hooks,可以允許開發(fā)者為一個組件聲明某一個 命名空間 的 reducer 與其生命周期一致地進行掛載與解除掛載。開發(fā)者只需要傳入 reducer 的命名空間與 reducer 實現(xiàn),并將這個 hooks 放到相應的組件邏輯中即可。

          import React from 'react';
          import { ReactReduxContext } from 'react-redux';

          // 這是我們在上一步實現(xiàn)的 injector 工廠,通過他來產出一個與固定 store 綁定的 injectReducer 函數(shù)
          import getInjectors from './reducerInjectors';

          const useInjectReducer = ({ key, reducer }) => {
            // 需要從 Redux 的 context 中獲取到當前應用的全局 store 實例
            const context = React.useContext(ReactReduxContext);

            // 為了模擬 constructor 的運行時機
            const initFlagRef = React.useRef(false);
            if (!initFlagRef.current) {
              initFlagRef.current = true;
              getInjectors(context.store).injectReducer(key, reducer);
            }

            // 如果需要加入 eject 的邏輯,則可以使用這樣的寫法。類似于為當前組件增加一個 willUnmount 的生命周期邏輯。
            // React.useEffect(() => (() => {
            //   const injectors = getInjectors(context.store);
            //   injectors.ejectReducer(key);
            // }), []);
          };

          export { useInjectReducer };

          useInjectReducer 這個 Hooks 幫助我們處理了何時去掛載,怎么掛載等問題,我們最終只需要告訴他 掛載什么 就可以了。通過這層封裝,可以發(fā)現(xiàn)我們進一步收斂了關注點。到這一步為止,我們都是提供了一個項目級別的公共方法。在下一步中,我們會提供一個統(tǒng)一的寫法,在具體的開發(fā)過程中去使用,進一步做封裝收斂。

          在進入下一步之前,我們先簡單解釋一下上面的邏輯。邏輯通過注釋分為了三段(第三段在 reducer 場景下沒用到),第一段我們通過當前組件所處的 redux 上下文,拿到了 store 的引用,第二段與第三段我們分別讓組件在 初始化 和 銷毀前 執(zhí)行掛載與解除掛載的操作。通過一個 initFlagRef 為 functional 的組件模擬構造器的生命周期(如果有更好的實現(xiàn)方案歡迎指教),因為如果在掛載之后再 inject 的話,會在第一次渲染時取不到對應 store 的內容。

          對開發(fā)者暴露封裝

          在完成公用方法的封裝之后,我們下一步考慮的就是如何用更簡單的方式,為我們的模塊掛載 store 。按照下面的方式,開發(fā)者不用關心任何東西,只需一句話就可以完成掛載,也不用提供額外的參數(shù)。如果同時有 reducer、saga 或其他中間件內容,也可以一起打包搞定。

          import { 
            useInjectReducer, 
            // useInjectSaga,
          from '@/utils/store';

          import actions from './actions';
          import constants from './constants';
          import reducer from './reducer';
          // import saga from './saga';

          const namespace = constants.namespace;

          const useSubStore = () => {
            useInjectReducer({ key: namespace, reducer });
            // useInjectSaga({ key: namespace, saga });
          };

          export {
            namespace,
            actions,
            constants,
            useSubStore,
          };

          實際使用范例:

          import React from 'react';
          import {
            useSubStore,
          from './store';

          export default function Page({
            useSubStore();

            return <div />;
          };

          具體的數(shù)據(jù)和邏輯我們也可以封裝成幾個 Hooks ,例如我們需要提供一個數(shù)組數(shù)據(jù)簡單操作,我們只關心 添加 和 數(shù)量,就可以封裝一個 Hooks,這樣實際使用方只需要關心 添加 和 數(shù)量 這兩個要素,不用關心 redux 的具體實現(xiàn)方式了。

          import { useMemo, useCallback } from 'react';
          import { useDispatch, useSelector } from 'react-redux';

          import {
            actions, constants, namespace,
          from './store';

          export function useItemList({
            const dispatch = useDispatch();
            const list = useSelector(state => state[namespace].itemList);
            // 這只是范例!
            const count = useMemo(() => list.length, [list]);
            const add = useCallback((item) => dispatch(actions.addItem(item)), []);

            return [count, add];
          }

          下面我們修改一下使用的地方:

          import React from 'react';
          import {
            useSubStore,
          from './store';
          import { useItemList } from './useItemList';

          export default function Page({
            useSubStore();
            const [count, add] = useItemList();

            return <div onClick={() => add({})}>{count}</div>;
          };

          通過這樣一種拆分方式,store 的定義,store 的使用邏輯,業(yè)務側三者都只關注自己必須關注的部分,任何一方改動都可以盡量少地引起變更。

          可復用的 Hooks

          那我們進一步思考一下,以前我們可能一個頁面對應一個 store。通過 Hooks 進行拆分后,我們更方便從功能層面去拆分 store,store 的邏輯也會更為清晰。與 store 的交互被封裝成了 Hooks 之后也可以很快在多個展示層被使用。這在復雜 B 端工作臺場景下會展現(xiàn)出很大的價值。案例會有點長,以后有時間可以再補上。

          回顧

          看完上面的例子,相信聰明的讀者已經知道我想表達的問題了。通過結合 Redux + Hooks,標準化了定義代碼,對邏輯、調用、定義三者一定程度上進行了解耦。通過簡化的 API,減少了邏輯的理解成本,減少了后續(xù)維護的復雜度,一定程度上還可以達到復用。不管是相較于過去的 Redux 接入方案,還是相較于單純使用 Hooks,都有著其獨特的優(yōu)勢。特別適用于邏輯相對復雜的工作臺場景。(而且我很喜歡 Saga的設計思路,能用起來就很爽)。

          OK,收。這次以一個簡單的例子,稍稍展示了一下在 Hooks 大環(huán)境下 Redux 與其產生的化學反應。主要想展示的是依賴 Hooks 的邏輯可封裝能力的一種設計思路,Redux 黑的同學們不要過多糾結與這個選型,蘿卜青菜各有所愛。

          希望這個系列能繼續(xù)寫下去 :)

          作者:ES2049 / armslave00 https://zhuanlan.zhihu.com/p/374788504 非常歡迎有激情的你加入 ES2049 Studio,簡歷請發(fā)送至 [email protected]

          The End

          歡迎自薦投稿到《前端技術江湖》,如果你覺得這篇內容對你挺有啟發(fā),記得點個 「在看」


          點個『在看』支持下 


          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲精品一二三区 | 亚洲日韩欧美一区二区 | 青娱乐精品视觉盛宴 | 日本狠狠撸 | 午夜欧美性爱视频 |