Просто хочу строить свой 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/
Добавить комментарий