如何利用 React Hooks 管理全局狀態


如何利用 React Hooks 管理全局狀態

本文寫於 2020 年 1 月 6 日

React 社區最火的全局狀態管理庫必定是 Redux,但是 Redux 本身就是為了大型管理數據而妥協設計的——這就會讓一些小一點的應用一旦用上 Redux 就變得復雜無比

后來又有了 Mobx,它對於小型應用的狀態管理確實比 Redux 簡單不少。可是不得不說 Mobx+React 簡直就是一個繁瑣版本的 Vue。所以我也不太喜歡,不如直接用 Vue3。

總而言之,不管是 react-redux 還是 mobx,他們使用的時候都非常復雜,甚至需要你去組件函數或是組件類上修修改改,從審美角度上來說就令人不太喜歡。

直到后來某一天用了 Angular,我就開始對 SOA 產生好感,ng 的 Service 的寫法與依賴注入控制反轉着實驚艷到了我。

Service 是 Angular 的邏輯復用方法,並且解決了共享狀態的問題,那 React 的自定義 Hook 可以達到類似的效果嘛?

可以,並且會比 Angular 更簡潔!!!

什么是 Service

我們先來想一下,Service 到底是什么?

  • Service 包含 n 個方法;
  • Service 包含有狀態;
  • Service 應該是個單例。
  • 這些方法與狀態應該是高度整合的,一個 Service 解決的是一個模塊的問題。

例如下面這個負責 Todo List 記錄的 Service:

class TodoRecordService {
  private todoList: Record[] = [];

  get getTodoList() {
    return this.todoList;
  }

  public addRecord(newRecord: Record) {
    this.todoList.push(newRecord);
  }

  public deleteRecord(id: string) {
    this.todoList = this.todoList.filter((record) => record.id !== id);
  }

  public getRecord(id: string) {
    const targetIndex = this.todoList.findIndex((record) => record.id === id);
    return { index: targetIndex, ele: this.todoList[targetIndex] };
  }
}

自定義 Service

那我們用 React 如何實現一個狀態共享的單例呢?

使用 ContextuseContext 即可。

接下來我們做一個最簡單的計數器吧:一個負責計數的 button,一個負責顯示當前數值的 panel。

const App: React.FC = () => {
  return (
    <div>
      <Button />
      <Panel />
    </div>
  );
};

然后我們來定義我們的 Service:

interface State {
  count: number;
  handleAdd: () => void;
}

export const CountService = createContext<State>(null);

我們選擇讓一個 Context 成為一個 Service,這是因為我們可以利用 Context 的特性來進行狀態共享,達到單例的效果。

但是光這樣還不行,我們想讓 count 擁有響應性,就必須使用 useState(或者其他 hook)來創建。

因此需要一個自定義 Hook,並且在 Context.Provider 中傳入 Providervalue 值:

interface State {
  count: number;
  handleAdd: () => void;
}

export const CountService = createContext<State>(null);

export const useRootCountService = () => {
  const [count, setCount] = useState<number>(0);
  const handleAdd = useCallback(() => {
    setCount((n) => n + 1);
  }, []);

  return {
    count,
    handleAdd,
  };
};

那么在組建中,我們如何使用 Service 呢?

非常簡單:

const App: React.FC = () => {
  const countService = useContext(CountService);

  return <div>{countService.count}</div>;
};

所以計數器的完整代碼應該這么寫:

import { CountService, useRootCountService } from './service/count.service';

const App: React.FC = () => {
  return (
    <CountService.Provider value={useRooCountService()}>
      <div>
        <Button />
        <Panel />
      </div>
    </CountService.Provider>
  );
};

// Button.tsx
import { CountService } from '../services/global.service';

const Button: React.FC = () => {
  // 注意,此處是故意寫復雜了,是為了凸顯跨組件狀態管理的特性
  const countService = useContext(CountService);
  return <button onClick={() => countService.handleAdd()}>+</button>;
};

// Panel.tsx
import { CountService } from '../services/global.service';

const Panel: React.FC = () => {
  const countService = useContext(CountService);
  return <h2>{countService.count}</h2>;
};

hooks 與 Service

對於小組件而言,剛剛的寫法已經足夠了。

但是要知道,Service 是高度集中的某個模塊的狀態與方法,我們不能保證 Service 的方法可以直接用到組件的邏輯中去。

所以需要我們在組件內部對於邏輯進行二次拼裝。

但是把邏輯直接寫到組件里面是一件非常惡劣的事情!!!

幸好,React 有了 hooks 讓我們去抽離邏輯代碼。

const useLogic1 = () => {
  // 在 hook 中獲取服務
  const xxxService = useContext(XxxService);
  // ...
  const foo = useCallback(() => {
    // ...
    xxxService.xxxx();
    // ...
  }, []);

  return {
    // ...
    foo,
  };
};

const SomeComponent: React.FC = () => {
  // 復用邏輯
  const { a, b, foo } = useLogic1(someParams);
  const { c, bar } = useLogic2();

  return (
    <div>
      <button onClick={() => bar()}>Some Operation</button>
    </div>
  );
};

這種形式的組件,便是我們的目標。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM