#typescript #typescript-generics
Вопрос:
Пожалуйста, посоветуйте, как реализовать класс расширения для элементов DOM в typescript. Допустим, я хочу добавить два метода enable
и disable
к стандартным методам элементов DOM.
До сих пор я должен это делать:
export class FormElement { public element: HTMLElement constructor(selector: string) { this.element = document.querySelector(selector) } public disable = (): void =gt; { this.element.setAttribute('disabled', 'disabled') } public enable = (): void =gt; { this.element.removeAttribute('disabled') } }
В каком-то месте я хочу получить элемент DOM, расширенный классом FormElement
class SomeComponent { ... private get nameInput(): FormElement { return new FormElement('input#company_msisdn') } private disableNameInput = (): void =gt; { this.nameInput.disable() } }
Выглядит хорошо, но если вам нужно использовать методы DOM элемента, это выглядит не очень хорошо:
... private initializeNameInput = (): void =gt; { this.nameInput.element.addEventListener('change', this.onNameInputChange) }
Скажите мне, есть ли способ реализовать класс FormElement так, чтобы вызов вновь добавленных методов и методов DOM элемента выглядел одинаково:
this.nameInput.enable() this.nameInput.addEventListener('change', this.onNameInputChange)
?
Например, я пытаюсь создать класс с помощью genric:
class FormElementlt;T = HTMLElementgt; { private this.element constructor(selector: string) { this.element = document.querySelector(selector) as T } ... } const nameInput = new FormElementlt;HTMLInputElementgt;('#name-input') nameInput.disable() nameInput.addEventListener('change', this.onNameInputChange)
Но это неправильный код, пожалуйста, посоветуйте, как реализовать class и generic.
Комментарии:
1. Насколько я понял, вы хотите избавиться от промежуточного
element
имущества? правильно? Я все еще не понимаю, что не так сthis.nameInput.element.addEventListener('change', this.onNameInputChange)
2.
.element.
для меня это не ясное решение.3. Действительно ли нужен этот класс, поскольку вы только инкапсулируете личное поле, от которого, как я понимаю, вы хотите избавиться — простая функция, подобная следующей, может помочь:
const getElementFromDOM = (selector: string) =gt; document.querySelector(selector);
где, как вы можете ее использовать:const nameInput = getElementFromDOM(someSelector); nameInput.addEventListener(...)
? Я имею в виду, возможен ли для вас функциональный подход?4. Если вы делаете ООП более простым способом, почему бы просто не наследовать от
HTMLElement
конструктора?
Ответ №1:
Я вижу вашу боль с промежуточным свойством «элемент». Если ваше требование не зафиксировано на «это должен быть класс». Я бы предложил вам некоторые альтернативы, например, ввести функции в прототип или завершить функциональный стиль.
Ниже вы найдете три подхода к тому, как это можно было бы решить.
Подход 1 — Внедрение методов в прототип
Рассматривайте этот подход как синтаксический сахар и может быть очень спорным.
// Types for your new methods, this extends the original HTMLElement interface interface HTMLElement { enable: () =gt; void; disable: () =gt; void; } // Add concrete implementation of the new HTMLElement functions HTMLElement.prototype.disable = function () { this.setAttribute('disabled', 'disabled'); } HTMLElement.prototype.enable = function () { this.removeAttribute('disabled') } const getElementFromDOM = (selector: string): HTMLElement | null =gt; document.querySelector(selector); // usage const nameInput = getElementFromDOM("input#name"); nameInput?.disable(); nameInput?.enable();
Недостаток: Вновь добавленные функции могут мешать уже существующим методам, например, добавление «некоторых» в Array
прототип вызовет помехи. Это может быть трудно отследить или сбить с толку других. Хорошее ключевое слово, которое следует упомянуть здесь, — «загрязнение прототипа».
Подход 2 — Функциональный стиль
В качестве альтернативы, вместо расширения прототипа HTMLElement, вы можете предоставить дополнительные методы, которые используют HTMLElement в качестве параметра для отключения/включения элементов, это может выглядеть следующим образом:
const disable = (element: HTMLElement) =gt; element.setAttribute('disabled', 'disabled'); const enable = (element: HTMLElement) =gt; element.removeAttribute('disabled'); // usage const nameInput = getElementFromDOM("input#name"); disable(nameInput); enable(nameInput);
Недостаток: Нет ничего подобного nameInput.disable()
, и поэтому его нельзя смешивать с HTMLElement
функциями, указанными в вашем вопросе (например nameInput.setAttribute(...)
).
Подход 3 — Создайте новый элемент, полученный из HTMLElement
И если предыдущие подходы не подходят для вас, и вы определенно хотите, чтобы класс, от которого лучше всего избавиться .element.
, определил совершенно новый элемент.
class FormElement implements HTMLElement { // Here you would have to implement all methods etc, which HTMLElement already has }
Недостаток: Я надеюсь, что это очевидно для всех, огромное количество «повторной реализации» уже существующей функциональности.
Комментарии:
1. Мне больше всего нравится второй подход. Не думайте, что изменение прототипа-хорошая идея
2. Мой личный подход состоял бы в том, чтобы пойти со вторым. Это тот, который с меньшей вероятностью вызовет много переделок или путаницы и кажется самым чистым. Спасибо, что указали, я добавил небольшое описание «недостатков» к каждому из подходов.
3. Вариант 2, вероятно, также является самым простым для модульного тестирования, поэтому я бы согласился с этим.