如何确保数组的值是Typescript接口的键?

时间:2018-11-20 07:04:56

标签: typescript

我正在尝试在events上产生一个Klass属性,该属性包含与给定接口的所有键完全匹配的字符串数组。像这样:

interface Events {
    one: (foo: string) => void
    two: (bar: number) => void
}

class Klass {
    protected readonly events: [keyof Events] = ['one', 'two']
}

但是,上述错误与以下内容有关:

[ts]
Type '["one", "two"]' is not assignable to type '["one" | "two"]'.
  Types of property 'length' are incompatible.
    Type '2' is not assignable to type '1'. [2322]
(property) Klass.events: ["one" | "two"]

这里需要什么来确保events属性返回包含所有事件的数组?

3 个答案:

答案 0 :(得分:5)

您几乎可以使用conditional types在类型系统(假设为TS3.0 +)中表达这一点,但要注意以下几点:

type Invalid<T> = ["Needs to be all of", T]
const arrayOfAll = <T>() => <U extends T[]>(
  ...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>[])
) => array;
const arrayOfAllEventKeys = arrayOfAll<keyof Events>();

const goodEvents = arrayOfAllEventKeys('one', 'two'); // okay, type ['one', 'two']

const extraEvents = arrayOfAllEventKeys('one', 'two', 'three'); // error
//                                                    ~~~~~~~ 
// Argument of type "three" is not assignable to parameter of type "one" | "two"

const missingEvents = arrayOfAllEventKeys('one'); // error
//                                        ~~~~~ 
// Argument of type "one" is not assignable to 
// parameter of type ["Needs to be all of", "one" | "two"]

const redundantEvents = arrayOfAllEventKeys('one', 'two', 'one'); // no error
// doesn't enforce distinctness

请注意,goodEvents的类型为['one', 'two'],并且没有错误。那就是你想要的。您会在多余事件和丢失事件上出错。

注意事项1:丢失事件的错误有点神秘; TypeScript尚不支持custom error messages,所以我选择了一些希望可以理解的东西(Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"])。

注意事项2:冗余事件没有错误。我找不到一种通用的方法来要求arrayOfAllEventKeys的每个参数都必须具有独特的类型,并且不会与某些issues with recursive types发生冲突。可以使用重载或其他类似技术来处理最大长度为硬编码(例如10)的数组,但是我不知道这是否满足您的需求。让我知道。

希望有所帮助;祝你好运!

答案 1 :(得分:3)

您将[]放在错误的位置。

interface Events {
  one: (foo: string) => void
  two: (bar: number) => void
}

class Klass {
  protected readonly events: (keyof Events)[] = ['one', 'two']
}

请注意如何将[keyof Events]更改为(keyof Events)[]

检查this typescript playground以确认没有错误。

答案 2 :(得分:1)

我认为您需要一个具体的对象来实现您的目标。在下面的示例中,该接口的实现为您提供了一种获取正确列表的保证方法。如果您将新成员添加到Events,将迫使您将其添加到ConcreteEvents。唯一潜在的问题是,如果您将其他成员添加到ConcreteEvents

interface Events {
    one: (foo: string) => void;
    two: (bar: number) => void;
}

class ConcreteEvents implements Events {
    // Note! Only add members of Events
    one(foo: string) { };
    two(bar: number) { };
}

class Klass {
    public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events][];
}

const klass = new Klass();
console.log(klass.events);

您可以使用具体的类或对象以几种不同的方式来实现此目的,因此不必完全遵循此示例。