通过模板文字从字符串构造类型信息

Construct type information from string via template literal

是否可以直接从提供的字符串构造某种类型?

我想创建如下所示的类型:

type MyConfiguration<T> = {
  items: T[];
  onChange: (updated: ConstructedType<T>) => void;
}

const conf: MyConfiguration = {
  items: ['id', 'nested.id', 'nested.name'],
  onChange: updated => {
    console.log(`You updated ${updated.nested.name}(id: ${updated.nested.id})`);
  },
};

所以它会为 updated 生成一个类型 {id: string; nested: { id: string; name: string}}

这个解决方案可能并不完美,但 updated 的类型似乎是正确的:

type First<T extends string> = T extends `${infer L}.${string}` ? L : T
type Nested<T extends string> = T extends `${string}.${infer R}` ? R : string

type _ConstructedType<T extends string> = string extends Nested<T> ? string : {
  [Key in T as First<Nested<T>>]: _ConstructedType<Nested<T>>
}

type ConstructedType<K extends readonly string[]> = {
  [Key in K[number] as First<Key>]: _ConstructedType<Key>
}

function createConf<K extends readonly string[]>(conf: {items: K, onChange: (updated: ConstructedType<K>) => any}) {
    return conf
}

createConf({
  items: ['id', 'nested.id', 'nested.name'] as const,
  onChange: updated => {
    console.log(`You updated ${updated.nested.name}(id: ${updated.nested.id})`);
  },
})

在您的问题中,您指定想要一个名为 MyConfigurationtype。单独 type 不能在属性之间强制执行任何约束。所以我们必须创建一个名为 createConf 的工厂函数。我们现在可以将 conf 对象传递给 createConf 并推断出所有类型。

我还不知道如何解决的一个缺点是,您必须在 items 数组后面写上 as const。否则 TypeScript 会将类型推断为 string[] 而不是元组。

Playground


感谢@jcalz 修复了这个问题的版本:

type ConstructedType<K extends string> = {
  [P in K as P extends `${infer L}.${string}` ? L : P]:
  [P] extends [`${string}.${infer R}`] ? ConstructedType<R> : string;
}

function createConf<K extends string>(conf:
  { items: readonly K[], onChange: (updated: ConstructedType<K>) => any }) {
  return conf
}

type Test = ConstructedType<'id' | 'nested.id' | 'nested.child.name'>

const x = createConf({
  items: ['id', 'nested.id', 'nested.name', 'nested.child.name'],
  onChange: updated => {
    console.log(`You updated ${updated.nested.name}(id: ${updated.nested.id})`);
  },
})

他还提供了一个alternative solution