使用Typescript检查接口类型

lhk*_*lhk 244 javascript compiler-construction interface web typescript

这个问题是使用TypeScript直接类比检查Class类型

我需要在运行时找出类型为any的变量实现接口.这是我的代码:

interface A{
    member:string;
}

var a:any={member:"foobar"};

if(a instanceof A) alert(a.member);
Run Code Online (Sandbox Code Playgroud)

如果您在typescript playground中输入此代码,则最后一行将被标记为错误,"当前范围中不存在名称A".但事实并非如此,该名称确实存在于当前范围内.我甚至可以在var a:A={member:"foobar"};没有编辑投诉的情况下将变量声明更改为.浏览网页并在SO上找到另一个问题后,我将界面更改为类,但后来我无法使用对象文字来创建实例.

我想知道A类型是如何消失的,但是看看生成的javascript解释了这个问题:

var a = {
    member: "foobar"
};
if(a instanceof A) {
    alert(a.member);
}
Run Code Online (Sandbox Code Playgroud)

A没有表示作为接口,因此不可能进行运行时类型检查.

我知道javascript作为一种动态语言没有接口的概念.有没有办法输入接口检查?

打字稿操场的自动完成显示打字稿甚至提供了一种方法implements.我怎么用呢?

Fen*_*ton 175

您可以在没有instanceof关键字的情况下实现所需,因为您现在可以编写自定义类型保护:

interface A{
    member:string;
}

function instanceOfA(object: any): object is A {
    return 'member' in object;
}

var a:any={member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}
Run Code Online (Sandbox Code Playgroud)

很多会员

如果您需要检查很多成员以确定对象是否与您的类型匹配,您可以改为添加一个鉴别器.以下是最基本的示例,并要求您管理自己的鉴别器......您需要深入了解模式以确保避免重复的鉴别器.

interface A{
    discriminator: 'I-AM-A';
    member:string;
}

function instanceOfA(object: any): object is A {
    return object.discriminator === 'I-AM-A';
}

var a:any = {discriminator: 'I-AM-A', member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}
Run Code Online (Sandbox Code Playgroud)

  • 不敢相信我们已经进入了 2020 年,而且没有更好的方法来做到这一点...=/ (72认同)
  • "没有办法运行时检查接口." 有,他们只是因为某种原因还没有实现它. (61认同)
  • 如果界面有100个成员,你需要检查所有100个?Foobar的. (8认同)
  • 这个鉴别器范例(如此处所写)不支持扩展接口.如果检查它是否是一个基本接口的实例,派生接口将返回false. (5认同)
  • 看起来已经是 2023 年了,没有更好的方法来做到这一点...... (4认同)
  • 您可以向对象添加一个鉴别器,而不是全部选中100个... (3认同)
  • 这看起来是个坏习惯。还是没有更好的解决办法吗? (3认同)
  • 只不过是黑客攻击而已。 (3认同)
  • @Fenton 也许我对此还不够了解,但假设您有一个扩展接口 A 的接口 B,您希望 `isInstanceOfA(instantiatedB)` 返回 true,但您希望 `isInstanceOfB(instantiatedA)` 返回返回假。对于后者,B 的鉴别器不是必须不是“I-AM-A”吗? (2认同)
  • 如果您需要检查带引号的界面,那么输入所有内容有什么意义?哦 (2认同)

vil*_*ane 72

在TypeScript 1.6中,用户定义的类型保护将完成这项工作.

interface Foo {
    fooProperty: string;
}

interface Bar {
    barProperty: string;
}

function isFoo(object: any): object is Foo {
    return 'fooProperty' in object;
}

let object: Foo | Bar;

if (isFoo(object)) {
    // `object` has type `Foo`.
    object.fooProperty;
} else {
    // `object` has type `Bar`.
    object.barProperty;
}
Run Code Online (Sandbox Code Playgroud)

就像Joe Yang提到的那样:自TypeScript 2.0以来,你甚至可以利用标记的联合类型.

interface Foo {
    type: 'foo';
    fooProperty: string;
}

interface Bar {
    type: 'bar';
    barProperty: number;
}

let object: Foo | Bar;

// You will see errors if `strictNullChecks` is enabled.
if (object.type === 'foo') {
    // object has type `Foo`.
    object.fooProperty;
} else {
    // object has type `Bar`.
    object.barProperty;
}
Run Code Online (Sandbox Code Playgroud)

