как сузить тип для SVG Element union - PullRequest
5 голосов
/ 26 мая 2019

Я использую реагирование для установки ссылки на элемент svg, который может быть <rect>, <polygon> или <ellipse>.

У меня есть это объявление:

const shapeRef = useRef<SVGPolygonElement | SVGEllipseElement | SVGRectElement>(null);

Но когда я пытаюсь установить это на элемент <ellipse>, например:

<ellipse
  cx={width / 8}
  cy={-sideDimension(y) / 8}
  rx={width}
  ry={height}
  ref={shapeRef}
/>

Я получаю эту ошибку:

Тип 'RefObject' нельзя назначить типу 'string | ((пример: SVGEllipseElement | null) => void) | RefObject | ноль | не определено. Тип «RefObject» не может быть назначен типу 'RefObject. Тип 'SVGPolygonElement | SVGEllipseElement | SVGRectElement 'нельзя назначить типу' SVGEllipseElement '. Типу 'SVGPolygonElement' не хватает следующих свойств из типа 'SVGEllipseElement': cx, cy, rx, ryts (2322)

Насколько я понимаю, мне нужно как-то сузить тип, чтобы это сработало, иначе каждый объект, использующий этот ref, должен иметь все свойства объединения.

1 Ответ

8 голосов
/ 30 мая 2019

Вы правы.Typescript дает вам эту ошибку, потому что он не знает, какой из типов должен учитывать shapreRef как.

Лучшее решение IMO использует Type Guards .A Type Guard - это машинописный способ проверки, относится ли переменная к определенному типу.Для объединяемых типов это дает типизированному представлению понимание того, что что-то имеет определенный тип.

Например, в вашем случае это может быть что-то вроде этого:

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;
}

Обратите внимание, что возвращаемый результаттип shape is IEllipse.Это означает, что машинопись будет интерпретировать истинное возвращаемое значение здесь, как если бы shape было IEllipse.

Затем, где бы вы ни хотели использовать SvgShape, вы можете проверить, какиетип SvgShape это так, и машинопись должна знать тип на основе этого:

// ...
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="..." />;
  }
}
// ...

Почему бы не просто тип пересечения?

Ну ... Типы пересечения больше подходят для случаев, когдакаждый из типов (Rect, Polygon и т. д.) имеет одинаковые атрибуты в новом элементе.Например:

type Inter = IRect & IPolygon & IEllipse;

Означает, что тип Inter имеет тип IRect и IPolygon и IEllipse.Это означает, что объект этого типа будет иметь все члены всех трех типов.Таким образом, попытка получить доступ к атрибуту points (который существует на IPolygon) в форме, которая на самом деле является IRect, будет действовать так, как если бы этот атрибут существовал там (чего мы не хотим)

В большинстве случаев вы увидите типы пересечений, используемые для смешивания, и другие понятия, которые не вписываются в классическую объектно-ориентированную форму.

как использовать с 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...