美文网首页
React Hook丨这两个hook加ts,如虎添翼

React Hook丨这两个hook加ts,如虎添翼

作者: 前端精 | 来源:发表于2020-11-20 13:26 被阅读0次

    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
    });
    
    // 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 异步/同步 高阶函数
     */
    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 方法组合各个高阶组件,让我们更加灵活的共享各个状态。

    所以,赶紧点我查看完整例子

    相关文章

      网友评论

          本文标题:React Hook丨这两个hook加ts,如虎添翼

          本文链接:https://www.haomeiwen.com/subject/mbkliktx.html