typescript - 对象属性值的递归类型约束
问题描述
我试图以递归方式约束对象上的属性类型。最终目标是给定对象的属性需要是number
,string
或另一个符合相同描述的嵌套对象(属性类型为number
,string
或进一步嵌套)
目前我发现的唯一方法是使用索引签名。就像在这个 TypeScript Playground 的代码示例中演示的那样,但是它并不完美,因为需要在类中添加额外的行,以及它在我的代码库的其他地方导致的问题(它会阻止类型推断在某些地方)
type AllowedTypes = string | number | ConstrainedClass;
type ConstrainedClass = { [key: string]: AllowedTypes };
class Test2 {
[key: string]: AllowedTypes; // this line is needed
public prop1: number;
}
class Test1 {
[key: string]: AllowedTypes; // this line is needed
public prop1: string;
public prop2: number;
public nestedProp: Test2;
}
function somefunction<T extends ConstrainedClass>(param: T) {
return;
}
somefunction(new Test1());
我想知道是否有更好的方法来做到这一点?
解决方案
你可以得到你想要的,使用映射的条件类型和自界泛型(我找不到任何好的 TypeScript 文档;但在 Java 中有类似的用途,可能是好的读物?)。让我们来看看:
type Constrained<T> = {
[K in keyof T]: T[K] extends object ? Constrained<T[K]> :
T[K] extends string | number ? T[K] : never
}
AConstrained<T>
接受一个类型T
并递归地遍历它,检查每个属性是 a string
、 anumber
还是object
也符合的 an Constrained
。它不喜欢的任何属性都替换为never
.
有了它,你可以像这样创建你的接口:
interface Test extends Constrained<Test> {
a: string; // okay
b: number; // okay
// c: boolean; // uncommenting this causes an error
d: { foo: string; bar: string } // okay
}
注意自界泛型,其中Test
声明为 extend Constrained<Test>
。这会强制执行您想要的精确约束,而无需索引签名。如果你添加一个不满足约束的属性,它会给你一个错误,通常类似于Type 'XXX' is not assignable to type 'never'
.
用类做到这一点看起来像这样:
class Test2 implements Constrained<Test2> {
public prop1: number = 1;
}
class Test1 implements Constrained<Test1> {
public prop1: string = "a";
public prop2: number = 1;
public nestedProp: Test2 = new Test2();
}
(我添加了初始化程序,因为 TypeScript 现在在您不初始化类实例时会抱怨)。你的函数是这样工作的:
function someFunction<T extends Constrained<T>>(x: T) { }
someFunction(new Test1());
希望有帮助。祝你好运!