我正在使用这些接口
--globalize-symbol
我正在编写一个辅助函数,它给出一个路径,增加集合中实体的相应值。
示例:
/**
* 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
但上述内容并未按预期强制实施约束
有什么想法吗?
答案 0 :(得分:2)
您需要按属性类型过滤可用的键。从Omit
类型中获取提示,我们可以使用条件类型来创建一个类型,该类型将根据属性类型过滤掉属性。
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
答案 1 :(得分:2)
我尽量不打破条件类型,除非我没有它们就无法解决问题(因为它们有时会有幽灵般的推理属性,我无法将我的大脑包裹起来)。那么这些打字怎么样呢?
declare function increment<
C extends Collection<Entity & Record<K1,Record<K2,number>>>,
K1 extends string,
K2 extends string>(collection: C, entityId: string, path: [K1, K2]): C;
declare function increment<
C extends Collection<Entity & Record<K1, number>>,
K1 extends string>(collection: C, entityId: string, path: K1 | [K1]): C;
只有C
和K1
(可能是K2
)。我的想法是C
应该是Collection<Entity & Something>
,其中Something
是Record<K1, number>
或Record<K1, Record<K2, number>>
,具体取决于{{1}中的元素数量}}。 (如果重要,path
定义为Record<K extends string, V>
,它只是意味着“具有{[P in K]: V}
类型的键和类型K
的值”的对象。)
您可以验证其行为符合要求:
V
希望有所帮助;祝你好运!