繁体   English   中英

打字稿:通用到特定类型

[英]Typescript: Generic to specific type

我有一个通用类型

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

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

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

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

Obj1.???

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


更新 1

我希望 vscode/typescript 显示/验证

Obj1.key1.prop1
Obj1.key2.prop1

和错误如果

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

我看到的用例是,您希望GenericType将任何类型的字符串作为键,但仍想确切地确定这些键的值在您声明它们的地方可以是什么。

在这种情况下,您可以使用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<typeof CorrectObj>

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)

解释是当T不匹配时Constraint<T>never ,但您仍在尝试为CheckedBadObj分配一个非never值,从而导致类型冲突!

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

  1. 让 TypeScript确切知道对象上存在哪些字段,包括所有子对象,同时仍然
  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",
  },
});

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

使用一个通用的实用函数( 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
  }
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM