具有选定属性的递归嵌套类型

Recursive nesting typings with selected properties

所以我有以下接口和类型:

interface A {
    A(): void;
}

interface B {
    B(): void;
}

interface C {
    C(): void;
}

type All = A & B & C;

我想提取部分方法集并创建另一个可以递归执行相同操作的嵌套级别:[typescript version 4.1.0]

type SelectMethods<
    METHODS extends Partial<All>, 
    MORE extends Record<string, SelectMethods<any>> = {}
> = Pick<All, Extract<keyof All, keyof METHODS>> & MORE;

这让我可以执行以下操作,效果很好

const obj: SelectMethods<A & B, { x: SelectMethods<A & B & C> }> = {
    A() {
    },
    B() {
    },
    x: {
        A() {
        },
        B() {
        },
        C() {
        }
    }
};

但是当删除内部 SelectMethods 中的 C(或任何其他)时,它 停止工作 : [星号之间的字面量出现错误]

const obj: SelectMethods<A & B, **{ x: SelectMethods<A & B> }**> = {  // Property 'C' is missing in type 'Pick<All, "A" | "B">' but required in type 'Pick<All, "A" | "B" | "C">'.
    A() {
    },
    B() {
    },
    x: {
        A() {
        },
        B() {
        }
    }
};

到目前为止我尝试过的事情:

type SelectMethods<METHODS extends Partial<All>, MORE extends Record<string, SelectMethods<any>> = {}> =
    Pick<All, Extract<keyof All, keyof METHODS>> & (
        MORE extends Record<string, infer DEEP> ?
            DEEP extends SelectMethods<infer SUBMETHODS> ? 
                SUBMETHODS extends Partial<All> ?
                    MORE
                    : never
                : never
            : never
    );

但我得到 TS2589: Type instantiation is excessively deep and possibly infinite.

有什么想法吗? :)

问题在于

Pick<All, Extract<keyof All, keyof METHODS>>

让我们把它变成自己的类型,这样我们就可以玩了:

type MethodPick<METHODS extends Partial<All>> = Pick<All, Extract<keyof All, keyof METHODS>>

这在第一层工作正常。 type AB = MethodPick<A & B> 只是 A 和 B。

但是 SelectMethods<any> 又名 SelectMethods<Partial<All>> 的实际类型是什么?您可能认为这将是方法的部分选择,但事实并非如此。它实际上要求 A、B 和 C 都已设置。这是因为当我们执行 MethodPick 时,我们获取了我们正在选择的内容的键并获得了那些方法。但缺陷是 我们还没有检查是否需要这些密钥keyof Partial<All>'A' | 'B' | 'C',所以 type P = MethodPick<Partial<All>> 是 A & B & C。这意味着 SelectMethods<any> 是 A & B & C,其中需要所有三种方法。

MORE extends Record<string, SelectMethods<any>>

所以上面的语句意味着这条记录的值必须扩展All,而不是Partial<All>。而这不是我们想要的。

如果我们已经知道 METHODS extends Partial<All>,为什么我们还需要 Pick

我们将类型简化为 = METHODS & MORE,并将 SelectMethods<any> 替换为 SelectMethods<Partial<All>>,因为 any 不是真正有效的输入,我们得到:

type SelectMethods<
    METHODS extends Partial<All>, 
    MORE extends Record<string, SelectMethods<Partial<All>>> = {}
> = METHODS & MORE;

这应该可以解决您的问题。

Typescript Playground Link