它也适用switch.

  • 哦,但是,这必须假设在运行时这些对象将使用`type`属性创建.在那种情况下它有效.那个例子没有说明这个事实. (3认同)
  • @lhk 不,没有这样的语句,它更像是一种特殊类型,它告诉如何在条件分支内缩小类型。由于 TypeScript 的“范围”,我相信即使将来也不会有这样的声明。`object is type` 和 `object instanceof class` 之间的另一个不同之处在于,TypeScript 中的类型是结构化的,它只关心“形状”而不是对象从哪里获得形状:普通对象或类的实例,没关系。 (2认同)
  • 只是为了清除一个误解,这个答案可以创建:在运行时期间没有元信息来扣除对象类型或其接口. (2认同)

Joe*_*ang 36

typescript 2.0引入了标记联合

Typescript 2.0功能

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape) {
    // In the following switch statement, the type of s is narrowed in each case clause
    // according to the value of the discriminant property, thus allowing the other properties
    // of that variant to be accessed without a type assertion.
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这实际上只是使用鉴别器. (9认同)
  • 创建对象时必须指定其类型吗?这是不可接受的! (4认同)

Cal*_*ack 26

用户定义的类型防护怎么样?https://www.typescriptlang.org/docs/handbook/advanced-types.html

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function isFish(pet: Fish | Bird): pet is Fish { //magic happens here
    return (<Fish>pet).swim !== undefined;
}

// Both calls to 'swim' and 'fly' are now okay.

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}
Run Code Online (Sandbox Code Playgroud)

  • 由于某种原因,这对我不起作用,但 `(pet as Fish).swim !== undefined;` 起作用了。 (4认同)
  • 这是我最喜欢的答案 - 类似于http://stackoverflow.com/a/33733258/469777,但没有可能因缩小等问题而破坏的魔法字符串. (3认同)
  • 当你给 Bird 添加 `swim();` 时,会发生什么,因为你有一只宠物鸭?每个宠物都会被认为是鱼,不是吗? (3认同)
  • @Kayz我猜当你使用“isFish”时,你的代码并不真正关心该对象是否属于任意鱼类别,你更关心你的对象是否支持游泳操作。也许更好的函数名称可以反映这一点,例如“isAquatic”或其他名称。这种识别对象类型的方法称为鸭子类型,如果需要,您可以进一步研究它。但简而言之,如果鸭子会游泳,那么它就是鱼,我们有一个命名问题需要解决。https://en.wikipedia.org/wiki/Duck_typing (2认同)
  • 如果“宠物是鱼”,那么传递鱼或鸟的能力有什么意义呢?这是可怕的可读性! (2认同)

Wil*_*een 20

Typescript 中的类型保护:

TS 有用于此目的的类型保护。他们通过以下方式定义它:

执行运行时检查以保证某个范围内的类型的某些表达式。

这基本上意味着 TS 编译器在拥有足够的信息时可以将类型缩小到更具体的类型。例如:

function foo (arg: number | string) {
    if (typeof arg === 'number') {
        // fine, type number has toFixed method
        arg.toFixed()
    } else {
        // Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?
        arg.toFixed()
        // TSC can infer that the type is string because 
        // the possibility of type number is eliminated at the if statement
    }
}
Run Code Online (Sandbox Code Playgroud)

回到你的问题,我们还可以将类型保护的概念应用于对象以确定它们的类型。要为对象定义类型保护,我们需要定义一个返回类型为类型谓词的函数。例如:

interface Dog {
    bark: () => void;
}

// The function isDog is a user defined type guard
// the return type: 'pet is Dog' is a type predicate, 
// it determines whether the object is a Dog
function isDog(pet: object): pet is Dog {
  return (pet as Dog).bark !== undefined;
}

const dog: any = {bark: () => {console.log('woof')}};

if (isDog(dog)) {
    // TS now knows that objects within this if statement are always type Dog
    // This is because the type guard isDog narrowed down the type to Dog
    dog.bark();
}
Run Code Online (Sandbox Code Playgroud)


小智 15

OP 已经快九年了,这个问题依然存在。我真的真的很想喜欢 Typescript。通常我都会成功。但它的类型安全漏洞是我捏着鼻子也挡不住的恶臭。

