首页 > 解决方案 > 如何在 Typescript 中为 React useReducer 挂钩操作创建类型定义?

问题描述

假设我们这样userReducer定义:

function userReducer(state: string, action: UserAction): string {
  switch (action.type) {
    case "LOGIN":
      return action.username;
    case "LOGOUT":
      return "";
    default:
      throw new Error("Unknown 'user' action");
  }
}

定义类型的最佳方法是什么,UserAction因此可以dispatch使用username有效负载和不使用以下方法进行调用:

dispatch({ type: "LOGIN", username: "Joe"}});
/* ... */
dispatch({ type: "LOGOUT" });

如果类型是这样定义的:

type UserActionWithPayload = {
  type: string;
  username: string;
};

type UserActionWithoutPayload = {
  type: string;
};

export type UserAction = UserActionWithPayload | UserActionWithoutPayload;

在“LOGIN”情况下,编译器在减速器中抛出错误:TS2339: Property 'username' does not exist on type 'UserAction'.   Property 'username' does not exist on type 'UserActionWithoutPayload'.

如果使用可选成员定义类型:

export type UserAction = {
  type: string;
  username?: string;
}

然后编译器显示此错误:TS2322: Type 'string | undefined' is not assignable to type 'string'.   Type 'undefined' is not assignable to type 'string'.

这里缺少什么?也许整个方法是错误的?

项目使用 TypeScript 3.8.3 和 React.js 16.13.0。

标签: reactjstypescripttypesreact-hooks

解决方案


enum经过数小时的挖掘和试验,通过 Typescript和联合类型的操作找到了一个非常优雅的解决方案:

enum UserActionType {
  LOGIN = "LOGIN",
  LOGOUT = "LOGOUT"
}

type UserState = string;

type UserAction =
| { type: UserActionType.LOGIN; username: string }
| { type: UserActionType.LOGOUT }

function userReducer(state: UserState, action: UserAction): string {
  switch (action.type) {
    case UserActionType.LOGIN:
      return action.username;
    case UserActionType.LOGOUT:
      return "";
    default:
      throw new Error();
  }
}

function App() {
  const [user, userDispatch] = useReducer(userReducer, "");

  function handleLogin() {
    userDispatch({ type: UserActionType.LOGIN, username: "Joe" });
  }

  function handleLogout() {
    userDispatch({ type: UserActionType.LOGOUT });
  }

  /* ... */
}

使用上述方法没有错误或警告,此外还有一个非常严格的操作使用合同。


推荐阅读