Как правильно объявить имена вычисляемых свойств в классах TypeScript?

Короче говоря

Как объявить в TypeScript: В этом классе любое свойство, начинающееся с '$', является ссылкой на элемент?

Контекст

I have a Custom Element class method that automatically stores any child element having attribute data-reference in a property named after its value prefixed by '$'. It works perfectly in vanilla JavaScript :

class SomeComponent extends HTMLElement {
  connectedCallback() {
    this.setReferences()
    console.log(this.$myRef) // <div data-reference="myRef"></div>
  }

  setReferences() {
    const referencedElements = this.querySelectorAll('[data-reference]')

    referencedElements.forEach((element) => {
      const referenceName = '$' + element.getAttribute('data-reference')
      this[referenceName] = element
    })
  }
}
customElements.define('some-component', SomeComponent)
<some-component>
  <div data-reference="myRef"></div>
</some-component>

Но это не скомпилируется в TypeScript, кто не знает о свойстве $myRef:

console.log(this.$myRef) // Property '$myRef' does not exist on type 'SomeComponent'
                 ^^^^^^

Пока я придумал 3 обходных пути, но ни один из них не полностью удовлетворяет.

1. Утверждение типа

Неа!

console.log((this as any).$myRef) // <div data-reference="myRef"></div>

Дело в том, чтобы обеспечить быстрый доступ к любому $ref, поэтому добавление as any или <any> (плюс скобки) не является жизнеспособным решением.

2. Литерал объекта

Вероятно, самый чистый, но не совсем то, чего я хочу достичь.

type ElementReferenceMap = { [key: string]: Element }

class SomeComponent extends HTMLElement {
    ref: ElementReferenceMap = {};

    setReferences() {
        const referencedElements = this.querySelectorAll('[data-reference]');

        referencedElements.forEach((element) => {
            const referenceName = element.getAttribute('data-reference');
            this.ref[referenceName] = element as Element;
        })

        console.log(this.ref.myRef); // <div data-reference="myRef"></div>
    }
}

Переименовав его в this.$.myRef, я стал ближе, но он все еще не эквивалентен стандартной версии JavaScript.

3. Свойство индекса

Работает, как и ожидалось (как и версия JavaScript), но кажется неправильным, так как допускает любое значение свойства.

class SomeComponent extends HTMLElement {
    // [x: string]: Element; // conflict with other properties
    [x: string]: any; // ok

    setReferences() {
        const referencedElements = this.querySelectorAll('[data-reference]');

        referencedElements.forEach((element) => {
            const referenceName = '$' + element.getAttribute('data-reference');
            this[referenceName] = element;
        })

        console.log(this.$myRef); // <div data-reference="myRef"></div>
    }
}

Возможно, мне не хватает очевидного решения, так как я новичок в TypeScript. Заранее спасибо!


person Alkaniszt    schedule 17.06.2020    source источник
comment
Второй вариант - путь. Проблема с любым свойством, начинающимся с «$» — его невозможно проверить.   -  person Aleksey L.    schedule 17.06.2020


Ответы (1)


Невозможно, чтобы машинописный текст мог генерировать статические проверки вашего html-кода, он просто не может сделать html динамическим (вы можете динамически генерировать DOM с атрибутами data-reference) 2 или 3 оба являются жизнеспособными вариантами. Просто подумайте об этом: typescript проверяет типы во время компиляции, перед доставкой, когда HTML можно заполнить дополнительными элементами позже, во время выполнения.

Если вы заранее знаете свои элементы, вы можете предоставить информацию о статическом типе для машинописного текста. Например, для варианта (3):

class SomeComponent extends HTMLElement {
    [x: '$myRef' | '$myOtherRef']: HTMLElement; // ok
    ....

Или вы можете просто иметь простое свойство:

class SomeComponent extends HTMLElement {
    $myProp: HTMLElement
    ....
person Archeg    schedule 17.06.2020
comment
Ну, я не ожидал, что машинописный текст будет проверять непосредственно HTML, мне было интересно, может ли быть решение, похожее на ваш первый пример, например [x: '$' + string]: HTMLElement (конечно, это не имеет смысла, но вы поняли идею). Кроме того, идея заключалась в том, что любой class SomeDerivingComponent extends SomeComponent {} получает свои собственные ссылки автоматически, поэтому я немного упускаю смысл, если мне приходится их явно указывать. Но я понимаю, что то, что я ищу, не существует. Спасибо за Ваш ответ! - person Alkaniszt; 17.06.2020
comment
Я не знаю, насколько хорош этот совет (имеется в виду, пожалуйста, относитесь к нему с долей скептицизма), но в подобных случаях я часто заканчиваю тем, что генерирую машинописные определения вручную. Например, напишите небольшой скрипт на Python, который, возможно, анализирует ваш html (или что-то еще, idk?) И генерирует .ts файл с определениями типов для того, что вам нужно. На самом деле это намного проще, чем кажется, но это дополнительная поддержка. - person Archeg; 18.06.2020