声明两个不相关类型的公共属性名称的编译器可检查数组

时间:2018-08-15 16:19:13

标签: typescript

我正在使用TypeScript 2.9.2。我有一堂课:

class Class1 {
    public prop1: number;
    public prop2: number;
    public prop3: number;
}

我声明了一个ReadOnlyArray,代表Class1键的子集:

private readonly keysSubset: ReadOnlyArray<keyof Class1> = ["prop1", "prop2"];

现在,我想声明一个变量,该变量可以采用keysSubset中声明的任何属性名称:

const someKey: ? = this.keysSubset[1];

我既不想提供string类型,也不想手动枚举如下所示的可能值:

const someKey: "prop1" | "prop2" = this.keysSubset[1];

我想明确地说“它可以包含来自keysSubset的任何值”,所以当keysSubset中的项目在代码中被静态更改(而不是在运行时)时,我不会必须手动更改someKey类型声明,编译器仍然可以检查是否为someKey分配了keysSubset中的值之一。

更新(要提供更多基本原理信息):
我需要someKey的确切类型来遍历keysSubset并从类型Class2的对象更新不同类Class1的实例的某些属性:

class Class2 {
    public prop1: number;
    public prop2: number;

    private readonly keysSubset: ReadOnlyArray<keyof Class1> = ["prop1", "prop2"];

    public updateFrom(source: Class1): void {
        this.keysSubset.forEach((someKey: ?/*keyof Class1 won't work since Class2 doesn't have prop3*/): void => {
                this[someKey] = source[someKey];
            });
    }
}

简而言之,我要解决的问题是从不同类型Class1的实例更新Class2的给定属性子集。虽然子集是静态的,但每次属性更新都有一些通用逻辑,因此我不想只写一堆class1[someLey] = class2[someLey]语句。

在这种情况下如何声明someKey的类型?

2 个答案:

答案 0 :(得分:1)

问题在于keysSubset的类型是该类中所有键的数组。这是我们首先需要更改的内容,以便keysSubset的类型正是分配值的数组。我们可以为此使用辅助函数,同时仍然将值约束为类的键。

要获取数组中项目的类型,我们可以使用类型查询。

class Class1 {
    public prop1: number;
    public prop2: number;
    public prop3: number;
}
// Helper function to infer the subset of keys correctly 
function keyArrayOf<T>() {
    return function <K extends keyof T>(o: K[]) {
        return o;
    }
}

class Foo {
    // will be inferred as  ("prop1" | "prop2")[]
    private readonly keysSubset = keyArrayOf<Class1>()(["prop1", "prop2"])
    method(){
        // Use a type query to get the type of an item of the keysSubset array
        const someKey: Foo['keysSubset'][number] = this.keysSubset[1]; // "prop1" | "prop2"
        // Or just let inference work
        const someKeyInffered = this.keysSubset[1]; // "prop1" | "prop2", no need for an explicit annotation 
    }
}

修改

您可以使用keyArrayOf帮助函数编写更新后的示例,

class Class2 {
    public prop1: number;
    public prop2: number;

    private readonly keysSubset = keyArrayOf<Class1>()(["prop1", "prop2"])

    public updateFrom(source: Class1): void {
        this.keysSubset.forEach((someKey: Class2['keysSubset'][number]): void => {
            this[someKey] = source[someKey];
        });
    }
}

答案 1 :(得分:0)

@ TitianCernicova-Dragomir post包含了我最初问题的直接答案。此答案包含了如何声明编译器检查的答案-能够使用两个不相关类型的通用属性名称数组。感谢@Titian,我意识到我在回想:我不需要基于常量字符串数组值创建联合类型类型,该类型由Class1Class2 的通用属性名称组成,然后,我就可以声明一个数组因此,下面包含了该问题的答案。)

TLDR

这里是如何声明仅包含其他两种类型的通用属性的类型:

type CommonProperty = keyof {
  [P in keyof Class1 & keyof Class2]: Class1[P] | Class2[P]
}

现在,您可以声明一个包含所需公共属性子集的数组,该数组将由编译器检查值的正确性:

const keysSubset: ReadOnlyArray<CommonProperty> = [
        "prop1" // Ok,
        "prop2" // Ok,
        "prop4" // Error - property is missing in Class1
    ];

逐步:

方法的第一步是了解如何声明类型Class1Class2中仅包含公共属性的类型:

type CommonFromClass1AndClass2 = {
  [P in keyof Class1 & keyof Class2]: Class1[P] | Class2[P]
}

那对我来说是最复杂的部分。

现在,我们可以声明一个表示通用属性名称子集的字符串联合类型:

type CommonProperty = keyof CommonFromClass1AndClass2;

最后,我们可以在数组声明中使用它:

const keysSubset: ReadOnlyArray<CommonProperty> = [
        "prop1" // Ok,
        "prop2" // Ok,
        "prop4" // Error - property is missing in Class1
    ];