我的 goto 解决方案并不完美。但我认为它们比大多数更常用的解决方案更好。事实证明,鉴别器是一种不好的做法,因为它们限制了可扩展性并完全违背了类型安全的目的。我的两个最漂亮的屁股丑陋的解决方案按顺序是:

类装饰器:递归扫描类型化对象的成员并根据符号名称计算哈希值。将哈希值与静态 KVP 属性中的类型名称相关联。在哈希计算中包含类型名称,以减轻与祖先的歧义风险(发生在空子类中)。优点:它被证明是最值得信赖的。它还提供了非常严格的执法。这也类似于其他高级语言本身实现多态性的方式。然而,该解决方案需要进一步扩展才能真正实现多态。缺点:匿名/JSON 对象必须在每次类型检查时重新哈希,因为它们没有要关联和静态缓存的类型定义。过多的堆栈开销会导致高负载场景下出现严重的性能瓶颈。可以通过 IoC 容器来缓解,但这对于没有其他理由的小型应用程序来说也可能是不受欢迎的开销。还需要额外的努力将装饰器应用到每个需要它的对象上。

克隆:非常丑陋,但通过深思熟虑的策略可以带来好处。创建类型化对象的新实例,并从匿名对象中反射性地复制顶级成员分配。给定预定的通过标准,您可以同时检查和克隆转换为类型。类似于其他语言中的“tryParse”。优点:在某些情况下,可以通过立即使用转换后的“测试”实例来减轻资源开销。装饰者不需要额外的努力。大量的灵活性公差。缺点:内存泄漏就像面粉筛子一样。如果没有“深度”克隆,突变的引用可能会破坏其他未预料到封装破坏的组件。静态缓存不适用,因此每次调用都会执行操作 - 具有大量顶级成员的对象将影响性能。刚接触 Typescript 的开发人员会因为不理解你为什么要编写这种模式而误认为你是初级程序员。

总而言之:我不相信“JS 不支持”这种借口来解释 Typescript 在多态性方面的细微差别。转译器绝对适合这个目的。伤口撒盐:来自微软。他们多年前就已经解决了同样的问题,并取得了巨大的成功:.Net Framework 提供了强大的 Interop API,用于采用与 COM 和 ActiveX 的向后兼容性。他们没有尝试转换到较旧的运行时。对于像 JS 这样的松散的解释性语言来说,这个解决方案会更容易、更不那么混乱……但他们却因为害怕输给其他超集而退缩了。使用 JS 中本应由 TS 解决的缺陷作为重新定义静态类型面向对象原则的畸形基础是——嗯——胡说八道。它与数十年来为高级软件开发提供信息的行业领先文档和规范的数量相悖。

  • 考虑添加代码示例,这样可以更轻松地阅读带有简短说明的小代码示例。 (14认同)

pca*_*can 14

现在可以,我刚刚发布了一个TypeScript提供全反射功能的增强版编译器.您可以从其元数据对象中实例化类,从类构造函数中检索元数据并在运行时检查接口/类.你可以在这里查看

用法示例:

在您的一个打字稿文件中,创建一个接口和一个实现它的类,如下所示:

interface MyInterface {
    doSomething(what: string): number;
}

class MyClass implements MyInterface {
    counter = 0;

