首页 > 解决方案 > 对象属性值的递归类型约束

问题描述

我试图以递归方式约束对象上的属性类型。最终目标是给定对象的属性需要是number,string或另一个符合相同描述的嵌套对象(属性类型为numberstring或进一步嵌套)

目前我发现的唯一方法是使用索引签名。就像在这个 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());

我想知道是否有更好的方法来做到这一点?

标签: typescriptrecursionindexing

解决方案


你可以得到你想要的,使用映射的条件类型和自界泛型(我找不到任何好的 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());

希望有帮助。祝你好运!


推荐阅读