从泛型类型创建时映射类型产生意外结果

Mapped type producing unexpected results when created from generic type

我正在使用静态对象来描述契约定义,并尝试基于它们生成类型。这些合约可以有选择地有一些选项,我想用一组选项名称来描述这些选项。然后我想使用这个数组来构造一个类型来传递选项值。所有选项都应该是必需的。见以下代码:

const definition  = { optionNames: ['a', 'b'] as const }

type OptionNames = typeof definition['optionNames'][number]
// type OptionNames = "a" | "b"

type Options = Record<OptionNames, string>
// type Options = {
//   a: string;
//   b: string;
// }

到目前为止一切顺利,我可以毫无问题地静态构造 Options 类型。但是,当我尝试对泛型类型执行相同操作时,我得到了一些意想不到的结果:

interface Definition { optionNames?: readonly string[] }

type ConstructOptions<T1 extends Definition, T2 = T1['optionNames'][number]> = T2 extends string | number | symbol ? Record<T2, string> : {}

type GenericOptions = ConstructOptions<typeof definition>
// type GenericOptions = Record<"a", string> | Record<"b", string>

我希望 GenericOptions 与 Options 相同,但结果是 Records 的联合,每个 Records 只有 1 个选项作为必需的键。为什么会这样?

使用元组确保不单独考虑每个成分:

type ConstructOptions<T1 extends Definition, T2 = T1['optionNames'][number]> = [T2] extends [string | number | symbol] ? Record<T2, string> : {}

完整示例:

const definition  = { optionNames: ['a', 'b'] as const }

interface Definition { optionNames?: readonly string[] }

type ConstructOptions<T1 extends Definition, T2 = T1['optionNames'][number]> = [T2] extends [string | number | symbol] ? Record<T2, string> : {}

type GenericOptions = ConstructOptions<typeof definition>;

一个你可能永远不会想到的奇怪的东西,但它确实有效。这里只有 TypeScript 的东西 ✌️。

Playground

顺便说一下,您可能想使用 NonNullableRequired 修复该错误 ;)