【问题标题】:Proxy a WebComponent's constructor that extends HTMLElement代理扩展 HTMLElement 的 WebComponent 的构造函数
【发布时间】:2018-05-26 13:26:34
【问题描述】:

因此,在我创建的使用自定义元素的库中,您显然需要在实例化之前在 CustomElementsRegistry 中定义类。

目前,这个问题正在通过装饰器解决:

class Component extends HTMLElement {

    static register (componentName) {
        return component => {
            window.customElements.define(componentName, component);
            return component;
        }
    }
}

@Component.register('my-element')
class MyElement extends Component { }

document.body.appendChild(new MyElement());

这可行,但是,我想在类的实例化时自动注册自定义元素(这样作者就不必将装饰器添加到他们编写的每个组件中)。这可以通过Proxy 来完成。


不过,我的问题是,当我尝试在构造函数上使用代理并尝试返回目标的实例时,我仍然得到 Illegal Constructor,就好像该元素具有从未在注册表中定义。

这显然与我在代理内部实例化类的方式有关,但我不确定如何做到这一点。我的代码如下:

请在最新的 Chrome 中运行:

class Component extends HTMLElement {

    static get componentName () {
        return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
    }
}

const ProxiedComponent = new Proxy(Component, {

    construct (target, args, extender) {
        const { componentName } = extender;
  	
        if (!window.customElements.get(componentName)) {
            window.customElements.define(componentName, extender);
        }
    
        return new target(); // culprit
    }
});

class MyElement extends ProxiedComponent { }

document.body.appendChild(new MyElement());

如何在代理内部继续继承链,而不会丢失我正在实例化 MyElement 类的上下文,这样它就不会抛出 Illegal Constructor异常?

【问题讨论】:

  • 我相信这是预期的行为。你应该可以做到document.createElement(LibElement.componentName)。通过扩展原生 HTMLElement 创建的类实际上对于构造元素无效,而应该使用 document.createElement 来构造。
  • 作为解决方案,您可以在代理构造中执行return document.createElement(target.componentName)
  • @shamsup 你是说它们对于根据规范构造元素无效,还是说它根本不起作用——因为它确实起作用(在 chrome 中)。
  • 我的意思是即使只是class Custom extends HTMLElement {}; new Custom();也会抛出非法构造函数错误,因为HTMLElement不能用new关键字创建,必须通过document.createElement创建
  • 这仅适用于尚未将类添加到CustomElementsRegistry

标签: javascript custom-element proxy-pattern


【解决方案1】:

有两个问题:

  • new target() 创建了 LibElement 实例,该实例未注册为自定义元素。在这里你得到了Illegal Constructor 错误。
  • 即使您注册LibElement,生成的 DOM 元素也将是 <lib-element>,因为您调用了 new target,此时 javascript 不知道子类。

我发现的唯一方法是使用Reflect API 创建正确的对象实例。

class LibElement extends HTMLElement {
    static get componentName () {
        return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
    }
}

const LibElementProxy = new Proxy(LibElement, {
    construct (base, args, extended) {
        if (!customElements.get(extended.componentName)) {
            customElements.define(extended.componentName, extended);
        }
    
        return Reflect.construct(base, args, extended);
    }
});

class MyCustomComponent extends LibElementProxy {}
class MyCustomComponentExtended extends MyCustomComponent {}

document.body.appendChild(new MyCustomComponent());
document.body.appendChild(new MyCustomComponentExtended());

我真的很喜欢这种使用代理构造函数来自动注册自定义元素的想法)

【讨论】:

  • 这就是解决方案!你太棒了。我以为我将不得不求助于Reflect,但我不确定代码中的时间和位置。
【解决方案2】:

您在这里非常接近解决方案,唯一的问题是原生HTMLElement无法用new关键字实例化,必须通过document.createElement创建。您可以重用您拥有的所有内容,并且只替换代理中construct 方法的返回值:

class Component extends HTMLElement {

  static get componentName() {
    return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
  }
}

const ProxiedComponent = new Proxy(Component, {

  construct(target, arguments, extender) {
    const {
      componentName
    } = target;

    if (!window.customElements.get(componentName)) {
      window.customElements.define(componentName, extender);
    }

    return document.createElement(target.componentName); // Properly constructs the new element
  }
});

class MyElement extends ProxiedComponent {}

document.body.appendChild(new MyElement());

【讨论】:

  • 除非你可以在继承类上使用new关键字,只要类已经注册到CustomElementsRegistry。不过,我会看看这是否有效。
  • 是的,这不能正常工作。它要么创建一个<component/> 节点,要么如果我将target 更改为extender,它会创建一个<my-element/> 节点,但它是一个HTMLUnknownElement,而不是从Component 继承。
猜你喜欢
  • 2016-12-26
  • 2012-01-09
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 2014-05-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多