typescript添加新属性(类型)到现有接口

6jygbczu  于 2023-02-05  发布在  TypeScript
关注(0)|答案(1)|浏览(330)

我对TypeScript是个新手,3天前才开始学习。当我在玩TypeScript(接口)的时候,我遇到了一个问题。当我尝试这样做的时候,它给了我一个错误:

const divEl:HTMLElement = document.createElement("div");

divEl.someRandomThing = "Some random thing!";
ERROR: error TS2339: Property 'someRandomThing' does not exist on type 'HTMLElement'.

如何向HTMLElement添加自定义属性(type)(此接口来自lib. dom. d. ts)?
我试过这个:

interface IMyInterface extends HTMLElement {
  someRandomThing: string
}

const divEl:IMyInterface = document.createElement("div");

divEl.someRandomThing = "Some random thing!";

它给了我另一个错误:

ERROR: error TS2741: Property 'someRandomThing' is missing in type 'HTMLDivElement' but required in type 'IMyInterface'.

如果我在"someRandomThing"中添加一个问号,错误就会消失:

interface IMyInterface extends HTMLElement {
  someRandomThing?: string //<----- 
}

我总是要加一个问号吗?有没有一种方法,我不需要使用问号,而只是添加到HTMLElement的属性(类型)(这个接口是从lib.dom.d.ts)接口?

nle07wnf

nle07wnf1#

如何向HTMLElement添加自定义属性(type)(此接口来自lib. dom. d. ts)?
您的接口可以很好地完成这一任务,问题是您从createElement返回的内容没有新属性,因此无法满足新接口类型的要求,因此TypeScript会警告您这一点,因为这是TypeScript的主要任务。:-)
当需要以这种方式扩展对象时,Object.assign是一种方便的一次性方法:

const div = Object.assign(
    document.createElement("div"),
    { someRandomThing: "Some random thing!"}
);

Object.assign的返回类型是其输入类型的交集,在本例中为HTMLElement & { someRandomThing: string; }
对于可重用的对象,你可以写一个返回IMyInterface的新函数,在内部处理对象类型的变化,这个函数可以像上面那样使用Object.assign,或者直接在内部使用一个小类型Assert来赋值给对象:

function createExtendedElement(tagName: string, someRandomThing: string): IMyInterface {
    const element: Partial<IMyInterface> = document.createElement(tagName);
    element.someRandomThing = someRandomThing;
    return element as IMyInterface;
}
// Usage:
const divEl = createExtendedElement("div", "Some random thing!");

as IMyInterface是一个类型Assert,一般来说,避免类型Assert,但是在像这种实用函数这样的包含良好的上下文中,它们是可以的。
如果需要的话,可以通过使该函数通用并提供属性名称和值来泛化该函数:

function createExtendedElement<Key extends PropertyKey, Value extends any>(
    tagName: string,
    propName: Key,
    value: Value
): HTMLElement & {[P in Key]: Value} {
    const element = Object.assign(
        document.createElement(tagName),
        {[propName]: value}
    );
    return element as HTMLElement & {[P in Key]: Value};
}
// Usage:
const divEl = createExtendedElement("div", "someRandomThing", "Some random thing!");

上面的Playground链接。
在上面的代码中,你可能已经注意到,在第一个内联版本中,元素的类型是HTMLDivElement(加上someRandomThing),而不是jut HTMLElement(加上someRandomThing),但是其他版本中它只是HTMLElement(加上someRandomThing)。
您可以通过使标记名也成为泛型来解决这个问题,从createElement中提升标记参数和返回类型的定义:

function createExtendedElement<
    Tag extends keyof HTMLElementTagNameMap,
    Key extends PropertyKey,
    Value extends any
>(
    tagName: Tag,
    propName: Key,
    value: Value
): HTMLElementTagNameMap[Tag] & {[P in Key]: Value} {
    const element = Object.assign(
        document.createElement<Tag>(tagName),
        {[propName]: value}
    );
    return element as HTMLElementTagNameMap[Tag] & {[P in Key]: Value};
}
// Usage:
const divEl = createExtendedElement("div", "someRandomThing", "Some random thing!");

其中,divEl的类型是HTMLDivElement & { someRandomThing: string; }
Playground链接
附带说明:这与上面的类型相关方面基本上没有什么关系,但一般来说,就是向来自代码外部的对象(在本例中是DOM)添加自己的属性(这不是TypeScript的问题,只是编码的问题)。
1.从工程学的Angular 来看,它确实不是你要修改的对象,你不能确定这会对拥有该对象的代码产生什么影响。
1.可能会与页面上运行的其他代码(包括浏览器扩展)发生冲突。假设您的代码向元素添加了marker属性,而页面中的其他代码也对元素使用了自定义marker属性?突然之间,您的代码和其他代码发生了交叉对话,可能会相互干扰。
1.如果用足够多的代码或足够成功的库来完成,那么(在本例中)WHAT-WG就很难向DOM元素添加新的标准属性。在ES5中,JavaScript的数组有了一个新的标准方法some。它返回一个标志,表示数组中的any元素是否与您提供的 predicate 匹配。那么为什么它被称为"some"而不是"any"呢?因为有一个非常重要的库,它向数组添加了自己的方法any,以及该库的编写方式,添加一个标准的会破坏使用该库的网站上的代码,所以我们坚持使用some,它实际上没有什么意义,但却是最好的。
在DOM的特定情况下,添加您自己的属性在所有现代浏览器上都能可靠地工作,而且这样做已有很长的历史(jQuery从v1开始就这样做了,尽管名称越来越晦涩)如果您选择这样做,请确保您使用的名称不太可能与现在或将来的任何名称冲突,或者更好的是,使用Symbol作为属性键。它保证是唯一的(如果您不通过Symbol.for使其全局可访问)。
无论何时,只要您想将自己的信息添加到来自代码外部的对象(并且不打算由代码"拥有"),至少可以执行两项操作,而不是将自己的属性添加到对象:
1.使用合成。例如:

const something = {
    element: document.createElement("div"),
    someRandomThing: "Some random thing!"
};

现在,元素和someRandomThing被放在同一个容器中,当你需要someRandomThing时,你可以使用something.someRandomThing,当你需要元素时,你可以使用something.element
1.使用由元素键控的WeakMap。例如:

// Somewhere you can access it wherever you need it in your app:
const elementInfo = new WeakMap();
// ...
// Now when you're associating information with the element:
const element = document.createElement("div");
elementInfo.set(element, {
    someRandomThing: "Some random thing!"
});

额外的信息在由实际元素示例键控的WeakMap中。当需要该元素时,使用element。当需要它的信息时,使用elementInfo.get(element)?.someRandomThing。如果在某个时候从DOM中删除了该元素,并且删除了对它的所有其他引用,则WeakMap不会阻止该元素被垃圾收集。相反,则Map中针对它的条目消失。
只是FWIW

相关问题