通过模板文字从字符串构造类型信息
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})`);
},
})
在您的问题中,您指定想要一个名为 MyConfiguration
的 type
。单独 type
不能在属性之间强制执行任何约束。所以我们必须创建一个名为 createConf
的工厂函数。我们现在可以将 conf
对象传递给 createConf
并推断出所有类型。
我还不知道如何解决的一个缺点是,您必须在 items
数组后面写上 as const
。否则 TypeScript 会将类型推断为 string[]
而不是元组。
感谢@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。
是否可以直接从提供的字符串构造某种类型?
我想创建如下所示的类型:
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})`);
},
})
在您的问题中,您指定想要一个名为 MyConfiguration
的 type
。单独 type
不能在属性之间强制执行任何约束。所以我们必须创建一个名为 createConf
的工厂函数。我们现在可以将 conf
对象传递给 createConf
并推断出所有类型。
我还不知道如何解决的一个缺点是,您必须在 items
数组后面写上 as const
。否则 TypeScript 会将类型推断为 string[]
而不是元组。
感谢@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。