Typescript将泛型约束为字符串文字类型,以用于计算对象属性

时间:2019-05-30 07:36:05

标签: typescript

我正在尝试编写一个函数,该函数将使用字符串文字并返回一个名称为该字符串文字的具有单个字段的对象。我可以编写一个可以实现我想要的功能的函数,但是我不知道如何表达其参数类型为字符串文字的约束。

我最近得到的是使用扩展了try { // do something } catch (Error | Exception $e) { echo $e->getMessage(); } 的泛型类型。这样既可以使用字符串文字类型,也可以使用字符串文字类型与类型string的并集,但我不想将它们传递给函数。

只要string是字符串文字类型,它就可以编译并执行我想要的操作。请注意,在打字稿3.4中不需要类型断言,但在3.5中是必需的。

K

如果function makeObject<K extends string>(key: K): { [P in K]: string } { return { [key]: "Hello, World!" } as { [P in K]: string }; } 不是字符串文字,则此函数的返回类型将不同于它返回的值的类型。

我能想到的2种方法是:

  • 将K限制为仅字符串文字
  • 将返回类型表示为具有单个字段的对象,该对象的名称是以K为单位的值(不那么令人满意,但至少函数的类型是诚实的)

打字稿的打字系统可以表达其中一种吗?

如果我删除打字稿3.5中的类型断言,则会收到错误消息:

K

2 个答案:

答案 0 :(得分:3)

TypeScript 4.2 更新

以下工作:

type StringLiteral<T> = T extends string ? string extends T ? never : T : never;

(不再有效):TypeScript 4.1 模板文字类型技巧

编辑:下面的内容实际上是在 4.2 中出现的。 Discussion here

type StringLiteral<T> = T extends `${string & T}` ? T : never;

TS 4.1 引入了 template literal types,它允许您将字符串文字转换为其他字符串文字。您可以将字符串文字转换为自身。由于只能对文字进行模板化,而不能对一般字符串进行模板化,因此您只需有条件地检查字符串文字是否从自身扩展。

完整示例:

type StringLiteral<T> = T extends `${string & T}` ? T : never;

type CheckLiteral = StringLiteral<'foo'>;  // type is 'foo'
type CheckString = StringLiteral<string>;  // type is never

function makeObject<K>(key: StringLiteral<K>) {
    return { [key]: 'Hello, World!' } as { [P in StringLiteral<K>]: string };
}

const resultWithLiteral = makeObject('hello');  // type is {hello: string;}
let someString = 'prop';
const resultWithString = makeObject(someString); // compiler error.

我认为 K 的联合不再是一个问题,因为没有必要缩小 makeObject 签名中属性键的类型。如果有的话,这会变得更加灵活。

答案 1 :(得分:0)

对于任何东西都是单字符串文字类型没有任何限制。如果您指定extends string,则编译器将推断K的字符串文字类型,但根据定义,编译器还将允许字符串文字类型的并集(在该集合中包括所有字符串文字类型的并集之后)的所有字符串中

我们可以创建一个自定义错误,如果它检测到字符串文字类型的并集,则将as调用强制为错误状态。可以使用条件类型确保KUnionToIntersection<K>相同来进行这种检查。如果为真,则K不是联合,因为'a' extends 'a''a' | 'b'不会扩展'a' & 'b'

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type CheckForUnion<T, TErr, TOk> = [T] extends [UnionToIntersection<T>] ? TOk : TErr

function makeObject<K extends string>(key: K & CheckForUnion<K, never, {}>): { [P in K]: string } {
    return { [key]: "Hello, World!" } as { [P in K]: string };
}

makeObject("a")
makeObject("a" as "a" | "b") // Argument of type '"a" | "b"' is not assignable to parameter of type 'never'
相关问题