    doSomething(what: string): number {
        console.log('Doing ' + what);
        return this.counter++;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们打印一些已实现接口的列表.

for (let classInterface of MyClass.getClass().implements) {
    console.log('Implemented interface: ' + classInterface.name)
}
Run Code Online (Sandbox Code Playgroud)

用reflec-ts编译并启动它:

$ node main.js
Implemented interface: MyInterface
Member name: counter - member kind: number
Member name: doSomething - member kind: function
Run Code Online (Sandbox Code Playgroud)

有关Interface元类型的详细信息,请参见reflection.d.ts

更新: 你可以在这里找到一个完整的工作示例

  • downvoted cos我认为这是愚蠢的,但后来暂停了一下,看着你的github页面,看到它保持最新,并且记录良好,所以upvoted而不是:-)我仍然无法证明自己现在正在使用它只是为了`implements`但想要承认你的承诺并且不想成为:-) (7认同)
  • ...呃,那么,我们必须将这些编译器"增强"推送到任何未来的Typescript构建中?这实际上是Typescript的一个分支,而不是Typescript本身,对吧?如果是这样,这不是一个可行的长期解决方案. (4认同)
  • 实际上,我看到这个反射特征的主要目的是创建更好的IoC框架,就像Java世界已经拥有的那些框架(Spring是第一个也是最重要的一个).我坚信TypeScript可以成为未来最好的开发工具之一,而反射是它真正需要的功能之一. (3认同)
  • @dudewad 正如在许多其他主题中所说,这是一个临时解决方案。我们正在等待通过转换器实现编译器可扩展性。请参阅官方 TypeScript 存储库中的相关问题。此外,所有广泛采用的强类型语言都有反射,我认为 TypeScript 也应该有。和我一样,许多其他用户也这么认为。 (2认同)
  • 我对此投了反对票,因为这不是关于打字稿的答案。你基本上是在推广你自己的语言,它可以很好,可以很强大,可以很棒,但无论如何它都不是打字稿。 (2认同)
  • 这正是概念验证的目的:向人们证明事情是可以完成的。问题是:“我知道 javascript 作为一种动态语言没有接口的概念。有什么方法可以对接口进行类型检查吗?” 答案是:不,无需修改/改进,但如果我们有办法扩展/改进语言和编译器,则可以。问题是:谁来决定改变?但这是另一个话题了。 (2认同)

fro*_*rli 12

我认为这是最好的方法;将“Fubber”符号附加到接口上。它的编写速度要快得多,对于 JavaScript 引擎而言,它比类型保护要快得多,它支持接口的继承,并且如果您需要的话,可以使类型保护变得容易编写。

这就是 ES6 使用符号的目的。

界面

// Notice there is no naming conflict, because interfaces are a *type*
export const IAnimal = Symbol("IAnimal"); 
export interface IAnimal {
  [IAnimal]: boolean; // the fubber
}

export const IDog = Symbol("IDog");
export interface IDog extends IAnimal {
  [IDog]: boolean;
}

export const IHound = Symbol("IDog");
export interface IHound extends IDog {
  // The fubber can also be typed as only 'true'; meaning it can't be disabled.
  [IDog]: true;
  [IHound]: boolean;
}
Run Code Online (Sandbox Code Playgroud)

班级

import { IDog, IAnimal } from './interfaces';
class Dog implements IDog {
  // Multiple fubbers to handle inheritance:
  [IAnimal] = true;
  [IDog] = true;
}

class Hound extends Dog implements IHound {
  [IHound] = true;
}
Run Code Online (Sandbox Code Playgroud)

测试

如果您想帮助 TypeScript 编译器,可以将此代码放入类型保护中。

import { IDog, IAnimal } from './interfaces';

let dog = new Dog();

if (dog instanceof Hound || dog[IHound]) {
  // false
}
if (dog[IAnimal]?) {
  // true
}

let houndDog = new Hound();

if (houndDog[IDog]) {
  // true
}

if (dog[IDog]?) {
  // it definitely is a dog
}

Run Code Online (Sandbox Code Playgroud)


Dmi*_*eev 7

类型保护

interface MyInterfaced {
    x: number
}

function isMyInterfaced(arg: any): arg is MyInterfaced {
    return arg.x !== undefined;
}

if (isMyInterfaced(obj)) {
    (obj as MyInterfaced ).x;
}
Run Code Online (Sandbox Code Playgroud)

  • @lhk 只是阅读有关类型保护的打字稿文档... https://www.typescriptlang.org/docs/handbook/advanced-types.html (3认同)
  • “arg is MyInterfaced”是一个有趣的注释。如果失败会怎样?看起来像一个编译时接口检查——这正是我首先想要的。但是如果编译器检查了参数,为什么还要有一个函数体。如果这样的检查是可能的,为什么要把它移到一个单独的函数中。 (2认同)

DS.*_*DS. 7

这是另一个选择:ts-interface-builder模块提供了一个构建时工具,可以将TypeScript接口转换为运行时描述符,而ts-interface-checker可以检查对象是否满足要求。

以OP为例

interface A {
  member: string;
}
Run Code Online (Sandbox Code Playgroud)

