【问题标题】:TypeScript: How to create a type or interface that is an object that has one of n keys or is a string?TypeScript:如何创建一个类型或接口,它是一个具有 n 个键之一的对象还是一个字符串?
【发布时间】:2019-03-11 21:36:41
【问题描述】:

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

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

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,如果它有一个键,它就只有一个。

你会如何设计这样的类型或接口?

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

【问题讨论】:

  • Re non_field_errorsdetailemail -- 它们是互斥的吗?是否有任何(或全部)可选?
  • 它们都是可选的。可以有一个、两个或同时存在三个。如果没有,它只是一个正在通过的字符串。
  • 除了下面建议的 | string 联合之外,这是 How to create a Partial-like that requires a single property to be set 的副本。
  • 您的 linter 抱怨说...该属性可能是 undefined,我猜?是的,问号可以(在--strictNullChecks 模式下)为属性传递undefined,因此您也需要检查它(或使用断言)。
  • 您真的打算将 non_field_errorsemail 设为 1 元素数组(也称为 1 元组或单例)吗?这就是[string] 的意思:数组中的单个字符串。如果数组可以包含多个元素,则应使用语法string[]Array<string>

标签: typescript types typescript-typings


【解决方案1】:

如果我的理解正确,只需将参数声明为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

【讨论】:

  • 谢谢!这样就解决了一半的问题。它解决了字符串的可能性。但它缺少的是要检查界面的键(non_field_errors、detail、email)。
  • @J.Hesters - 正如您在问题中所说,如果它们是可选的(并且您现在已经确认它们是可选的),请使用 ? 标记它们。我已经更新了答案。
  • 任何对象现在都匹配ServerAlerts,因为所有字段都是可选的。
  • 那是因为您使用的是文字对象,当使用文字对象时打字稿会抱怨额外的字段。如果您使用非文字对象,它不会失败(游乐场链接太长,无法评论,所以只需将这三行添加到您的示例中)let nonLiteralServerAlerts; nonLiteralServerAlerts = { foo: ["x"] }; determineError(nonLiteralServerAlerts);
  • @Motti - 谢谢!我已将其折叠到答案中。
猜你喜欢
  • 1970-01-01
  • 2021-09-28
  • 2020-05-02
  • 1970-01-01
  • 2010-10-17
  • 2022-01-18
  • 2017-08-01
  • 2011-06-12
  • 1970-01-01
相关资源
最近更新 更多