javascript - 在 TypeScript 中为动态键指定最大属性数量的类型
我在想在 TypeScript 中是否有可能对对象中动态属性的最大数量进行类型限制。
所以基本的例子是用于跟踪事件:
events.track('SOME_EVENT', { first: 'a', other: 'b', some: 'c'})
事件数据应包含最多3个属性及其各自的值,键也可以是动态的。
我用一个基本的 Record 类型来编写,但允许的属性数量没有限制:
export interface Events {
track: (name: string, params?: Record<string, string | number | unknown>) => void;
}
这可能吗?
答案#1
我在这里使用 TuplifyUnion 找到了解决方案:
我不确定这有多“安全”(请参见免责声明)。使用 TuplifyUnion 被认为是不安全的,因为顺序可能随时变化。由于这里顺序并不重要,这里仅关心元素的数量,我认为在这里使用是可以的。
该解决方案允许 0-3 个键。如果你想要其他数量,只需将它们添加到联合类型中(例如 1 | 2 接受 1 或 2 个键)。
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type LastOf<T> =
UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never
type Push<T extends any[], V> = [...T, V];
type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> =
true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>
type MaxThreeProperties<T extends Record<string, any>> =
TuplifyUnion<keyof T>["length"] extends 0 | 1 | 2 | 3 ? T : never
// add all acceptable key amounts here ^ ^ ^ ^
function track<T extends Record<string, any>>(
name: string,
params: MaxThreeProperties<T>
) {}
我们基本上把所有键放入一个元组中,然后“手动”检查元组的长度。虽然可能会变得很乱,但这可以很容易地扩展到其他数量的属性。
不过,有一个缺点是会出现错误信息:
类型 'string' 不能分配给类型 'never'。(2322)
这对于使用该函数的人来说可能会令人困惑...
这里有一些测试:
track('SOME_EVENT', {}) // works
track('SOME_EVENT', {a: ""}) // works
track('SOME_EVENT', {a: "", b: ""}) // works
track('SOME_EVENT', {a: "", b: "", c: ""}) // works
track('SOME_EVENT', {a: "", b: "", c: "", d: ""}) // ERROR
track('SOME_EVENT', {a: "", b: "", c: "", d: "", e: ""}) // ERROR
const a = {a: "", b: "", c: ""}
const b = {a: "", b: "", c: "", d: ""}
track('SOME_EVENT', a) // works
track('SOME_EVENT', b) // ERROR
操场
答案#2
我想不出一种方法来声明一个对象接口,该接口有 1 到 3 个(但只能是 1 到 3 个)未知的字符串命名属性(但这并不意味着没有这种方法;我在 TypeScript 上只处于中级水平)。
我倾向于使用元组的联合:
type EventParam = [name: string, value: string | number | unknown];
type EventParams =
[EventParam]
| [EventParam, EventParam]
| [EventParam, EventParam, EventParam];
export interface Events {
track: (name: string, params?: EventParams) => void;
}
declare let events: Events;
// Works with 1:
events.track("something", [["first", "a"]]);
// Works with 2:
events.track("something", [["first", "a"], ["other", "b"]]);
// Works with 3:
events.track("something", [["first", "a"], ["other", "b"], ["some", "c"]]);
// Fails with 4:
events.track("something", [["first", "a"], ["other", "b"], ["some", "c"], ["fourth", 42]]);
// Error as desired −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^
操场链接
但那可能没有最佳的人体工学设计。
