You are correct. Typescript gives you that error because it doesn't know which one of the types it should account the shapreRef
as.
The best solution IMO is using a Type Guards. A Type Guard is the typescript way to check if a variable is of a certain type. For union types, that gives typescript the understanding that something is of a specific type.
For example, in your case, it can be something like this:
interface IEllipse {
attr1: string;
attr2: string;
}
interface IRect {
attr3: string;
attr4: string;
}
type SvgShape = IEllipse | IRect | IPolygon;
function isEllipse(shape: SvgShape): shape is IEllipse {
return (shape as IEllipse).attr1 !== undefined;
}
Notice that the return type is shape is IEllipse
. This means that typescript will interpret a truthy return value here as if shape
is an IEllipse
.
Then, wherever you want to use a SvgShape
, you can check which type of SvgShape
it is and typescript should know the type based on that:
// ...
render() {
const shape: SvgShape = this.getCurrentShape();
if (isEllipse(shape)) {
// typescript should KNOW that this is an ellipse inside this if
// it will accept all of Ellipse's attribute and reject other attributes
// that appear in other shapes
return <ellipse .../>;
} else if (isRect(shape)) {
// typescript should interpet this shape as a Rect inside the `if`
return <rect ... />;
} else {
// typescript will know only one subtype left (IPolygon)
return <polygon points="..." />;
}
}
// ...
Why not just an Intersection type?
Well... Intersection types are more for cases where every one of the types (Rect, Polygon, etc) have the exact same attributes in the new item.
For example:
type Inter = IRect & IPolygon & IEllipse;
Means that an Inter
type is IRect
and IPolygon
and IEllipse
. That means an object of this type will have all members of all three types.
So, trying to access the attribute points
(which exists on IPolygon
) on a shape that is actually an IRect
, will act as if that attribute exists there (which we don't want)
You will mostly see intersection types used for mixins and other concepts that don’t fit in the classic object-oriented mold.
how to use with useRef?
type SvgShape = SVGPolygonElement | SVGEllipseElement | SVGRectElement;
const shapeRef = useRef<SvgShape>(null);
function isEllipseRef(shapeRef: MutableRefObject<SvgShape>): shapeRef is MutableRefObject<IEllipse> {
const shape: SvgShape = shapeRef.current;
return (shape as IEllipse).attr1 !== undefined;
}