Получить экземпляр класса в различенном объединении с его конкретным типом, а не типом объединения - PullRequest
0 голосов
/ 10 июня 2019

Есть ли хороший способ иметь функцию, которая создает новый экземпляр класса на основе значения общего свойства, которое вы передаете ему, и заставить его возвращать определенный тип созданного экземпляра, а не тип объединения ? Взять и немного подправить пример машинописной документации с помощью фигур:

class Square {
    kind: "square";
    size: number;
}
class Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
class Circle {
    kind: "circle";
    radius: number;
}

type Kinds = "circle" | "rectangle" | "square";
type Shape = Square | Rectangle | Circle;

function createShape(kind: Kinds) {
    switch (kind) {
        case "circle":
            return new Circle();
        case "rectangle":
            return new Rectangle();
        case "square":
            return new Square();
    }
}

createShape("circle").radius; //Property 'radius' does not exist on type 'Square | Rectangle | Circle'

Я мог бы, например, добавить отображение между Kinds и Shape и добавить некоторые аннотации типов в функцию:

type Kinds = "circle" | "rectangle" | "square";
type Shape = Square | Rectangle | Circle;
type ShapeKind = { "circle": Circle, "square": Square, "rectangle": Rectangle };

function createShape<T extends Kinds>(kind: T): ShapeKind[T] {
    switch (kind) {
        case "circle":
            return new Circle();
        case "rectangle":
            return new Rectangle();
        case "square":
            return new Square();
    }
}

createShape("circle").radius; //All good now

Но создание этого отображения кажется отвратительным. Я мог бы также использовать защиту типов, но это кажется излишним, учитывая, что я точно знаю тип Shape в момент, когда я создаю и возвращаю его. Есть ли лучший способ справиться с этим?

1 Ответ

4 голосов
/ 10 июня 2019

Вам не нужно создавать отображение;Вы можете извлечь это из вашего Shape типа:

class Square {
  readonly kind = "square";
  size!: number;
}
class Rectangle {
  readonly kind = "rectangle";
  width!: number;
  height!: number;
}
class Circle {
  readonly kind = "circle";
  radius!: number;
}

type Shape = Square | Rectangle | Circle;
type Kinds = Shape["kind"]; // automatically

// return type is the consituent of Shape that matches {kind: K}
function createShape<K extends Kinds>(kind: K): Extract<Shape, { kind: K }>;
function createShape(kind: Kinds): Shape {
  switch (kind) {
    case "circle":
      return new Circle();
    case "rectangle":
      return new Rectangle();
    case "square":
      return new Square();
  }
}

createShape("circle").radius; // okay

Ссылка на код

Тип возврата использует Extract<T, U>, встроенный условный тип для фильтрации объединения T, чтобы разрешить присваивание только компонентам другого типа U.

Обратите внимание, что я использовал перегрузку с одной сигнатурой в createShape(), поскольку компилятор действительно не может проверить, что оператор switch всегда возвращает что-то, что соответствует неразрешенному условному типу Extract<Shape, { kind: K}>.

Надеюсь, что это поможет;удачи!


ОБНОВЛЕНИЕ: Вы не просили об этом, но если бы я писал такую ​​функцию, как createShape(), я мог бы хранить конструкторы, содержащие объект, и использовать индексированный доступ, чтобы проверить компиляторвведите безопасность для меня внутри createShape():

const verifyShapeConstructors = <
  T extends { [K in keyof T]: new () => { kind: K } }
>(
  x: T
) => x;

const badShapeConstuctors = verifyShapeConstructors({
    square: Square,
    circle: Rectangle, // error!
    rectangle: Circle, // error!
})

const shapeConstructors = verifyShapeConstructors({
  square: Square,
  rectangle: Rectangle,
  circle: Circle
});
type ShapeConstructors = typeof shapeConstructors;

type Instance<T extends Function> = T["prototype"];

type Shape = Instance<ShapeConstructors[keyof ShapeConstructors]>;
type Kinds = keyof ShapeConstructors;

function createShape<K extends Kinds>(kind: K): Instance<ShapeConstructors[K]> {
  return new shapeConstructors[kind]();
}

Ссылка на код

В этом я использую вспомогательную функцию verifyShapeConstructors(), чтобы убедиться, что я надеваюне испортите конструктор-ключи.И я пользуюсь тем, что компилятор знает, что конструкторы классов, такие как Square, имеют свойство "prototype" типа экземпляра ... поэтому он может использовать индексный доступ к этому свойству для проверки типа экземпляра конструктора.(Встроенный условный тип InstanceType<C> действует аналогично, но компилятор не может рассуждать об условных типах так же, как о доступе к индексу).

Все это сводится к тому, что createShape() теперь является однострочной универсальной функцией, которую компилятор проверяет как корректную.

Как я уже сказал, вы не просили об этом, но это может представлять некоторый интерес.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...