您将首先运行ts-interface-builder,它会生成一个带有描述符的新的简洁文件,例如,foo-ti.ts您可以像这样使用它:

interface A {
  member: string;
}
Run Code Online (Sandbox Code Playgroud)

您可以创建一个单行类型保护功能:

import fooDesc from './foo-ti.ts';
import {createCheckers} from "ts-interface-checker";
const {A} = createCheckers(fooDesc);

A.check({member: "hello"});           // OK
A.check({member: 17});                // Fails with ".member is not a string" 
Run Code Online (Sandbox Code Playgroud)


ale*_*rdo 7

根据芬顿的回答,这是我实现的一个函数,用于验证给定的object密钥interface是否全部或部分拥有。

根据您的用例,您可能还需要检查每个接口属性的类型。下面的代码并没有这样做。

function implementsTKeys<T>(obj: any, keys: (keyof T)[]): obj is T {
    if (!obj || !Array.isArray(keys)) {
        return false;
    }

    const implementKeys = keys.reduce((impl, key) => impl && key in obj, true);

    return implementKeys;
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

interface A {
    propOfA: string;
    methodOfA: Function;
}

let objectA: any = { propOfA: '' };

// Check if objectA partially implements A
let implementsA = implementsTKeys<A>(objectA, ['propOfA']);

console.log(implementsA); // true

objectA.methodOfA = () => true;

// Check if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // true

objectA = {};

// Check again if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // false, as objectA now is an empty object
Run Code Online (Sandbox Code Playgroud)


Dan*_*aru 6

与上面使用用户定义的防护的情况相同,但是这次带有箭头功能谓词

interface A {
  member:string;
}

const check = (p: any): p is A => p.hasOwnProperty('member');

var foo: any = { member: "foobar" };
if (check(foo))
    alert(foo.member);
Run Code Online (Sandbox Code Playgroud)


xin*_*ose 6

@progress/kendo-data-query我从文件中找到了一个例子filter-descriptor.interface.d.ts

检查者

declare const isCompositeFilterDescriptor: (source: FilterDescriptor | CompositeFilterDescriptor) => source is CompositeFilterDescriptor;
Run Code Online (Sandbox Code Playgroud)

用法示例

const filters: Array<FilterDescriptor | CompositeFilterDescriptor> = filter.filters;

filters.forEach((element: FilterDescriptor | CompositeFilterDescriptor) => {
    if (isCompositeFilterDescriptor(element)) {
        // element type is CompositeFilterDescriptor
    } else {
        // element type is FilterDescriptor
    }
});
Run Code Online (Sandbox Code Playgroud)


Dan*_*iro 5

我想指出,TypeScript没有提供动态测试对象是否实现特定接口的直接机制.

相反,TypeScript代码可以使用JavaScript技术来检查对象上是否存在适当的成员集.例如:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你有一个复杂的形状怎么办?您不希望在每个深度级别对每个属性进行硬编码 (4认同)

Geo*_*ids 5

我知道我偶然发现了一个 github 包,它可以正确解决这个问题,在浏览我的搜索历史之后,我终于找到了它。查看typescript-is - 虽然它要求您的代码使用 ttypescript 进行编译(我目前正在欺负它与 create-react-app 一起使用,稍后将更新成功/失败),您可以做各种事情与它一起发生的疯狂的事情。与ts-validate-type不同,该包也得到积极维护。

您可以检查某些内容是否是字符串或数字并按原样使用它,而编译器不会抱怨:

import { is } from 'typescript-is';

const wildString: any = 'a string, but nobody knows at compile time, because it is cast to `any`';

if (is<string>(wildString)) { // returns true
    // wildString can be used as string!
} else {
    // never gets to this branch
}

if (is<number>(wildString)) { // returns false
    // never gets to this branch
} else {
    // Now you know that wildString is not a number!
}
Run Code Online (Sandbox Code Playgroud)

您还可以检查自己的接口:

import { is } from 'typescript-is';

interface MyInterface {
    someObject: string;
    without: string;
}

const foreignObject: any = { someObject: 'obtained from the wild', without: 'type safety' };

if (is<MyInterface>(foreignObject)) { // returns true
    const someObject = foreignObject.someObject; // type: string
    const without = foreignObject.without; // type: string
}
Run Code Online (Sandbox Code Playgroud)