如何在 TypeScript 中检查运行时的对象类型?
How to check the object type on runtime in TypeScript?
我正在尝试找到一种方法来传递一个对象以在运行时运行并检查它的类型。这是一个伪代码:
function func (obj:any) {
if(typeof obj === "A") {
// do something
} else if(typeof obj === "B") {
//do something else
}
}
let a:A;
let b:B;
func(a);
但是 typeof
总是 returns "object"
而且我找不到获取 a
或 b
的真实类型的方法。 instanceof
也没有工作,返回相同。
知道如何在 TypeScript 中做到这一点吗?
Edit: I want to point out to people coming here from searches that this question is specifically dealing with non-class types, ie object
shapes as defined by interface
or type
alias. For class types you
can use JavaScript's instanceof
to determine the class an instance comes from, and TypeScript will narrow the type in the type-checker automatically.
类型在编译时被剥离,在运行时不存在,因此您无法在运行时检查类型。
您可以做的是检查对象的形状是否符合您的预期,TypeScript 可以在编译时使用 user-defined type guard 断言类型 return 为真(注释 return 类型是 "type predicate" 形式 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;
}
}
类型保护实现的深度完全取决于您,它只需要 return true 或 false。例如,正如 Carl 在他的回答中指出的那样,上面的示例仅检查是否定义了预期的属性(按照文档中的示例),而不是检查是否为它们分配了预期的类型。对于可空类型和嵌套对象,这可能会变得棘手,由您决定进行形状检查的详细程度。
我一直在研究 Aaron 的回答,认为测试 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
扩展 Aaron 的回答,我制作了一个在编译时生成类型保护函数的转换器。这样你就不用手动写了。
例如:
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
}
您可以在此处找到该项目及其使用说明:
“我正在尝试找到一种方法来传递对象以在运行时运行并检查它的类型”。
由于 class 实例只是一个 object
,“本机”答案是在需要运行时类型检查时使用 class 实例和 instanceof
,不使用时使用接口以保持合同并分离您的应用程序,减少 methods/ctors 上的签名大小,同时不添加任何额外的大小。在我看来,这是我在决定使用 class 与 type/interface 时在 TypeScript 中的几个主要考虑因素之一。另一个主要驱动因素是该对象是否需要实例化,或者它是否定义了一个 POJO。
在我的代码库中,我通常会有一个 class 实现一个接口,该接口在编译期间用于预编译时类型安全,而 classes 用于组织我的代码并允许在函数、classes 和方法之间轻松传递数据,以及在打字稿中进行运行时类型检查。
有效,因为 routerEvent 是 NavigationStart 的一个实例 class
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);
正如您在上面的代码中看到的,classes 用于将实例与 Angular 库中的类型 NavigationStart|Cancel|Error
进行比较,如果您之前使用过路由器你是一个项目,我愿意成为你在自己的代码库中进行了类似但不完全相同的检查,以便在运行时确定应用程序状态。
在类型或接口上使用 instanceof
是不可能的,因为 ts 编译器在其编译过程中以及在被 JIT 或 AOT 解释之前会去除这些属性。 类 是创建可在预编译以及 JS 运行时使用的类型的好方法。
2022 年更新
除了我对此的原始回复之外,您还可以利用 The TypeScript Reflect Metadata API 或使用 TypeScript 编译器推出您自己的解决方案来对您的代码进行静态分析并解析 AST,查询如下:
switch (node.kind) {
case ts.SyntaxKind.InterfaceDeclaration:
// ...
break;
case ts.SyntaxKind.TypeDeclaration:
// ...
break;
}
See this solution for additonal details
否,你不能在运行时引用一个type
,但是是你可以转换一个object
到 type
和 typeof
,并在运行时针对此 object
执行 validation/sanitisation/checks。
const plainObject = {
someKey: "string",
someKey2: 1,
};
type TypeWithAllOptionalFields = Partial<typeof plainObject>; //do further utility typings as you please, Partial being one of examples.
function customChecks(userInput: any) {
// do whatever you want with the 'plainObject'
}
以上等于
type TypeWithAllOptionalFields = {
someKey?: string;
someKey2?: number;
};
const plainObject = {
someKey: "string",
someKey2: 1,
};
function customChecks(userInput: any) {
// ...
}
但您的代码中没有重复的键名
无需检查类型的替代方法
如果要引入更多类型怎么办?然后你会扩展你的 if 语句吗?您的代码库中有多少这样的 if 语句?
在条件中使用类型会使您的代码难以维护。这背后有很多理论,但我会为你省去麻烦。您可以改为执行以下操作:
使用多态
像这样:
abstract class BaseClass {
abstract theLogic();
}
class A extends BaseClass {
theLogic() {
// do something if class is A
}
}
class B extends BaseClass {
theLogic() {
// do something if class is B
}
}
然后你只需要从任何你想要的 class 调用 theLogic():
let a: A = new A();
a.theLogic();
let b: B = new B();
b.theLogic();
您可以调用构造函数并获取其名称
let className = this.constructor.name
I know this is an old question and the "true" question here is not the
same as the question in the title, but Google throws this question for
"typescript runtime types" and some people may know what they are
looking for and it can be runtime types.
Right answer here is what Aaron Beall answered, the type guards.
But answer, matching question in the title and matching Google
searches, is only usage of TypeScript transformers/plugins. TypeScript
itself strip out information about types when transpiling TS to JS.
And well,.. it is one of the possible ways how to implement the type guards,
eg. the typescript-is
transformer as user7132587 pointed out.
另一个选项是 transformer tst-reflect
. 它在运行时提供有关类型的所有信息。它允许您根据类型信息编写自己的类型保护,例如。检查该对象是否具有您期望的所有属性。或者你可以直接使用转换器中的 Type.is(Type)
方法,它直接基于 TypeScript 的类型检查信息。
我已经创建了 this REPL。玩得开心!
更多信息见 Github repository.
import { getType, Type } from "tst-reflect";
class A {
alphaProperty: string;
}
interface B {
betaProperty: string;
}
class Bb extends A implements B {
betaProperty = "tst-reflect!!";
bBetaProperty: "yes" | "no" = "yes";
}
/** @reflectGeneric */
function func<TType>(obj?: TType)
{
const type: Type = getType<TType>();
console.log(
type.name,
"\n\textends", type.baseType.name,
"\n\timplements", type.getInterface()?.name ?? "nothing",
"\n\tproperties:", type.getProperties().map(p => p.name + ": " + p.type.name),
"\n"
);
console.log("\tis A:", type.is(getType<A>()) ? "yes" : "no");
console.log("\tis assignable to A:", type.isAssignableTo(getType<A>()) ? "yes" : "no");
console.log("\tis assignable to B:", type.isAssignableTo(getType<B>()) ? "yes" : "no");
}
let a: A = new A();
let b: B = new Bb();
let c = new Bb();
func(a);
func<typeof b>();
func<Bb>();
输出:
A
extends Object
implements nothing
properties: [ 'alphaProperty: string' ]
is A: yes
is assignable to A: yes
is assignable to B: no
B
extends Object
implements nothing
properties: [ 'betaProperty: string' ]
is A: no
is assignable to A: no
is assignable to B: yes
Bb
extends A
implements B
properties: [ 'betaProperty: string', 'bBetaProperty: ' ]
is A: no
is assignable to A: yes
is assignable to B: yes
我正在尝试找到一种方法来传递一个对象以在运行时运行并检查它的类型。这是一个伪代码:
function func (obj:any) {
if(typeof obj === "A") {
// do something
} else if(typeof obj === "B") {
//do something else
}
}
let a:A;
let b:B;
func(a);
但是 typeof
总是 returns "object"
而且我找不到获取 a
或 b
的真实类型的方法。 instanceof
也没有工作,返回相同。
知道如何在 TypeScript 中做到这一点吗?
Edit: I want to point out to people coming here from searches that this question is specifically dealing with non-class types, ie object shapes as defined by
interface
ortype
alias. For class types you can use JavaScript'sinstanceof
to determine the class an instance comes from, and TypeScript will narrow the type in the type-checker automatically.
类型在编译时被剥离,在运行时不存在,因此您无法在运行时检查类型。
您可以做的是检查对象的形状是否符合您的预期,TypeScript 可以在编译时使用 user-defined type guard 断言类型 return 为真(注释 return 类型是 "type predicate" 形式 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;
}
}
类型保护实现的深度完全取决于您,它只需要 return true 或 false。例如,正如 Carl 在他的回答中指出的那样,上面的示例仅检查是否定义了预期的属性(按照文档中的示例),而不是检查是否为它们分配了预期的类型。对于可空类型和嵌套对象,这可能会变得棘手,由您决定进行形状检查的详细程度。
我一直在研究 Aaron 的回答,认为测试 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
扩展 Aaron 的回答,我制作了一个在编译时生成类型保护函数的转换器。这样你就不用手动写了。
例如:
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
}
您可以在此处找到该项目及其使用说明:
“我正在尝试找到一种方法来传递对象以在运行时运行并检查它的类型”。
由于 class 实例只是一个 object
,“本机”答案是在需要运行时类型检查时使用 class 实例和 instanceof
,不使用时使用接口以保持合同并分离您的应用程序,减少 methods/ctors 上的签名大小,同时不添加任何额外的大小。在我看来,这是我在决定使用 class 与 type/interface 时在 TypeScript 中的几个主要考虑因素之一。另一个主要驱动因素是该对象是否需要实例化,或者它是否定义了一个 POJO。
在我的代码库中,我通常会有一个 class 实现一个接口,该接口在编译期间用于预编译时类型安全,而 classes 用于组织我的代码并允许在函数、classes 和方法之间轻松传递数据,以及在打字稿中进行运行时类型检查。
有效,因为 routerEvent 是 NavigationStart 的一个实例 class
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);
正如您在上面的代码中看到的,classes 用于将实例与 Angular 库中的类型 NavigationStart|Cancel|Error
进行比较,如果您之前使用过路由器你是一个项目,我愿意成为你在自己的代码库中进行了类似但不完全相同的检查,以便在运行时确定应用程序状态。
在类型或接口上使用 instanceof
是不可能的,因为 ts 编译器在其编译过程中以及在被 JIT 或 AOT 解释之前会去除这些属性。 类 是创建可在预编译以及 JS 运行时使用的类型的好方法。
2022 年更新
除了我对此的原始回复之外,您还可以利用 The TypeScript Reflect Metadata API 或使用 TypeScript 编译器推出您自己的解决方案来对您的代码进行静态分析并解析 AST,查询如下:
switch (node.kind) {
case ts.SyntaxKind.InterfaceDeclaration:
// ...
break;
case ts.SyntaxKind.TypeDeclaration:
// ...
break;
}
See this solution for additonal details
否,你不能在运行时引用一个type
,但是是你可以转换一个object
到 type
和 typeof
,并在运行时针对此 object
执行 validation/sanitisation/checks。
const plainObject = {
someKey: "string",
someKey2: 1,
};
type TypeWithAllOptionalFields = Partial<typeof plainObject>; //do further utility typings as you please, Partial being one of examples.
function customChecks(userInput: any) {
// do whatever you want with the 'plainObject'
}
以上等于
type TypeWithAllOptionalFields = {
someKey?: string;
someKey2?: number;
};
const plainObject = {
someKey: "string",
someKey2: 1,
};
function customChecks(userInput: any) {
// ...
}
但您的代码中没有重复的键名
无需检查类型的替代方法
如果要引入更多类型怎么办?然后你会扩展你的 if 语句吗?您的代码库中有多少这样的 if 语句?
在条件中使用类型会使您的代码难以维护。这背后有很多理论,但我会为你省去麻烦。您可以改为执行以下操作:
使用多态
像这样:
abstract class BaseClass {
abstract theLogic();
}
class A extends BaseClass {
theLogic() {
// do something if class is A
}
}
class B extends BaseClass {
theLogic() {
// do something if class is B
}
}
然后你只需要从任何你想要的 class 调用 theLogic():
let a: A = new A();
a.theLogic();
let b: B = new B();
b.theLogic();
您可以调用构造函数并获取其名称
let className = this.constructor.name
I know this is an old question and the "true" question here is not the same as the question in the title, but Google throws this question for "typescript runtime types" and some people may know what they are looking for and it can be runtime types.
Right answer here is what Aaron Beall answered, the type guards.
But answer, matching question in the title and matching Google searches, is only usage of TypeScript transformers/plugins. TypeScript itself strip out information about types when transpiling TS to JS. And well,.. it is one of the possible ways how to implement the type guards, eg. the
typescript-is
transformer as user7132587 pointed out.
另一个选项是 transformer tst-reflect
. 它在运行时提供有关类型的所有信息。它允许您根据类型信息编写自己的类型保护,例如。检查该对象是否具有您期望的所有属性。或者你可以直接使用转换器中的 Type.is(Type)
方法,它直接基于 TypeScript 的类型检查信息。
我已经创建了 this REPL。玩得开心! 更多信息见 Github repository.
import { getType, Type } from "tst-reflect";
class A {
alphaProperty: string;
}
interface B {
betaProperty: string;
}
class Bb extends A implements B {
betaProperty = "tst-reflect!!";
bBetaProperty: "yes" | "no" = "yes";
}
/** @reflectGeneric */
function func<TType>(obj?: TType)
{
const type: Type = getType<TType>();
console.log(
type.name,
"\n\textends", type.baseType.name,
"\n\timplements", type.getInterface()?.name ?? "nothing",
"\n\tproperties:", type.getProperties().map(p => p.name + ": " + p.type.name),
"\n"
);
console.log("\tis A:", type.is(getType<A>()) ? "yes" : "no");
console.log("\tis assignable to A:", type.isAssignableTo(getType<A>()) ? "yes" : "no");
console.log("\tis assignable to B:", type.isAssignableTo(getType<B>()) ? "yes" : "no");
}
let a: A = new A();
let b: B = new Bb();
let c = new Bb();
func(a);
func<typeof b>();
func<Bb>();
输出:
A
extends Object
implements nothing
properties: [ 'alphaProperty: string' ]
is A: yes
is assignable to A: yes
is assignable to B: no
B
extends Object
implements nothing
properties: [ 'betaProperty: string' ]
is A: no
is assignable to A: no
is assignable to B: yes
Bb
extends A
implements B
properties: [ 'betaProperty: string', 'bBetaProperty: ' ]
is A: no
is assignable to A: yes
is assignable to B: yes