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)狭莱。
所以,趕緊點我查看完整例子