【问题标题】:How to augment interfaces with functions in Typescript?如何在 Typescript 中使用函数增强接口?
【发布时间】:2021-10-26 06:44:14
【问题描述】:

我的界面如下所示:

export interface Attribute {
  name: string;
}

有了这个,我可以使用这个语法来创建一个Attribute

const attr: Attribute = {
  name: "foo"
};

我的问题是我不想使用类,因为它们笨拙且不够灵活,但我想继续使用上面的语法来创建对象,因为它非常简洁。我读过可以进行声明合并,所以我尝试这样做以向该接口添加一个新函数:

import { Attribute } from "./Attribute";

declare module "./Attribute" {
  interface Attribute {
    matches(other: Attribute): boolean;
  }
}

Attribute.prototype.matches = function (other: Attribute): boolean {
  return this.name === other.name;
};

这里的问题是没有prototype 可以扩充,因为接口在运行时不可用。如果我将Attribute 接口与一个类合并:

export class Attribute {}

export interface Attribute {
  name: string;
}

有一个原型并且扩充编译但我仍然不能使用它:

import { Attribute } from "./Attribute";
import "./AttributeAugments";

const attr: Attribute = {
  name: "foo"
};
// ^^^--- Property 'matches' is missing in type '{ id: number; name: string; }' but required in type 'Attribute'

console.log(attr);

有没有办法做到这一点?我的目标是能够做到attr.matches(other) 而不必做matches(attr, other)。这样我就可以像attr.a().b().c() 那样做函数链接,而不必做c(b(a(attr)))

【问题讨论】:

  • 你知道attr的所有方法吗?
  • 你说类不够灵活,但看起来你只是想创建一个类,但隐含:)

标签: typescript


【解决方案1】:

根据您的代码,Attribute 是一个接口。一个类型或接口在 JavaScript 中编译为空;因此,您不能在您的问题中这样做。

declare module "./Attribute" {
  interface Attribute {
    matches(other: Attribute): boolean;
  }
}

// No such "Attribute" variable!
Attribute.prototype.matches = function (other: Attribute): boolean {
  return this.name === other.name;
};

const attr: Attribute = {
  name: "foo"
};
attr.matches(); // error, attr's prototype is Object, not Attribute
// because there is no constructor function or class Attribute

有一些方法可以解决您的问题。

(1) 每次创建Attribute 类型的变量时始终包含matches 方法:

export function matches(this: Attribute, other: Attribute): boolean {
  return this.name === other.name;
};
const attr: Attribute = {
  name: "foo",
  matches,
}; // works

(2) 使用Attribute 类但简化其实例化:

type ExcludeMethods<T extends object> = Pick<
  T,
  {
    [x in keyof T]: T[x] extends Function ? never : x;
  }[keyof T]
>; // without this type, "matches" method is required in the object literal!

class Attribute {
  constructor(init: ExcludeMethods<Attribute>) {
    Object.assign(this, init);
  }

  name: string;

  matches(other: Attribute): boolean {
    return this.name === other.name;
  }
}

const attr = new Attribute({ name: "foo" }); // works
const attr2 = new Attribute({}); // error, name is required

如果name: string 属性导致编译错误,请添加非空断言(使其变为name!: string),因为我们非常确定非函数字段必须存在于Object.assignthis

【讨论】:

    【解决方案2】:

    我不太确定您是否需要那种复杂的声明合并;更简单的解释是“接口是开放式的 (cite)”,因此您可以向现有接口添加功能。

    例子:

    interface Attribute {
        name: string;
    }
    
    interface Attribute {
        matches(other: Attribute): boolean;
    }
    
    const attr1: Attribute = {
        name: "foo",
        matches: function(other: Attribute): boolean {
            return this.name === other.name;
        }
    }
    
    const attr2: Attribute = {
        name: "foo",
        matches: function(other: Attribute): boolean {
            return this.name === other.name;
        }
    }
    
    console.log(attr1.matches(attr2));
    

    仅供参考,您实际上会更好地使用此课程。在我的第一个示例中,我通过为每个对象字面量重复 matches 实现来违反 DRY 原则。使用类时,matches函数是绑定到原型的,所以没有重复。

    例子:

    class Attribute {
        constructor(public name: string) {
        }
    
        // Typically this is called `equals` in other languages; i.e. Java, Kotlin, C#...
        public matches(other: Attribute): boolean {
            return this.name == other.name;
        }
    }
    
    const attr1 = new Attribute("foo");
    const attr2 = new Attribute("foo");
    
    console.log(attr1.matches(attr2));
    

    如果您真的想要声明这样的对象,您也可以使用类来执行此操作(尽管它不是很传统)。以下代码是上述示例的扩展。

    例子:

    const attr3: Attribute = {
        name: "bar",
        matches: Attribute.prototype.matches
    }
    

    如果您真的不想使用类,create 函数可能会有所帮助(大致相当于类构造函数)。

    例子:

    interface Attribute {
        name: string;
    }
    
    interface Attribute {
        matches(other: Attribute): boolean;
    }
    
    function createAttribute(name: string): Attribute {
        return {
            name: name,
            matches: function(other: Attribute): boolean {
                return this.name === other.name;
            }
        }
    }
    
    const attr1: Attribute = createAttribute("foo");
    const attr2: Attribute = createAttribute("bar");
    const attr3: Attribute = createAttribute("foo");
    
    console.log(attr1.matches(attr2));
    console.log(attr1.matches(attr3));
    

    【讨论】:

    • 重点是我不想每次都重新声明matches。这就是为什么我尝试将它添加到prototype,但当然interfaces 没有运行时表示,所以我做不到。
    • 谢谢。我想我只会使用类型所在模块中定义的普通函数。仅仅能够做到foo.bar 而不是bar(foo) 就太麻烦了。谢谢!
    猜你喜欢
    • 2018-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-21
    • 2019-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多