To ensure that the compiler can accurately keep track of the specific type of key being passed in, we must make MyClass
generic with that type. Therefore, instead of just MyClass
, it should be MyClass<K extends string>
.
class MyClass<K extends string> {
Next, we need to specify that the data
property should have a key of type K
with a corresponding value of type string
. It may seem somewhat confusing at first, as the syntax for a computed property name, an index signature, and a mapped type all appear quite similar but have different rules regarding functionality.
Avoid writing { [this.key]: string }
as it implies a computed property key, requiring a statically known literal/symbol type. Instead, since the key type K
is generic, employ a mapped type using {[P in K]: string}
or the equivalent
Record<K, string></code utilizing the <code>Record<K, V>
utility type.
If additional properties are needed for data
, you'll need to intersect Record<K, string>
with the remaining object type since mapped types do not permit the addition of other entries.
data: Record<K, string> & { otherProps: { [k: string]: number | undefined }
Furthermore, when creating the constructor, it's advisable not to solely rely on key
of type
K</code, as this could lead to potential conflicts if someone attempts to pass in a value that matches an existing property. To prevent this, use <code>Exclude<K, "otherProps">
from the
Exclude<T, U>
utility type to ensure proper differentiation.
constructor(key: Exclude<K, "otherProps">) {
this.data = { [key]: "prop", otherProps: { a: 1, b: 2, c: 3 } } as typeof this.data;
}
}
new MyClass("okay"); // okay
new MyClass("otherProps"); // error!
Let's now put our code to the test:
const c = new MyClass("hello"); // okay
console.log(c.data.hello.toUpperCase()); // PROP
console.log(Object.entries(c.data.otherProps).map(
([k, v]) => k + ":" + v?.toFixed(2)).join("; ")) // "a:1.00; b:2.00; c:3.00"
Everything appears to be functioning correctly. The compiler recognizes that c.data
includes a string
-valued hello
property, while also accepting otherProps
as a dictionary of number
values.
Link to Playground for code testing