【问题标题】:Typescript constrain generic to string literal type for use in computed object property打字稿将泛型约束为字符串文字类型以用于计算对象属性
【发布时间】:2019-10-15 20:16:03
【问题描述】:

我正在尝试编写一个函数,该函数将接受一个字符串文字并返回一个具有单个字段的对象,该字段的名称是该字符串文字。我可以编写一个函数来做我想做的事,但是我不知道如何表达它的参数类型是字符串文字的约束。

我最接近的是使用扩展string 的泛型类型。这允许字符串文字类型,但也允许字符串文字类型和类型 string 的联合,我不想将其传递给我的函数。

如果K 是字符串文字类型,这将编译并执行我想要的操作。请注意,类型断言在 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 };
}

如果K 不是字符串字面量,则此函数的返回类型将与它返回的值的类型不同。

我能想到的实现这项工作的两条途径是:

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

typescript 的类型系统可以表达这些吗?

如果我删除 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!" };

【问题讨论】:

  • 不确定结果应该是什么。 makeObject("a" as "a" | "b"); 应该返回什么? { a?: string; b?: string; } 也许?如果K 不是单个字符串文字(即不是联合),我们可以触发错误这是你想要的吗?
  • 理想情况下,您的示例会导致类型错误,因为参数是字符串文字的联合,而不是字符串文字。

标签: typescript


【解决方案1】:

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 签名中属性键的类型。如果有的话,这会变得更加灵活。

【讨论】:

  • 是否可以将字符串字面量泛型转换为实际的字符串值?
【解决方案2】:

对于单个字符串文字类型没有任何限制。如果您指定extends string,编译器将推断K 的字符串文字类型,但它也将根据定义允许字符串文字类型的联合(毕竟字符串文字类型的联合集合包含在所有字符串的集合中)

我们可以创建一个自定义错误,如果它检测到字符串文字类型的联合,它会强制 as 调用处于错误状态。可以使用条件类型进行此类检查,确保KUnionToIntersection&lt;K&gt; 相同。如果这是真的K 不是联合,因为'a' extends 'a''a' | 'b' 不会扩展'a' &amp; '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'

【讨论】:

  • 写元组类型[T] extends [UnionToIntersection&lt;T&gt;]的目的是什么?是否只是为了防止在T是联合类型的情况下将条件分布到联合上?
  • 另外,有关UnionToIntersection 为何有效的解释,请参阅stackoverflow.com/questions/50374908/…
猜你喜欢
  • 2021-04-20
  • 2019-03-10
  • 2021-12-06
  • 2022-11-23
  • 2021-12-10
  • 2019-09-23
  • 1970-01-01
  • 2019-03-12
  • 2021-11-06
相关资源
最近更新 更多