Прокси - конструктор WebComponent, который расширяет HTMLElement
Итак, в библиотеке, которую я создаю, которая использует пользовательские элементы, вам, очевидно, нужно определить класс в 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());
Ответы
Ответ 1
Было 2 проблемы:
-
new target()
создан LibElement
экземпляр, который не зарегистрирован как пользовательский элемент. И здесь вы получили ошибку Illegal Constructor
.
- даже если вы зарегистрируете
LibElement
, в результате элемент DOM будет <lib-element>
, вы вызовите new target
, и в этот момент javascript не имеет понятия о дочернем классе.
Единственный способ, которым я нашел, - использовать API Reflect
для создания правильного экземпляра объекта.
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());
Ответ 2
Здесь вы очень близки к решению, и единственная проблема заключается в том, что native 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());