我正在尝试在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
属性返回包含所有事件的数组?
答案 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);
您可以使用具体的类或对象以几种不同的方式来实现此目的,因此不必完全遵循此示例。