如何在Typescript中为React useReducer钩子动作创建类型定义?

时间:2020-03-03 17:55:47

标签: reactjs typescript types react-hooks

让我们假设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;

在“登录”情况下,编译器在reducer中抛出错误: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。

4 个答案:

答案 0 :(得分:2)

经过数小时的探索和实验,发现通过Typescript enum和动作的联合类型可以找到一个非常优雅的解决方案:

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 });
  }

  /* ... */
}

使用上述方法不会产生任何错误或警告,此外,对于使用行为也有相当严格的约定。

答案 1 :(得分:2)

您需要通过强制类型转换来为特定的reducer案例提供更多类型信息

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

type UserActionWithoutPayload = {
  type: string;
};

type UserAction = UserActionWithPayload | UserActionWithoutPayload;

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

let state = "";
state = userReducer(state, { type: "LOGIN", username: "John" });
console.log("LOGIN", state);

state = userReducer(state, { type: "LOGOUT" });
console.log("LOGOUT", state);

Playground Link

答案 2 :(得分:1)

该方法看起来还可以,问题在于减速器的返回类型为string,但是如果传递了UserActionWithoutPayload,则它可能会返回action.username,其中用户名未定义。 / p>

一种解决方法是放宽返回类型:

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

答案 3 :(得分:0)

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

在此函数中,您将返回类型定义为字符串。但是,当您将UserAction作为UserActionWithoutPayload类型传递时,返回值应该是未定义的。这些原因在userReducer(...)函数的返回类型和实际返回类型之间有所不同,它们是字符串形式的,并且是未定义的。 要解决此问题,您可以将返回类型更改为任意。

我希望这个答案可能对您的工作有所帮助。

关于。 林