【问题标题】:Typescript: Generic to specific type打字稿:通用到特定类型
【发布时间】:2020-09-07 08:11:45
【问题描述】:

我有一个通用类型

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  },
};

我使用通用类型来帮助构建/检查我创建的新对象。

const Obj1: GenericType = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

它有效。但是,当我使用新对象时,vscode / typescript 不会显示Obj1 的键或道具,而不会从中删除GenericType。我也可以“扩展”类型,但这是代码重复。

Obj1.???

有没有办法在保留 GenericType 的同时从我创建的新对象中访问更具体的道具?


更新 1

我希望 vscode / typescript 显示 / 验证

Obj1.key1.prop1
Obj1.key2.prop1

如果出现错误

Obj1.key1.prop2
Obj1.key2.prop3
Obj1.key2.prop321

【问题讨论】:

  • 您的目标是将Obj1 的键限制为仅key1key2 还是在自动完成菜单中提供key1key2 但仍然能够添加更多以后再开钥匙?
  • @MátéSolymosi 我的目标是提供 keyx。我将使用它来创建多种不同类型的模型。然后在其他函数/道具中使用这些对象。

标签: typescript


【解决方案1】:

我看到这样的用例是,您希望 GenericTypes 将任何类型的字符串作为键,但仍想在您声明它们时准确确定这些键的值。

在这种情况下,您可以使用Record 类型将Obj1 的允许键限制为您指定的键。

type GenericType<K extends string> = Record<K, {
  prop1: string,
  prop2?: string,
  prop3?: number,
}>

然后在定义Obj1时,您可以通过将允许键的并集设置为第一个类型参数来指定允许的键应该是什么。

const Obj1: GenericType<"key1" | "key2"> = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

TypeScript 现在可以让您访问 key1key2 并且具有完全的类型安全性。

Obj1.key1
// (property) key1: {
//     prop1: string;
//     prop2?: string | undefined;
//     prop3?: number | undefined;
// }

编辑

根据 OP 的评论,听起来他宁愿不指定所有键名,也不愿手动检查可选字段是否存在。

在确保您声明的对象与GenericType 接口的约束匹配的同时,我能想到的最佳方法是执行以下操作。

首先你需要这个工具类型:

type Constraint<T> = T extends Record<string, {
  prop1: string,
  prop2?: string,
  prop3?: number,
}> ? T : never

如果T 不匹配约束,这将返回never,否则只返回T

现在你声明你真正想要的普通对象。没有类型注释。

const CorrectObj = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

然后将此对象字面量分配给另一个变量,但声明新变量的类型必须为Constraint&lt;typeof CorrectObj&gt;

const CheckedObj: Constraint<typeof CorrectObj> = CorrectObj

如果CorrectObj 与约束匹配,则CheckedObj 将是CorrectObj 的简单副本,其中包含所有可用字段。

如果对象字面量与约束不匹配,但是在尝试将 CheckedBadObj 分配给字面量时会出现类型错误:

const BadObj = {
  key1: {
    progfdgp1: "hi",
  },
  key2: {
    prop1: "bye",
    prdfgop2: "sup",
  },
};

const CheckedBadObj: Constraint<typeof BadObj> = BadObj
//    ^^^^^^^^^^^^^
// Type '{ key1: { progfdgp1: string; }; key2: { prop1: string; prdfgop2: string; }; }' is not assignable to type 'never'. (2322)

解释是Constraint&lt;T&gt;T不匹配时是never,但你仍然试图将非never值分配给CheckedBadObj,导致类型冲突!

这涉及到在声明每个对象字面量的两个实例时有点重复,但这是唯一的方法

  1. 让 TypeScript 知道确切对象上存在哪些字段,包括所有子对象,同时仍然
  2. 检查“通用”类型的值 匹配您的约束

您可以在playground 中尝试这种方法。

【讨论】:

  • 这是最接近纯打字稿的,但它有几个问题。除了必须输入每个键之外,它还为我提供了 key1 上的 prop2。
  • 与其他解决方案相比,此解决方案存在类似问题。它允许包含不受通用类型限制的属性,即。 { key1: { prop1: "hi", prop321: "what" } 。但是,它比其他解决方案更好,因为不需要额外的函数包装器。
  • 为什么有额外的属性是个问题?
  • 我使用 Typescript 的目的是减少类型检查错误或无效属性等错误。如果我能够创建无效属性(可能是由于拼写错误)并且没有收到任何警告,我会坚持使用纯 JS。
【解决方案2】:

假设您的目标是让key1key2 显示在Obj1 的自动完成菜单中,但以后仍然可以设置其他键,这是一个可能的解决方案:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const generify = <T extends GenericType>(obj: T): T & GenericType => obj;

const Obj1 = generify({
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
});

我现在想不出更简单的解决方案,它可以在 GenericType 和仅包含 key1key2 属性的特定类型之间提供相同的交集类型 Obj1

【讨论】:

  • 这是最接近我正在寻找的内容,但是,这是添加了一个不必要的 javascript 函数包装器。我很惊讶没有一种纯粹的打字稿方法来解决这个问题。
  • 看起来我可以添加除 GenericType 中列出的内容之外的新道具。即key1: { prop321: "boom" }
  • 这是此解决方案的一个不幸的副作用,因为 key1: { prop1: string; prop321: string; } 在技术上扩展了 key1: { prop1: string; prop2?: string; prop3?: number; }
  • 我试图想出一个优雅的解决方案来解决这个问题,但每种方法在某种程度上都有点老套。
【解决方案3】:

使用通用实用程序函数 (enforce)

  • 使用通用约束 (extends) 确保对象与 GenericType 匹配
  • 并返回传入对象的类型以进行类型推断。

代码:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const enforce = <T extends GenericType>(obj: T): T => obj;

工作解决方案:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const enforce = <T extends GenericType>(obj: T): T => obj;

const Obj1 = enforce({
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
});


Obj1.key1.prop1; // Ok
Obj1.key2.prop1; // Ok

/** 
 * ERROR: does not match passed in object
 */
Obj1.key1.prop2 // Error 
Obj1.key2.prop3 // Error
Obj1.key2.prop321 // Error

Obj1.key3; // Error 

/**
 * ERRORS: Does not match GenericType
 */
const Obj2 = enforce({
  key1: { // Error 
  }
});
const Obj3 = enforce({
  key1: {
    prop1: 123, // Error
  }
});

【讨论】:

  • 感谢您的回复。不幸的是,这个答案就像 Mate 的答案一样,也有同样的缺点。它仍然允许用户输入比GenericType 中指定的更多的属性,并且当转录为 javascript 时,它添加了一个额外的功能。
猜你喜欢
  • 2018-06-08
  • 1970-01-01
  • 1970-01-01
  • 2020-04-30
  • 1970-01-01
  • 2018-01-16
  • 1970-01-01
  • 2021-03-21
  • 2019-11-02
相关资源
最近更新 更多