TypeScript:如何创建类型或接口,该类型或接口是具有n个键之一或字符串的对象?

时间:2018-10-06 17:19:01

标签: typescript types typescript-typings

我需要创建一个接口,该接口可以是字符串,也可以是具有三个键之一的对象。

基本上我有一个函数,根据错误返回一些信息:

export const determineError = (error: ServerAlerts): AlertError => {
  if (typeof error !== "string") {
    if (error.hasOwnProperty("non_field_errors")) {
      return error.non_field_errors[0];
    } else if (error.hasOwnProperty("detail")) {
      return error.detail;
    } else if (error.hasOwnProperty("email")) {
      return error.email[0];
    } else {
      return UNKNOWN_ERROR;
    }
  } else {
    return error;
  }
};

以下是这些类型:

export type AlertError =
  | "Unable to log in with provided credentials."
  | "E-mail is not verified."
  | "Password reset e-mail has been sent."
  | "Verification e-mail sent."
  | "A user is already registered with this e-mail address."
  | "Facebook Log In is cancelled."
  | string;

export interface ServerAlerts {
  non_field_errors: [string];
  detail: string;
  email: [string];
}

但是我在这里设计ServerAlerts的方法对我来说不起作用,因为ServerAlerts也可以是string,并且如果它具有其密钥之一,则只能具有一个。

您将如何设计这种类型或接口?

编辑:我尝试通过给键打个问号来使键成为可选键,但随后我的棉绒布在determineError中的相应键的错误返回语句中抱怨。

1 个答案:

答案 0 :(得分:2)

如果我对您的理解正确,只需将参数声明为ServerAlertsstring

export const determineError = (error: ServerAlerts|string): AlertError => {
// -----------------------------------------------^^^^^^^

在评论中,您说过ServerAlerts这三个属性都是可选的,因此您需要使用?对其进行标记:

interface ServerAlerts {
  non_field_errors?: [string];
  detail?: string;
  email?: [string];
}

但是,这意味着键入object的任何内容也将起作用,因为所有字段都是可选的。因此,如果您同时执行上述两项操作,则会得到:

determineError("foo");                       // Works
determineError({ non_field_errors: ["x"] }); // Works
determineError({ detail: "x" });             // Works
determineError({ email: ["x"] });            // Works
determineError({});                          // Works (because all fields are optional)
let nonLiteralServerAlerts: object;
nonLiteralServerAlerts = { foo: ["x"] };
determineError(nonLiteralServerAlerts);      // Works (because all fields are optional)
determineError({ foo: ["x"] });              // Fails (correctly)

Playground example

建议您仅在参数签名中使用object。如果您要需要这三个字段之一(我相信可以取消该UNKNOWN_ERROR分支),则可以定义三个接口,并ServerAlerts组成一个他们:

interface ServerAlertsNonFieldErrors {
  non_field_errors: [string];
}

interface ServerAlertsDetail {
  detail: string;
}

interface ServerAlertsEmail {
  email: [string];
}

type ServerAlerts = ServerAlertsNonFieldErrors | ServerAlertsDetail | ServerAlertsEmail;

然后您将在返回特定字段时使用类型断言:

if (error.hasOwnProperty("non_field_errors")) {
  return (error as ServerAlertsNonFieldErrors).non_field_errors[0];
// ------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

如果这样做,您将得到:

determineError("foo");                       // Works
determineError({ non_field_errors: ["x"] }); // Works
determineError({ detail: "x" });             // Works
determineError({ email: ["x"] });            // Works
determineError({});                          // Fails (correctly)
let nonLiteralServerAlerts: object;
nonLiteralServerAlerts = { foo: ["x"] };
determineError(nonLiteralServerAlerts);      // Fails (correctly)
determineError({ foo: ["x"] });              // Fails (correctly)

Playground Example