I am trying to produce an events
property on Klass
which contains an array of strings that exactly matches all the keys of a given interface. Like so:
interface Events {
one: (foo: string) => void
two: (bar: number) => void
}
class Klass {
protected readonly events: [keyof Events] = ['one', 'two']
}
However, the above errors out with the following:
[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"]
What is needed here to ensure that the events
property returns an array that contains all of the events?
To get an object's key by value in TypeScript: Use the Object. keys() method to get an array of the object's keys. Type the array to be an array of the object's keys.
Use an index signature to define a key-value pair in TypeScript, e.g. const employee: { [key: string]: string | number } = {} .
To iterate over interface properties in TypeScript, we can use the Object. keys method. const Activity = { id: "", title: "", body: "", json: {}, }; type Activity = typeof Activity; const headers: Array<Object> = Object. keys(Activity).
TypeScript Interface Inheritance Like JavaScript classes, an interface can inherit properties and methods from another interface using the extends keyword.
You can almost express this in the type system (assuming TS3.0+) with conditional types, with a few caveats:
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
Note that goodEvents
is inferred to be of type ['one', 'two']
, and there is no error. That's what you want. You get errors on extra events and on missing events.
Caveat 1: The error for missing events is a bit cryptic; TypeScript doesn't yet support custom error messages, so I chose something that hopefully is somewhat understandable (Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"]
).
Caveat 2: There is no error for redundant events. There's no general way that I can find to require that each parameter to arrayOfAllEventKeys
is of a distinct type that doesn't run afoul of some issues with recursive types. It's possible to use overloading or other similar techniques to work for arrays of up to some hardcoded length (say, 10), but I don't know if that would meet your needs. Let me know.
Hope that helps; good luck!
You are putting the [] in the wrong place.
interface Events {
one: (foo: string) => void
two: (bar: number) => void
}
class Klass {
protected readonly events: (keyof Events)[] = ['one', 'two']
}
Notice how I change [keyof Events]
to (keyof Events)[]
.
Check this typescript playground to verify that there are no errors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With