JavaScript: хочу свой HTMLElement

от автора

Просто хочу строить свой DOM из своих кирпичей.
С преферансом и поэтессами…
И, если уж на то пошло, может быть что-то типа: «раз пошла такая пъянка…»

Думаю некторые понимают, что так можно, но — повторение мать учения, и, то есть, никто не мешает и не мешал делать не так, как все привыкли, не брать чей-то готовый код, и не оставаться в рамках ограничений, наложенных кем-то на что-то «просто потому что».

Что мне это даст:
— мне больше не нужны подписки, могу просто знать, что какое-то свойство изменилось
— могу устраивать коммуникации с нодами на своё усмотрение
— стандартную логику вообще не трогаю и не мешаю ей

Как ? Возьму Proxy и буду оборачивать в него всё, что необходимо.

Итого, сначала создадим «базу» для нашего элемента:

const { HTMLElement } = window;  class MyHTMLElement extends HTMLElement {      constructor() {         super();     }  }  customElements.define('my-custom-element', MyHTMLElement); const myElement = document.createElement('my-custom-element');  console.log('myElement instanceof MyHTMLElement',     myElement instanceof MyHTMLElement);   // true console.log('myElement instanceof HTMLElement',     myElement instanceof HTMLElement);      // true

Да, тут штука в том, что нужно использовать customElements.define и без него никак нельзя, так как оно, в целом, как бы запрещено, без этого мы получим ошибку:

TypeError: Failed to construct ‘HTMLElement’: Illegal construct

Но в целом нас это никак особо не напрягает, поэтмоу продолжим.
Теперь можно добавить Proxy для отслеживания обращений к свойствам.

const { HTMLElement } = window;  class MyHTMLElement extends HTMLElement {      constructor() {         super();     }  }  const protoProps = {}; Object.setPrototypeOf(protoProps, HTMLElement.prototype);  const proxy = new Proxy(protoProps, {     get(target, prop, receiver) {         const result = Reflect.get(target, prop, receiver);         console.log('get:', prop, result);         return result;     },     set(target, prop, value, receiver) {         const result = Reflect.set(target, prop, value, receiver);         console.log('set:', prop, result, value);         return result;     }, });   Object.setPrototypeOf(MyHTMLElement.prototype, proxy);  customElements.define('my-custom-element', MyHTMLElement); const myElement = document.createElement('my-custom-element');  console.log('myElement instanceof MyHTMLElement',     myElement instanceof MyHTMLElement); console.log('myElement instanceof HTMLElement',     myElement instanceof HTMLElement);

Теперь убедимся, что всё работает как задумано, создадим простой HTML и добавим необходимую обвязку.

index.html :

<html>  <head>     <style>         #some {             padding: auto;             text-align: center;             border: 1px solid red;             min-height: 100px;             font-size: 7vh;         }     </style> </head>  <body bgcolor="white">     <div id="some"></div>     <script src="MyHTMLElement.js"></script> </body>  </html>

MyHTMLElement.js :

 const { HTMLElement } = window;  class MyHTMLElement extends HTMLElement {      constructor() {         super();     }  }  const protoProps = {}; Object.setPrototypeOf(protoProps, HTMLElement.prototype);  const proxy = new Proxy(protoProps, {     get(target, prop, receiver) {         const result = Reflect.get(target, prop, receiver);         console.log('get:', prop, result);         return result;     },     set(target, prop, value, receiver) {         const result = Reflect.set(target, prop, value, receiver);         console.log('set:', prop, result, value);         return result;     }, });   Object.setPrototypeOf(MyHTMLElement.prototype, proxy);  customElements.define('my-custom-element', MyHTMLElement); const myElement = document.createElement('my-custom-element');  console.log('myElement instanceof MyHTMLElement',     myElement instanceof MyHTMLElement); console.log('myElement instanceof HTMLElement',     myElement instanceof HTMLElement);  console.log('render begins'); myElement.innerText = 123; const renderBox = document.getElementById('some'); renderBox.appendChild(myElement); console.log('render finish'); 

Теперь в консоли «видно всё» :

Конечно, теперь нам никто не запрещает играться с ним так, как нам вздумается, например добавить в нехо собственных свойств и наладить собственный прямой канал общения для внешних коммуникаций:

 const { HTMLElement } = window;  class MyHTMLElement extends HTMLElement {      communicate(value) {         this.innerHTML = `${this.protoAddition} + ${this.addition} + ${value}`;     }      addition = 'addition';      constructor() {         super();     }  }  const protoProps = {     protoAddition: 'protoAddition', }; Object.setPrototypeOf(protoProps, HTMLElement.prototype);  const proxy = new Proxy(protoProps, {     get(target, prop, receiver) {         const result = Reflect.get(target, prop, receiver);         console.log('get:', prop, result);         return result;     },     set(target, prop, value, receiver) {         const result = Reflect.set(target, prop, value, receiver);         console.log('set:', prop, result, value);         return result;     }, });   Object.setPrototypeOf(MyHTMLElement.prototype, proxy);  customElements.define('my-custom-element', MyHTMLElement); const myElement = document.createElement('my-custom-element');  console.log('myElement instanceof MyHTMLElement',     myElement instanceof MyHTMLElement); console.log('myElement instanceof HTMLElement',     myElement instanceof HTMLElement);  console.log('render begins'); myElement.innerText = 123;          // set   const renderBox = document.getElementById('some'); renderBox.appendChild(myElement); console.log('render finish');  myElement.communicate('message');  console.log(myElement.innerText);   // get

Если нужна ссылка на Gist, то Держите .

Надеюсь, что теперь код различных Front-End библиотек будет менее пугающим и загадочным, нет никакой магии, всё банально и не очень сложно.

Конечно, есть и другие API со схожим поведением, Listeners, Observables и т.п. Есть преимущества, есть недостатки. Но если хочется чего-то «простого как топор», то вот.

Спасибо за внимание )


ссылка на оригинал статьи https://habr.com/ru/articles/939728/