You Might Not Need Redux.
—— Dan Abramov

然而咱们能够用 useReducer 和 useContext ~

后面说的话:

useContext 能够实现状态共享,useReducer 能够实现犹如 redux 状态管理器 dispatch 的性能 。
这样一来,咱们就能够拿这两个hook来实现一个简略的状态管理器了。
如果再退出ts呢,咱们能够想到的是本人定义的诸多 type,通过ts加编辑器的反对,在咱们眼前出现的那种愉悦感 ~

在我的项目中,咱们可能会有多个状态须要共享,咱们可能会在登录后异步申请获取用户信息,而后在其余页面会用到这个用户信息... 。

那就让咱们就用这两个hook举个例子吧:
( 这两个hook还不理解的小伙伴,能够看上一篇文章介绍,点我点我 )

实现异步获取用户信息的相干文件

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,参数用 payload 来接管

userInfo/index.tsx

import React, { useReducer, createContext, useContext } from "react";import { IState, IAction } from "./index.declare";// 初始化状态const initialState: IState = {  id: "",  name: "",  isFetching: false,  failure: false,  message: ""};// 创立一个 context,并初始化值const context: React.Context<{  state: IState;  dispatch?: React.Dispatch<IAction>;}> = createContext({  state: initialState});// reducerconst 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 异步/同步 高阶函数 */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 办法只是一个模仿接口申请期待而已。
  • 在实在的业务场景中,咱们会异步申请用户信息,所以实现 异步action 的外围代码就在 dispatchHO 办法,这是一个高阶函数,dispatch 作为参数。在咱们发动异步申请时,咱们须要对一些状态进行扭转,如申请前,申请胜利,申请失败...,咱们须要把它封装到一个 “大dispatch” 里。约定 type 有 “ASYNC” 的状况下才会触发这个特地的 “大dispatch”。
  • ProviderHOC 是一个高阶组件,个别咱们会用这种写法,来共享状态,如:
<context.Provider value={obj}>  <App /></context.Provider>;

然而这里咱们用高阶组件的形式,让咱们在对 root 组件包裹的时候能够更灵便,请急躁持续往下看。

  • useContextAlias办法 是对 useContext 的再封装,这里我把它转换成咱们比拟理解的 useReducer 写法,如:
const [state, dispatch] = useContext();

我的项目中文件目录构造可能是这样子的

咱们能够看到 reducers 专门用来放一些 reducer 模块,userInfo、userList...

reducers/index.ts 作为一个 main 文件,咱们来看看外面的实现:

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>状态:{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,咱们还能够本人实现异步 action。再加上ts,让咱们在其余页面书写 dispatch 有一种稳重感 ~,用 compose 办法组合各个高阶组件,让咱们更加灵便的共享各个状态。

所以,连忙点我查看残缺例子