React Hook丨這兩個hook加ts价捧,如虎添翼

You Might Not Need Redux.
—— Dan Abramov

但是我們可以用 useReducer 和 useContext ~

前面說的話:

useContext 可以實現(xiàn)狀態(tài)共享滤否,useReducer 可以實現(xiàn)猶如 redux 狀態(tài)管理器 dispatch 的功能 瘸味。
這樣一來眉菱,我們就可以拿這兩個hook來實現(xiàn)一個簡單的狀態(tài)管理器了磅轻。
如果再加入ts呢帆锋,我們可以想到的是自己定義的諸多 type吵取,通過ts加編輯器的支持,在我們眼前呈現(xiàn)的那種愉悅感 ~

在項目中锯厢,我們可能會有多個狀態(tài)需要共享皮官,我們可能會在登錄后異步請求獲取用戶信息,然后在其他頁面會用到這個用戶信息... 实辑。

那就讓我們就用這兩個hook舉個例子吧:
( 這兩個hook還不了解的小伙伴捺氢,可以看上一篇文章介紹,點我點我 )

實現(xiàn)異步獲取用戶信息的相關文件

userInfo/index.declare.ts

export interface IState {
  id?: string;
  name?: string;
  isFetching?: boolean;
  failure?: boolean;
  message?: string;
}
type TType =
  | "ASYNC_SET_USER_INFO"
  | "FETCHING_START"
  | "FETCHING_DONE"
  | "FETCHING_FAILURE";
export interface IAction {
  type: TType;
  payload?: IState;
}

這個文件這里把它提取出來剪撬,聲明了基本的 state 讯沈、type的約束與action,參數(shù)用 payload 來接收

userInfo/index.tsx

import React, { useReducer, createContext, useContext } from "react";
import { IState, IAction } from "./index.declare";

// 初始化狀態(tài)
const initialState: IState = {
  id: "",
  name: "",
  isFetching: false,
  failure: false,
  message: ""
};

// 創(chuàng)建一個 context婿奔,并初始化值
const context: React.Context<{
  state: IState;
  dispatch?: React.Dispatch<IAction>;
}> = createContext({
  state: initialState
});

// reducer
const reducer: React.Reducer<IState, IAction> = (
  state,
  { type, payload }
): IState => {
  switch (type) {
    case "ASYNC_SET_USER_INFO": {
      const { id, name, message } = payload!;
      return { ...state, id, name, message };
    }
    case "FETCHING_START": {
      return { ...state, failure: false, isFetching: true };
    }
    case "FETCHING_DONE": {
      return { ...state, isFetching: false };
    }
    case "FETCHING_FAILURE": {
      return { id: "", name: "", failure: true, message: payload?.message };
    }
    default:
      throw new Error();
  }
};

/**
 * mock:模擬了請求接口的異步等待
 */
const request = (id: string): Promise<any> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id === "998") {
        resolve({ id: "998", name: "liming", message: "獲取用戶成功" });
      } else {
        reject(`找不到id為${id}的用戶`);
      }
    }, 1000);
  });
};

/**
 * dispatch 異步/同步 高階函數(shù)
 */
const dispatchHO = (dispatch: React.Dispatch<IAction>) => {
  return async ({ type, payload }: IAction) => {
    if (type.indexOf("ASYNC") !== -1) {
      dispatch({
        type: "FETCHING_START"
      });
      try {
        const { id, name, message } = await request(payload!.id!);
        dispatch({ type, payload: { id, name, message } });
      } catch (err) {
        dispatch({ type: "FETCHING_FAILURE", payload: { message: err } });
      }
      dispatch({ type: "FETCHING_DONE" });
    } else {
      dispatch({ type, payload });
    }
  };
};

/**
 * ProviderHOC 高階組件
 */
export const ProviderHOC = (WrappedComponent: React.FC) => {
  const Comp: React.FC = (props) => {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
      <context.Provider value={{ state, dispatch: dispatchHO(dispatch) }}>
        <WrappedComponent {...props} />
      </context.Provider>
    );
  };
  return Comp;
};

/**
 * 封裝 useContext
 */
export const useContextAlias = () => {
  const { state, dispatch } = useContext(context);
  return [state, dispatch] as [IState, React.Dispatch<IAction>];
};

解釋:

  • request 方法只是一個模擬接口請求等待而已缺狠。
  • 在真實的業(yè)務場景中,我們會異步請求用戶信息萍摊,所以實現(xiàn) 異步action 的核心代碼就在 dispatchHO 方法挤茄,這是一個高階函數(shù),dispatch 作為參數(shù)冰木。在我們發(fā)起異步請求時穷劈,我們需要對一些狀態(tài)進行改變笼恰,如請求前,請求成功歇终,請求失敗...社证,我們需要把它封裝到一個 “大dispatch” 里。約定 type 有 “ASYNC” 的情況下才會觸發(fā)這個特別的 “大dispatch”评凝。
  • ProviderHOC 是一個高階組件追葡,一般我們會用這種寫法,來共享狀態(tài)奕短,如:
<context.Provider value={obj}>
  <App />
</context.Provider>;

