I want to define subclass type of object values as following SHAPE_PARAMS
.
I could set type with type assertion as <CircleParameter>
, but missing properties could not be linted.
How can I define subclass type strictly?
const Shapes = {
CIRCLE_A: "CIRCLE_A",
CIRCLE_B: "CIRCLE_B",
RECTANGLE_A: "RECTANGLE_A",
} as const;
type Shapes = typeof Shapes[keyof typeof Shapes];
interface ShapeParameter {
color: string;
}
interface CircleParameter extends ShapeParameter {
radius: number;
}
interface RectangleParameter extends ShapeParameter {
width: number;
height: number;
}
const SHAPE_PARAMS: { [key in Shapes]: ShapeParameter } = {
[Shapes.CIRCLE_A]: <CircleParameter>{
color: "red",
radius: 1,
},
[Shapes.CIRCLE_B]: <CircleParameter>{
// color: "blue", <- Missed property cannot be linted.
radius: 2,
},
[Shapes.RECTANGLE_A]: <RectangleParameter>{
color: "green",
width: 3,
// height: 3, <- Missed property cannot be linted.
},
};
CodePudding user response:
The problem here is that the <TypeName>
syntax asserts that the object has that type, whereas you want the compiler to check that the object has that type.
The best solution is probably to change the type { [key in Shapes]: ShapeParameter }
so that it is more specific:
type ShapeParameterMap = {
[Shapes.CIRCLE_A]: CircleParameter,
[Shapes.CIRCLE_B]: CircleParameter,
[Shapes.RECTANGLE_A]: RectangleParameter,
}
const SHAPE_PARAMS: ShapeParameterMap = {
// ...
};
The upside of this is that you can get a more specific type for SHAPE_PARAMS[k]
when you access it with some k
of a more specific type.
On the other hand, perhaps this seems like too much code repetition; in that case, you can achieve what you want by writing a trivial utility function which does nothing, but checks the type of its argument:
function check<T>(arg: T): T {
return arg;
}
const SHAPE_PARAMS: Record<Shapes, ShapeParameter> = {
[Shapes.CIRCLE_A]: check<CircleParameter>({
color: "red",
radius: 1,
}),
[Shapes.CIRCLE_B]: check<CircleParameter>({
radius: 2,
// missing property 'color'
}),
[Shapes.RECTANGLE_A]: check<RectangleParameter>({
color: "green",
width: 3,
// missing property 'height'
}),
};
CodePudding user response:
You could infer expected shape parameter type from key prefix:
type ShapeParams = {
[K in Shapes]: K extends `CIRCLE_${string}` ? CircleParameter : RectangleParameter
}
/** type ShapeParams is
{
CIRCLE_A: CircleParameter;
CIRCLE_B: CircleParameter;
RECTANGLE_A: RectangleParameter;
}
*/
const SHAPE_PARAMS: ShapeParams = {
[Shapes.CIRCLE_A]: {
color: "red",
radius: 1,
},
[Shapes.CIRCLE_B]: { // <- Property 'color' is missing
// color: "blue",
radius: 2,
},
[Shapes.RECTANGLE_A]: { // <- Property 'height' is missing
color: "green",
width: 3,
// height: 3,
},
};