简体   繁体   English

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

[英]Typescript constrain generic to string literal type for use in computed object property

I'm trying to write a function which will take a string literal and return an object with a single field whose name is that string literal.我正在尝试编写一个函数,该函数将接受一个字符串文字并返回一个带有单个字段的对象,该字段的名称是该字符串文字。 I can write a function that does what I want, but I don't know how to express the constraint that its argument type be a string literal.我可以编写一个执行我想要的函数的函数,但我不知道如何表达其参数类型为字符串文字的约束。

The closest I've got is using a generic type which extends string .我得到的最接近的是使用扩展string的泛型类型。 This permits string literal types, but also unions of string literal types and the type string , which I don't want to be passed to my function.这允许字符串文字类型,但也允许字符串文字类型和类型string ,我不想将其传递给我的函数。

This compiles, and does what I want, provided that K is a string literal type.只要K是字符串文字类型,就可以编译并执行我想要的操作。 Note that type assertion wasn't necessary in typescript 3.4 but it is required in 3.5.请注意,类型断言在 typescript 3.4 中不是必需的,但在 3.5 中是必需的。

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

If K is anything other than a string literal, the return type of this function won't be the same as the type of the value it returns.如果K不是字符串文字,则此函数的返回类型将与它返回的值的类型不同。

The 2 avenues I can imagine for making this work are:我可以想象的实现这项工作的 2 条途径是:

  • constrain K to be only string literals将 K 限制为仅字符串文字
  • express that the return type be an object with a single field whose name is a value in K (less satisfying, but at least the type of the function will be honest)表示返回类型是具有单个字段的对象,其名称是 K 中的值(不太令人满意,但至少函数的类型是诚实的)

Can typescript's type system express either of these?打字稿的类型系统可以表达其中的任何一个吗?

If I remove the type assertion in typescript 3.5 I get the error:如果我删除 typescript 3.5 中的类型断言,我会收到错误消息:

a.ts:2:3 - error TS2322: Type '{ [x: string]: string; }' is not assignable to type '{ [P in K]: string; }'.

2   return { [key]: "Hello, World!" };

There is no constraint for something to be a single string literal type.对于单个字符串文字类型没有限制。 If you specify extends string the compiler will infer string literal types for K but it will also by definition allow unions of string literal types (after all the set of a union of string literal types is included in the set of all strings)如果您指定extends string ,编译器将推断K字符串文字类型,但根据定义,它也将允许字符串文字类型的联合(毕竟字符串文字类型的联合集合包含在所有字符串的集合中)

We can create a custom error, that forces as call to be in an error state if it detects a union of string literal types.我们可以创建一个自定义错误,如果它检测到字符串文字类型的联合,则强制调用处于错误状态。 Such a check can be done using conditional types making sure that K is the same as UnionToIntersection<K> .这种检查可以使用条件类型来完成,确保KUnionToIntersection<K>相同。 If this is true K is not a union, since 'a' extends 'a' but 'a' | 'b'如果这是真的K不是联合,因为'a' extends 'a''a' | 'b' 'a' | 'b' does not extends 'a' & 'b' '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'

Update for TypeScript 4.2 TypeScript 4.2 更新

The following works:以下工作:

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

(No longer works): TypeScript 4.1 template literal type trick (不再有效):TypeScript 4.1 模板文字类型技巧

Edit: The below actually broke in 4.2.编辑:下面的内容实际上是在 4.2 中出现的。 Discussion here在这里讨论

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

TS 4.1 introduced template literal types which allows you to convert string literals to other string literals. TS 4.1 引入了模板文字类型,允许您将字符串文字转换为其他字符串文字。 You can convert the string literal to itself.您可以将字符串文字转换为自身。 Since only literals can be templated and not general strings, you just then conditionally check that the string literal extends from itself.由于只能对文字进行模板化,而不能对一般字符串进行模板化,因此您只需有条件地检查字符串文字是否从其自身扩展。

Full example:完整示例:

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.

I don't think unions for K are a problem any more because there is no need to narrow the type for the property key in the makeObject signature.我认为K联合不再是一个问题,因为没有必要缩小makeObject签名中属性键的类型。 If anything this becomes more flexible.如果有的话,这会变得更加灵活。

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

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