Как утверждать тип HTMLElement в TypeScript?

Я пытаюсь это сделать:

var script:HTMLScriptElement = document.getElementsByName("script")[0];
alert(script.type);

но это дает мне ошибку:

Cannot convert 'Node' to 'HTMLScriptElement': Type 'Node' is missing property 'defer' from type 'HTMLScriptElement'
(elementName: string) => NodeList

Я не могу получить доступ к члену type элемента скрипта, если не приведу его к правильному типу, но я не знаю, как это сделать. Я просмотрел документы и образцы, но ничего не нашел.


person Spongman    schedule 02.10.2012    source источник
comment
Обратите внимание, что эта проблема с кастингом больше не существует в версии 0.9 - см. Ответ @Steve ниже.   -  person Greg Gum    schedule 19.02.2014
comment
@GregGum Я не вижу ответа Стива   -  person Steve Schrab    schedule 04.05.2017


Ответы (13)


TypeScript использует '‹>' для окружения приведения типов, поэтому приведенное выше становится:

var script = <HTMLScriptElement>document.getElementsByName("script")[0];

Однако, к сожалению, нельзя:

var script = (<HTMLScriptElement[]>document.getElementsByName(id))[0];

Вы получаете ошибку

Cannot convert 'NodeList' to 'HTMLScriptElement[]'

Но вы можете:

