Description
Continue discussion on #12596 Allow property (dotted) access syntax for types with string index signatures (introduced in TS 2.2).
Let's consider a component with options.
class MyComponent {
static defaultOptions: MyComponent.Options = {
presentation: "dropdown"
};
options: MyComponent.Options;
constructor(options: MyComponent.Options) {
this.options = utils.append(this.options, options, {deep: true});
}
}
namespace MyComponent {
export interface Options {
presentation: "dropdown" | "inline";
}
}
All options the component uses are declared in its interface.
So to create an instance:
let c = new MyComponenent({ presentation: "inline" });
Now, imagine that component accepts a callback:
export interface Options {
presentation: "dropdown" | "inline";
onBeforeRender: () => void;
}
let c = new MyComponenent({
onBeforeRender: () => {
}
});
Let's imagine that inside onBeforeRender we need access to some value passed from parent content.
let c = new MyComponenent(utils.append({
onBeforeRender: () => {
let val = this.options["theValue"];
}
}, {
theValue: theValueFromParent
});
So we're adding an aspect to class and it depends on some data provided by a different aspect.
The easiest way to implement something like this was to add index signature into Options
:
namespace MyComponent {
export interface Options {
[key: string] => any;
}
}
It allowed us to extend components without subclassing.
Now (since TS 2.2) it has very serious impact - we lost type checking for Options
type at all. The worst thing is that it could be not very clear for people migrating from previous versions. Everything still works. No breaking changes. But accidentally one can notice then compiler doesn't help anymore:
class MyComponent {
static defaultOptions: MyComponent.Options = {
prisintetion: "dropdown" // should be error but it's not
};
}
So, probably we have two different cases here: a type as map and a type with optional extensibility.
I'd suggest to introduce optional index signature which implements the previous behavior:
export interface Options {
[key: string]? => any;
}
this would mean the same as before:
let opt: Options;
opt.noExisting; // error
opt["noExisting"]; // ok