Как проверить тип объекта во время выполнения в 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.