Как я могу создать класс расширения для элемента dom в typescript?

#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, вероятно, также является самым простым для модульного тестирования, поэтому я бы согласился с этим.