typescript - 强制类型是接口(interface)的子类型并扩展编号

标签 typescript

我正在使用这些接口(interface)

/**
 * Interface for entities
 */
export interface Entity extends Object {
    readonly id: EntityId;
}

/**
 * Interface for collection-based state
 */
export interface Collection<T extends Entity> extends Object {
    readonly entities: { [key: string]: T };
    readonly ids: EntityId[];
}

我正在编写一个辅助函数,它给定路径,增加集合中实体的相应值。

示例:

Collection.increment(myCollection, 'some-id', ['stats', 'total']);

这些是我当前的输入

    export function increment<
        C extends Collection<any>,
        E = C extends Collection<infer U> ? U : never,
        K1 extends keyof E = keyof E,
        V1 extends E[K1]= E[K1]
        >(collection: C, entityId: string, path: K1 | [K1]): C
    export function increment<
        C extends Collection<any>,
        E = C extends Collection<infer U> ? U : never,
        K1 extends keyof E = keyof E,
        V1 = Exclude<E[K1], void>,
        K2 extends keyof V1 = keyof V1,
        V2 extends V1[K2]= V1[K2]
        >(collection: C, entityId: string, path: K1 | [K1] | [K1, K2]): C

对于这个例子,类型非常冗长,因为它们用于对传入的内容进行类型检查

上述类型有效,唯一的问题是非数字类型的路径仍然有效。

I tried something like the below to force it to only allow numbers

export function increment<
    C extends Collection<any>,
    E = C extends Collection<infer U> ? U : never,
    K1 extends keyof E = keyof E,
    V1 extends E[K1]= E[K1]
    >(collection: C, entityId: string, path: K1 | [K1]): C
export function increment<
    C extends Collection<any>,
    E = C extends Collection<infer U> ? U : never,
    K1 extends keyof E = keyof E,
    V1 = Extract<Exclude<E[K1], void>, number>, // Note the Extract
    K2 extends keyof V1 = keyof V1,
    V2 extends V1[K2]= V1[K2]
    >(collection: C, entityId: string, path: K1 | [K1] | [K1, K2]): C

但是上面的内容并没有按预期强制执行约束

有什么想法吗?

最佳答案

您需要按属性类型过滤可用键。从 Omit 中获取线索type 我们可以使用条件类型来创建一个类型,该类型将根据属性类型过滤掉属性。

type FilterKeysByType<T, U> = ({[P in keyof T]: T[P] extends U ? P: never } & { [x: string]: never })[keyof T];  
// If there is a single key, it must be a key of a number field
export function increment<
    C extends Collection<any>,
    E = C extends Collection<infer U> ? U : never,
    K1 extends FilterKeysByType<E, number | undefined> = FilterKeysByType<E, number | undefined>,
    V1 extends Exclude<E[K1], void> = Exclude<E[K1], void>
>(collection: C, entityId: string, path: K1 | [K1]): C
// If there are two keys, the first key, can be any  type, but the second key must be the key of a number field 
export function increment<
    C extends Collection<any>,
    E = C extends Collection<infer U> ? U : never,
    K1 extends keyof E = keyof E,
    V1 = Exclude<E[K1], void>,
    K2 extends FilterKeysByType<V1, number| undefined> = FilterKeysByType<V1, number| undefined>,
    V2 extends V1[K2]= V1[K2]
    >(collection: C, entityId: string, path: [K1, K2]): C

// Usage
declare var c : Collection<Comment>;

interface Comment extends Entity{
    name: string;
    value: number;
    optValue?: number;
    subcomment: Comment;
    optSubcomment?: Comment;
}

increment(c, "", "value"); //ok
increment(c, "", "optValue"); //ok
increment(c, "", "name");// error
increment(c, "", ["subcomment", "value"]); // ok
increment(c, "", ["subcomment", "optValue"]); // ok
increment(c, "", ["subcomment", "name"]); // error
increment(c, "", ["optSubcomment", "value"]); // ok
increment(c, "", ["optSubcomment", "optValue"]); // ok
increment(c, "", ["optSubcomment", "name"]); // error

关于typescript - 强制类型是接口(interface)的子类型并扩展编号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49387333/

相关文章:

javascript - 如何在 NodeJS 中公开 TypeScript 模块?

javascript - 为什么Windows.dispatchEvent()在Angular 9的app.component.ts上不起作用

javascript - typescript 对象 : How do I restrict the keys to specific strings?

Angular2 - 在指令中更改 innerHTML 时动态创建 routerLink

javascript - 如何在单击按钮时验证输入不保存 0 或小于 0?

javascript - Push() 无法显示字符串

module - 使用 TypeScript 的默认导入

typescript - TypeScript 规范中 AB|C 中的 AB 是什么意思?

javascript - Angular2 在 ngFor 循环中绑定(bind)事件/DOM

javascript - 如何将属性转换为 bool 值(深)