predicate typescript

Нашел вот такую доку по predicates и хочу сделать +/- по аналогии https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates

Пытаюсь написать свой код:

type CtorType<T extends Animal> = {
  new(...args: any[]): T
}

class Animal {
  static isAnimal<T extends Animal>(animal: T): animal is T { // animal is T - не работает
    const Ctor = (this as any) as CtorType<T>  // тут не понятно, без явного as any, даже не компилится ????
    return animal instanceof Ctor
  }

  name: string

  constructor(name: string) {
    this.name = name
  }
}

class Rabbit extends Animal {
  constructor() {
    super('Rabbit')
  }
}

const rabbit: Animal = new Rabbit()

const isRabbit = (animal: Animal): animal is Rabbit => { // animal is Rabbit - не работает
  return animal instanceof Rabbit
}

if (Rabbit.isAnimal(rabbit)) {
  type MyAnimal1 = typeof rabbit // type MyAnimal = Animal
}

if (isRabbit(rabbit)) {
  type MyAnimal2 = typeof rabbit // type MyAnimal = Animal
}

if (rabbit instanceof Rabbit) {
  type MyAnimal3 = typeof rabbit // type MyAnimal3 = Rabbit
}

Хочу добавить в базовый класс статичный метод isAnimal, который проверял - порожден ли инстанс от соответствующего класса, чтобы потом внутри блока if, если это так, применял соответствующий тип. Но у меня что-то не выходит. Rabbit.isAnimal(rabbit)

Далее написал еще более простую функцию isRabbit, которая уже явно проверяет - происходит ли инстанс от класса Rabbit. Тут тоже внутри блока if все равно тип Animal, причем если в условии явно написать rabbit instanceof Rabbit, то все ок


Ответы (2 шт):

Автор решения: VerZsuT

Typescript иногда (но редко) делает довольно неявные вещи.

В данном случае Rabbit.isAnimal не работает потому что дженерик в isAnimal привязан конкретно к Animal и не зависит от того кто его наследует.
Использование же T extends typeof this не работает в статических методах (увы).

Конструкция animal is Rabbit не будет работать до тех пор пока класс Rabbit не имеет како-либо отличительное свойство/метод от Animal, даже приватный. Пример:

class Rabbit extends Animal {
    private legLength = 5 // То самое отличие от Animal
    constructor() {
        super('Rabbit')
    }
}

if (isRabbit(rabbit)) {
    type AnimalType = typeof rabbit // AnimalType is Rabbit
}
→ Ссылка
Автор решения: Qwertiy
static isAnimal<T extends Animal>(animal: T): animal is T { // animal is T - не работает

Ну наоборот же:

static isAnimal<T>(animal: T): animal is Animal

if (isRabbit(rabbit)) {
  type MyAnimal2 = typeof rabbit // type MyAnimal = Animal
}

Тайпскрипт использует структурную типизацию, а у тебя классы Rabbit и Animal ничем не отличаются.

Добавь кролику любое отличие от животного и всё заработает:

скриншот

→ Ссылка