Как проверить тип объекта во время выполнения в TypeScript?

Я пытаюсь найти способ передать объект для работы и проверить его тип во время выполнения. Это псевдокод:

func(obj:any){
  if(typeof obj === "A"){
    // do something
  }
  else if(typeof obj === "B"{
    //do something else
  }

}
 a:A;
 b:B;
 func(a);

Но typeof всегда возвращает "объект", и я не мог найти способ получить реальный тип "a" или "b". Экземпляр экземпляра тоже не работал и возвращал то же самое. Любая идея, как это сделать в TypeScript?

Благодарим за помощь!!!

Ответ 1

  Изменить: я хочу указать людям, приходящим сюда с поисков, что этот вопрос конкретно касается неклассных типов, то есть объекта формы, определенные псевдонимом interface или type. Для типов классов вы можно использовать JavaScript instanceof для определения класса, из которого поступил экземпляр, и TypeScript автоматически сузит тип в средстве проверки типов.

Типы удаляются во время компиляции и не существуют во время выполнения, поэтому вы не можете проверить тип во время выполнения.

Что вы можете сделать, это проверить, что форма объекта соответствует ожидаемой, а TypeScript может утверждать тип во время компиляции, используя определяемый пользователем тип guard, который возвращает true (аннотированный возвращаемый тип - это "тип"). предикат "формы arg is T), если форма соответствует вашим ожиданиям:

interface A {
  foo: string;
}

interface B {
  bar: number;
}

function isA(obj: any): obj is A {
  return obj.foo !== undefined 
}

function isB(obj: any): obj is B {
  return obj.bar !== undefined 
}

function func(obj: any) {
  if (isA(obj)) {
    // In this block 'obj' is narrowed to type 'A'
    obj.foo;
  }
  else if (isB(obj)) {
    // In this block 'obj' is narrowed to type 'B'
    obj.bar;
  }
}

Пример на детской площадке

Насколько глубоко вы понимаете реализацию type-guard, зависит только от вас, нужно только вернуть true или false. Например, как указывает Карл в своем ответе, в приведенном выше примере проверяется только то, что определены ожидаемые свойства (следуя примеру в документации), а не то, что им присвоен ожидаемый тип. Это может быть сложно с обнуляемыми типами и вложенными объектами, это зависит от вас, чтобы определить, насколько подробно сделать проверку формы.

Ответ 2

В продолжение ответа Аарона я создал преобразователь, который генерирует функции защиты типов во время компиляции. Таким образом, вам не нужно писать их вручную.

Например:

import { is } from 'typescript-is';

interface A {
  foo: string;
}

interface B {
  bar: number;
}

if (is<A>(obj)) {
  // obj is narrowed to type A
}

if (is<B>(obj)) {
  // obj is narrowed to type B
}

Вы можете найти проект здесь с инструкциями по его использованию:

https://github.com/woutervh-/typescript-is

Ответ 3

Я поигрался с ответом Аарона и думаю, что было бы лучше проверить на typeof, а не просто undefined, например так:

interface A {
  foo: string;
}

interface B {
  bar: number;
}

function isA(obj: any): obj is A {
  return typeof obj.foo === 'string' 
}

function isB(obj: any): obj is B {
  return typeof obj.bar === 'number' 
}

function func(obj: any) {
  if (isA(obj)) {
    console.log("A.foo:", obj.foo);
  }
  else if (isB(obj)) {
    console.log("B.bar:", obj.bar);
  }
  else {console.log("neither A nor B")}
}

const a: A = { foo: 567 }; // notice i am giving it a number, not a string 
const b: B = { bar: 123 };

func(a);  // neither A nor B
func(b);  // B.bar: 123

Ответ 4

Вопрос OP был "Я пытаюсь найти способ передать объект в функцию и проверить его тип во время выполнения".

Поскольку экземпляр класса - это просто объект, правильный ответ - использовать экземпляр класса и instanceof, когда требуется проверка типов во время выполнения, используйте интерфейс, если нет.

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

Работает, потому что routerEvent является экземпляром класса NavigationStart

if (routerEvent instanceof NavigationStart) {
  this.loading = true;
}

if (routerEvent instanceof NavigationEnd ||
  routerEvent instanceof NavigationCancel ||
  routerEvent instanceof NavigationError) {
  this.loading = false;
}

Не будет работать

// Must use a class not an interface
export interface IRouterEvent { ... }
// Fails
expect(IRouterEvent instanceof NavigationCancel).toBe(true); 

Не будет работать

// Must use a class not a type
export type RouterEvent { ... }
// Fails
expect(IRouterEvent instanceof NavigationCancel).toBe(true); 

Как видно из приведенного выше кода, классы используются для сравнения экземпляра с типами NavigationStart | Cancel | Error.

Использование instanceof для Type или Interface невозможно, так как компилятор ts удаляет эти атрибуты во время процесса компиляции и до интерпретации JIT или AOT. Классы - отличный способ создать тип, который можно использовать как перед компиляцией, так и во время выполнения JS.