但是這里我們用高階組件的方式宜肉,讓我們在對 root 組件包裹的時候可以更靈活,請耐心繼續(xù)往下看翎碑。

  • useContextAlias方法 是對 useContext 的再封裝谬返,這里我把它轉換成我們比較了解的 useReducer 寫法,如:
const [state, dispatch] = useContext();

項目中文件目錄結構可能是這樣子的

我們可以看到 reducers 專門用來放一些 reducer 模塊日杈,userInfo遣铝、userList...

reducers/index.ts 作為一個 main 文件,我們來看看里面的實現(xiàn):

import React from "react";
import {
  ProviderHOC as ProviderHOCUserList,
  useContextAlias as useContextUserList
} from "./userList";
import {
  ProviderHOC as ProviderHOCUserInfo,
  useContextAlias as useContextUserInfo
} from "./userInfo";

/**
 * 組合各個 provider
 */
const compose = (...providers: any[]) => (root: any) =>
  providers.reverse().reduce((prev, next) => next(prev), root);

const arr = [ProviderHOCUserList, ProviderHOCUserInfo];

const providers = (root: React.FC) => compose(...arr)(root);

export { useContextUserList, useContextUserInfo };

export default providers;

解釋:

  • compose 方法是組合各個 provider 的核心方法莉擒,我們引入了各個模塊暴露出來的方法 ProviderHOC 然后再進行組合他們酿炸,這使得我們可以很靈活的去添加更多的 provider ,而不必要手動的在 root 組件上進行包裹啰劲,在App中我們就可以這樣,如:

App.tsx

import React from "react";
import "./styles.css";
import providers from "./reducers";
import UseReducerDemo from "./userReducer.demo";

const App = () => {
  return (
    <div className="App">
      <UseReducerDemo />
    </div>
  );
};
export default providers(App);
  • 我們把 import 進來的 useContextUserList, useContextUserInfo檀何,別名之后再次導出蝇裤,在其他頁面,只要針對的引入想要用的 context 即可频鉴,如:

userReducer.demo.tsx

import React from "react";
import { useContextUserInfo, useContextUserList } from "./reducers";

const Index: React.FC = () => {
  const [userInfo, dispatchUserInfo] = useContextUserInfo();
  const [userList, dispatchUserList] = useContextUserList();

  return (
    <div className="demo">
      userInfo:
      <p>狀態(tài):{userInfo.isFetching ? "正在加載中..." : "加載完畢"}</p>
      <p>id:{userInfo.id}</p>
      <p>name:{userInfo.name}</p>
      <p>message:{userInfo.message}</p>
      <button
        disabled={userInfo.isFetching}
        onClick={() => {
          dispatchUserInfo({
            type: "ASYNC_SET_USER_INFO",
            payload: { id: "998" }
          });
        }}
      >
        異步獲取用戶信息 id="998"
      </button>
      <button
        disabled={userInfo.isFetching}
        onClick={() => {
          dispatchUserInfo({
            type: "ASYNC_SET_USER_INFO",
            payload: { id: "1" }
          });
        }}
      >
        異步獲取用戶信息 id="1"
      </button>
  );
};

export default Index;

總結

我們在做項目的時候栓辜,這兩個hook可以用來做很輕巧的 redux,我們還可以自己實現(xiàn)異步 action垛孔。再加上ts藕甩,讓我們在其他頁面書寫 dispatch 有一種穩(wěn)重感 ~,用 compose 方法組合各個高階組件周荐,讓我們更加靈活的共享各個狀態(tài)狭莱。

所以,趕緊點我查看完整例子

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末概作,一起剝皮案震驚了整個濱河市腋妙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讯榕,老刑警劉巖骤素,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匙睹,死亡現(xiàn)場離奇詭異,居然都是意外死亡济竹,警方通過查閱死者的電腦和手機痕檬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來送浊,“玉大人梦谜,你說我怎么就攤上這事『贝” “怎么了改淑?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浴讯。 經(jīng)常有香客問我朵夏,道長,這世上最難降的妖魔是什么榆纽? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任仰猖,我火速辦了婚禮,結果婚禮上奈籽,老公的妹妹穿的比我還像新娘饥侵。我一直安慰自己,他們只是感情好衣屏,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布躏升。 她就那樣靜靜地躺著,像睡著了一般狼忱。 火紅的嫁衣襯著肌膚如雪膨疏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天钻弄,我揣著相機與錄音佃却,去河邊找鬼。 笑死窘俺,一個胖子當著我的面吹牛饲帅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘤泪,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灶泵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了对途?” 一聲冷哼從身側響起丘逸,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掀宋,沒想到半個月后深纲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仲锄,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年湃鹊,在試婚紗的時候發(fā)現(xiàn)自己被綠了儒喊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡币呵,死狀恐怖怀愧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情余赢,我是刑警寧澤芯义,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站妻柒,受9級特大地震影響扛拨,放射性物質發(fā)生泄漏。R本人自食惡果不足惜举塔,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一绑警、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧央渣,春花似錦计盒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拔第,卻和暖如春咕村,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背楼肪。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工培廓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惹悄,地道東北人春叫。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像泣港,于是被迫代替她去往敵國和親暂殖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容