(<HTMLScriptElement[]><any>document.getElementsByName(id))[0];
person Spongman    schedule 02.10.2012
comment
Я думаю, им следует изучить это дополнительно, предположим, вы используете $ ('[type: input]'). each (function (index, element), и вам нужно, чтобы элемент был приведен к HTMLInputElement или HTMLSelectElement в зависимости от того, какое свойство вам нужно установить / get, использование кастинга (элемент ‹HTMLSelectElement› ‹any›) .selectedIndex = 0; добавляет () вокруг элемента, некрасиво - person rekna; 05.10.2012
comment
+1, который ответил на мой вопрос stackoverflow.com/questions/13669404 / - person lhk; 02.12.2012
comment
В конечном итоге (после выхода версии 0.9) вы сможете преобразовать его во что-то вроде NodeList ‹HtmlScriptElement›, плюс getElementsByName сможет использовать переопределения типа строкового литерала, чтобы получить это правильно, без какого-либо преобразования! - person Peter Burns; 07.04.2013
comment
любой просто огромен, без тс было бы просто шуткой - person Luke; 18.09.2013
comment
после 1.0 синтаксис должен быть (<NodeListOf<HTMLScriptElement>>document.getElementsByName(id))[0]; - person Will Huang; 10.07.2014
comment
any - это мост, но здесь его применять не следует. NodeList не является массивом из Node и не должен рассматриваться как массив. Лучше было бы <Node[]>Array.prototype.slice.call(nodeList); - person vilicvane; 15.11.2014
comment
Спасибо. Мне грустно, что это нужно делать вот так, но это работает. Лучше чем ничего:/ - person Laker; 21.06.2016
comment
Вы также можете использовать as to cast. var script = document.getElementsByName (script) [0] как HTMLScriptElement; - person JGFMK; 02.09.2017
comment
Рекомендуемый тип для использования - HTMLInputElement в соответствии с github.com/Microsoft/TypeScript/issues/10453. - person Daedalon; 21.03.2018

Начиная с TypeScript 0.9 файл lib.d.ts использует специальные сигнатуры перегрузки, которые возвращают правильные типы для вызовов getElementsByTagName.

Это означает, что вам больше не нужно использовать утверждения типа для изменения типа:

// No type assertions needed
var script: HTMLScriptElement = document.getElementsByTagName('script')[0];
alert(script.type);
person Fenton    schedule 15.01.2014
comment
как это сделать в объектной нотации? т.е. я не могу сделать {name: ‹HTMLInputElement›: document.querySelector ('# app-form [name]'). value,} - person Nikos; 14.01.2017
comment
это сработало: name: (‹HTMLInputElement› document.querySelector ('# app-form [name]')). value, - person Nikos; 14.01.2017

Не вводите cast. Никогда. Используйте защитные ограждения типа:

const e = document.getElementsByName("script")[0];
if (!(e instanceof HTMLScriptElement)) 
  throw new Error(`Expected e to be an HTMLScriptElement, was ${e && e.constructor && e.constructor.name || e}`);
// locally TypeScript now types e as an HTMLScriptElement, same as if you casted it.

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

В этом случае это может показаться излишним, но это очень поможет вам, если вы вернетесь позже и измените селектор, например, добавив класс, который отсутствует в dom.

person Benoit B.    schedule 20.04.2017
comment
Конечно, здесь безопасно? Мы можем гарантировать, что e всегда будет экземпляром HTMLScriptElement, не так ли (если он не существует, я полагаю)? - person Richard Hunter; 30.09.2020

Вы всегда можете взломать систему типов, используя:

var script = (<HTMLScriptElement[]><any>document.getElementsByName(id))[0];
person Jack128    schedule 02.10.2012
comment
использование ‹any› позволяет избежать проверки типов, что не идеально, но круто, пока находится в разработке - person tit; 22.11.2018

Чтобы в итоге получилось:

  • реальный Array объект (не NodeList, одетый как Array)
  • список, который гарантированно будет включать только HTMLElements, а не Nodes, принудительно приведенных к HTMLElements
  • теплое нечеткое чувство делать правильную вещь

Попробуй это:

let nodeList : NodeList = document.getElementsByTagName('script');
let elementList : Array<HTMLElement> = [];

if (nodeList) {
    for (let i = 0; i < nodeList.length; i++) {
        let node : Node = nodeList[i];

        // Make sure it's really an Element
        if (node.nodeType == Node.ELEMENT_NODE) {
            elementList.push(node as HTMLElement);
        }
    }
}

Наслаждаться.

person Johannes Fahrenkrug    schedule 25.09.2015

Мы могли бы ввести нашу переменную с явным возвращаемым типом:

const script: HTMLScriptElement = document.getElementsByName(id).item(0);

Или подтвердите как (требуется с TSX):

const script = document.getElementsByName(id).item(0) as HTMLScriptElement;

Или в более простых случаях утверждение с синтаксисом угловой скобки.


Утверждение типа похоже на приведение типа в других языках, но не выполняет специальной проверки или реструктуризации данных. Он не влияет на время выполнения и используется исключительно компилятором.

Документация:

TypeScript - основные типы - утверждения типа

person Leo    schedule 25.09.2018

Чтобы уточнить, это правильно.

Невозможно преобразовать "NodeList" в "HTMLScriptElement []"

поскольку NodeList не является фактическим массивом (например, он не содержит .forEach, .slice, .push и т. д. ...).

Таким образом, если бы он действительно преобразовался в HTMLScriptElement[] в системе типов, вы бы не получили ошибок типа, если бы попытались вызвать Array.prototype членов на нем во время компиляции, но во время выполнения он потерпит неудачу.

person Bill Ticehurst    schedule 02.10.2012
comment
при условии, что это правильно, но не совсем полезно. альтернатива - использовать любой, который не обеспечивает никакой полезной проверки типов ... - person Spongman; 02.10.2012

Кажется, это решает проблему, используя тип доступа к массиву [index: TYPE], ура.

interface ScriptNodeList extends NodeList {
    [index: number]: HTMLScriptElement;
}

var script = ( <ScriptNodeList>document.getElementsByName('foo') )[0];
person Tobias Cudnik    schedule 05.10.2012

Вместо того, чтобы использовать утверждение типа, защиту типа или any для обхода проблемы, более элегантным решением было бы использование generics для указания типа выбираемого элемента.

К сожалению, getElementsByName не является общим, но querySelector и querySelectorAll являются общими. (querySelector и querySelectorAll также гораздо более гибкие и поэтому могут быть предпочтительнее в большинстве случаев.)

Если вы передадите только имя тега в querySelector или querySelectorAll, оно будет автоматически набрано правильно благодаря следующей строке в lib.dom.d.ts:

querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;

Например, чтобы выбрать первый тег скрипта на странице, как в вашем вопросе, вы можете:

const script = document.querySelector('script')!;

Вот и все - TypeScript теперь может сделать вывод, что script теперь HTMLScriptElement.

Используйте querySelector, когда вам нужно выбрать один элемент. Если вам нужно выбрать несколько элементов, используйте querySelectorAll. Например:

document.querySelectorAll('script')

приводит к типу NodeListOf<HTMLScriptElement>.

Если вам нужен более сложный селектор, вы можете передать параметр типа, чтобы указать тип элемента, который вы собираетесь выбрать. Например:

const ageInput = document.querySelector<HTMLInputElement>('form input[name="age"]')!;

приводит к тому, что ageInput набирается как HTMLInputElement.

person CertainPerformance    schedule 11.08.2020

Может быть решено в файле декларации (lib.d.ts), если TypeScript определит HTMLCollection вместо NodeList в качестве возвращаемого типа.

DOM4 также указывает это как правильный тип возвращаемого значения, но более старые спецификации DOM менее ясны.

См. Также http://typescript.codeplex.com/workitem/252

person Peter    schedule 08.11.2012

Я также порекомендовал бы путеводители по сайту

https://www.sitepen.com/blog/2013/12/31/definitive-guide-to-typescript/ (см. Ниже) и https://www.sitepen.com/blog/2014/08/22/advanced-typescript-concept-classes-types/

TypeScript также позволяет указывать различные типы возвращаемых значений, когда в качестве аргумента функции предоставляется точная строка. Например, объявление окружения TypeScript для метода createElement модели DOM выглядит следующим образом:

createElement(tagName: 'a'): HTMLAnchorElement;
createElement(tagName: 'abbr'): HTMLElement;
createElement(tagName: 'address'): HTMLElement;
createElement(tagName: 'area'): HTMLAreaElement;
// ... etc.
createElement(tagName: string): HTMLElement;

Это означает, что в TypeScript, когда вы вызываете, например, document.createElement ('video'), TypeScript знает, что возвращаемое значение является HTMLVideoElement, и сможет гарантировать правильное взаимодействие с DOM Video API без необходимости вводить assert.

person sebilasse    schedule 25.08.2015

Поскольку это NodeList, а не Array, вам не следует использовать скобки или приводить к Array. Свойственный способ получить первый узел:

document.getElementsByName(id).item(0)

Вы можете просто использовать это:

var script = <HTMLScriptElement> document.getElementsByName(id).item(0)

Или продлите NodeList:

interface HTMLScriptElementNodeList extends NodeList
{
    item(index: number): HTMLScriptElement;
}
var scripts = <HTMLScriptElementNodeList> document.getElementsByName('script'),
    script = scripts.item(0);
person Mike Keesey    schedule 19.12.2013
comment
ОБНОВЛЕНИЕ Теперь кастинг выглядит так: const script = document.getElementsByName(id).item(0) as HTMLScriptElement; - person Mike Keesey; 11.05.2017
comment
То есть так выглядит для TS 2.3. - person markeissler; 11.05.2017

Как расширение CertainPerformance > ответьте, если вы используете слияние деклараций для увеличения интерфейс библиотеки стандартного определения Document, вы можете добавить универсальное переопределение для getElementsByName метода (или для любого другого в этом отношении) с параметром по умолчанию, установленным на HTMLElement, чтобы имитировать поведение неуниверсальной версии, когда аргумент типа не указан явно :

interface Document
  extends Node,
    DocumentAndElementEventHandlers,
    DocumentOrShadowRoot,
    GlobalEventHandlers,
    NonElementParentNode,
    ParentNode,
    XPathEvaluatorBase {
  getElementsByName<T extends HTMLElement>(elementName: string) : NodeListOf<T>;
}

Затем в пользовательском коде вы можете явно передать желаемый тип:

const scripts = document.getElementsByName<HTMLScriptElement>("name"); //NodeListOf<HTMLScriptElement>

площадка


Обратите внимание, что вам нужно повторно указать список extends, потому что можно объединить только идентичные объявления.

person Oleg Valter    schedule 15.05.2021