简体   繁体   English

在打字稿中键入具有最大属性数的动态键

[英]Type dynamic keys with max number of properties in typescript

I was wondering if it is possible in typescript to type the max number of dynamic properties in an object.我想知道是否可以在打字稿中键入对象中动态属性的最大数量。

So the basic example is for tracking events:所以基本的例子是跟踪事件:

events.track('SOME_EVENT', { first: 'a', other: 'b', some: 'c'})

the event data is supposed to hold a maximum of 3 properties with their respective values, the keys could also be dynamic.事件数据应该最多包含 3 个属性及其各自的值,键也可以是动态的。

I've typed it with a basic Record , but there is no limit on the amount of properties allowed:我用基本的Record输入了它,但是允许的属性数量没有限制:

export interface Events {
  track: (name: string, params?: Record<string, string | number | unknown>) => void;
}

Is this possible?这可能吗?

I can't think of a way to declare an object interface with 1-3 (but only 1-3) unknown string-named properties (but that doesn't mean there isn't one; I'm only at a journeyman level with TypeScript).我想不出一种方法来声明一个具有 1-3 个(但只有 1-3 个)未知字符串命名属性的对象接口(但这并不意味着没有;我只是在熟练工级别使用打字稿)。

I'd lean toward a union of tuples:我倾向于元组的联合:

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 −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^

Playground link 游乐场链接

But that may not have the best ergonomics.但这可能没有最好的人体工程学。

I got a solution using TuplifyUnion from here :我从这里得到了使用TuplifyUnion的解决方案:

I am not sure how "safe" this is (see the disclaimer ).我不确定这有多“安全”(请参阅​​免责声明)。 Using TuplifyUnion is considered unsafe since the order may change at any time.使用TuplifyUnion被认为是不安全的,因为顺序可能随时更改。 Since the order is not important here and only the amount of elements matters in this case, I think this is ok to use here.由于顺序在这里并不重要,在这种情况下只有元素的数量很重要,我认为在这里使用它是可以的。

The solution allows 0-3 keys.该解决方案允许 0-3 个密钥。 If you want any other amounts, just add them into the union (eg 1 | 2 accepts 1 or 2 keys).如果您想要任何其他数量,只需将它们添加到联合中(例如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>
) {}

We basically put all keys into a tuple and then "manually" check the length of the tuple.我们基本上将所有键放入一个元组中,然后“手动”检查元组的长度。 This can be easily extended to other amounts of properties though it might get ugly.这可以很容易地扩展到其他数量的属性,尽管它可能会变得丑陋。

One downside is the error message though:一个缺点是错误消息:

Type 'string' is not assignable to type 'never'.(2322)类型“字符串”不可分配给类型“从不”。(2322)

This may be confusing for someone using the function...这可能会让使用该功能的人感到困惑......


Here are some tests:以下是一些测试:

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

Playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM