问题
I have the following Interface definitions.
interface IComponents {
root: IComponent,
[key: string]: IComponent,
}
interface IComponent {
type: string,
children?: Array<keyof IComponents>;
}
I want that the "children" properties accept only keys of defined Components. in the case of the "root.children"-property it should only accept root, button1 and button2:
const list: IComponents = {
root: {
type: 'panel',
children: ['button1', 'button2', 'button3']
},
button1: {
type: 'button'
},
button2: {
type: 'button'
},
}
But it accepts also arbitrary strings, like in the example "button3".
回答1:
But it accepts also arbitrary strings, like in the example "button3".
Reason:
You have
interface IComponents {
root: IComponent,
[key: string]: IComponent,
}
so keyof IComponents
resolves to 'root' | string
or effectively string
. You almost always never want to have well defined names and string
indexers in the same group.
Solution
I would reconsider a non-cyclic design. The following:
const list: IComponents = {
root: {
type: 'panel',
children: ['button1', 'button2', 'button3']
},
button1: {
type: 'button'
},
button2: {
type: 'button'
},
}
The type of list
depends on the assigned object. Ideally you would figure out some way that type enforces what can be assigned.
回答2:
There's no single IComponents
type you can define that includes all (and only) component lists that are internally consistent in the sense that the children
lists only refer to defined components; this would require a form of existential types. However, you can define a generic type IComponents<K>
that represents a valid component list with a specific key list K
, and this will allow you to define functions that are generic in a type parameter K
and accept an IComponents<K>
and thus can be called on any valid component list. For example:
type IComponents<K extends string> = {
[P in K]: IComponent<K>;
} & {
// Needed for contextual typing to work.
// https://github.com/Microsoft/TypeScript/pull/27586 might remove the need for this.
[n: string]: IComponent<K>
};
interface IComponent<K extends string> {
type: string,
children?: Array<K>;
}
function processComponents<K extends string>(arg: IComponents<K>) {
// ...
}
// OK
processComponents({
root: {
type: 'panel',
children: ['button1', 'button2']
},
button1: {
type: 'button'
},
button2: {
type: 'button'
},
});
// Error (unfortunately it doesn't pinpoint the mistake)
processComponents({
root: {
type: 'panel',
children: ['button1', 'button2', 'button3']
},
button1: {
type: 'button'
},
button2: {
type: 'button'
},
});
来源:https://stackoverflow.com/questions/52843813/enforcing-that-an-array-within-an-object-literal-can-only-contain-keys-of-the-ou