typescript - Vlastnosť 'value' neexistuje na hodnote typu 'HTMLElement'
Hrávam sa s TypeScriptom a snažím sa vytvoriť skript, ktorý bude aktualizovať prvok p, keď sa zadáva text do vstupného poľa.
HTML vyzerá nasledovne:
<html>
<head>
</head>
<body>
<p id="greet"></p>
<form>
<input id="name" type="text" name="name" value="" onkeyup="greet('name')" />
</form>
</body>
<script src="greeter.js"></script>
</html>
A súbor greeter.ts:
function greeter(person)
{
return "Hello, " + person;
}
function greet(elementId)
{
var inputValue = document.getElementById(elementId).value;
if (inputValue.trim() == "")
inputValue = "World";
document.getElementById("greet").innerText = greeter(inputValue);
}
Keď kompilujem s tsc, dostanem nasledujúcu "chybu":
/home/bjarkef/sandbox/greeter.ts(8,53): The property 'value' does not exist on value of type 'HTMLElement'Avšak kompilátor naozaj vytvorí súbor JavaScript, ktorý funguje úplne dobre v Chrome.
Prečo dostávam túto chybu? A ako ju môžem opraviť?
Tiež, kde si môžem pozrieť, ktoré vlastnosti sú platné pre 'HTMLElement' podľa TypeScriptu?
Vezmite prosím na vedomie, že som v JavaScripte a TypeScripte veľmi nový, takže mi môže niečo zrejmé ujsť. :)
Odpoveď č. 1
Na základe odpovede Tomasza Nurkiewicza je „problém“ v tom, že TypeScript je typovo bezpečný. :) Takže document.getElementById() vracia typ HTMLElement, ktorý neobsahuje vlastnosť value. Podtyp HTMLInputElement však vlastnosť value obsahuje.
Riešením je preto pretypovať výsledok getElementById() na HTMLInputElement takto:
var inputValue = (<HTMLInputElement>document.getElementById(elementId)).value;
<> je operátor pretypovania v TypeScripte. Pozri otázku TypeScript: pretypovanie HTMLElement.
Ak ste v súbore .tsx, vyššie uvedená syntax pre typovanie vyvolá chybu. Namiesto toho budete chcieť použiť túto syntax:
(document.getElementById(elementId) as HTMLInputElement).value
Výsledný javascript z vyššie uvedeného riadku vyzerá takto:
inputValue = (document.getElementById(elementId)).value;
t. j. neobsahuje žiadne informácie o type.
Odpoveď č. 2
Ak používate React, môžete použiť operátor as.
let inputValue = (document.getElementById(elementId) as HTMLInputElement).value;
Odpoveď č. 3
Mohli by sme tvrdiť
const inputElement: HTMLInputElement = document.getElementById('greet')
Alebo s as-syntaxou
const inputElement = document.getElementById('greet') as HTMLInputElement
Dávanie
const inputValue = inputElement.value // now inferred to be string
Odpoveď č. 4
Skúste typovo pretypovať prvok, ktorý chcete aktualizovať, na HTMLInputElement. Ako sa uvádza v ostatných odpovediach, musíte kompilátoru naznačiť, že ide o konkrétny typ HTMLElementu:
var inputElement = <HTMLInputElement>document.getElementById('greet');
inputElement.value = greeter(inputValue);
Odpoveď č. 5
Rýchle riešenie tohto je použiť [ ] na výber atribútu.
function greet(elementId) {
var inputValue = document.getElementById(elementId)["value"];
if(inputValue.trim() == "") {
inputValue = "World";
}
document.getElementById("greet").innerText = greeter(inputValue);
}
Len som vyskúšal niekoľko metód a našiel toto riešenie,
Neviem, aký je problém za vaším pôvodným skriptom.
Pre referenciu sa môžete odkázať na príspevok Tomasza Nurkiewicza.
Odpoveď č. 6
Problém je tu:
document.getElementById(elementId).value
Viete, že HTMLElement vrátený z getElementById() je v skutočnosti inštancia HTMLInputElement, ktorá z neho dedí, pretože predávate ID vstupného prvku. Podobne, v staticky typovanom Jave sa to nevie zostaviť:
public Object foo() {
return 42;
}
foo().signum();
signum() je metóda triedy Integer, ale kompilátor pozná iba statický typ foo(), ktorým je Object. A Object nemá metódu signum().
Ale kompilátor to nemôže vedieť, môže sa opierať iba o statické typy, nie o dynamické správanie vášho kódu. A pokiaľ kompilátor vie, typ výrazu document.getElementById(elementId) nemá vlastnosť value. Len vstupné prvky majú value.
Pre kontrolu odkazov pozri HTMLElement a HTMLInputElement na MDN. Myslím, že Typescript je s nimi viac-menej konzistentný.
Odpoveď č. 7
Pre tých, ktorí s tým môžu stále zápasiť, ďalšia alternatíva je použitie (document.getElementById(elementId) as HTMLInputElement).value=''. zdroj.
Ak sa s tým stále stretávate s problémami, skúste to potom extrahovať do funkcie ako:
function myInput() {
(document.getElementById(elementId) as HTMLInputElement).value = ''
}
Odpoveď č. 8
Tiež pre kohokoľvek, kto používa vlastnosti ako Props alebo Refs bez vašich „DocgetId“, môžete:
("" as HTMLInputElement).value;
Kde sú uvedené úvodzovky, je hodnota vašej vlastnosti, takže príklad by bol nasledovný:
var val = (this.refs.newText as HTMLInputElement).value;
alert("Saving this:" + val);
Odpoveď č. 9
const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
// a.value is valid here
console.log(a.value)
}
Bezpečnejší spôsob
Uvedený úryvok kódu je jadro odpovede; pokračujte v čítaní pre zdôvodnenie.
Väčšina existujúcich odpovedí odporúčala tvrdenia typu (typové konverzie), ktoré plnia úlohu, ale sú trochu ako používanie any — čo vypína kontrolu typov. Existuje lepší, bezpečnejší spôsob.
Assercie typov sú ako povedať TypeScriptu, aby predstieral, že premenná je typu, ktorý sme uviedli. TypeScript teda vykoná kontrolu typu pre tento typ. Ak sme urobili chybu a povedali mu nesprávny typ, získame falošný pocit istoty, pretože nebudú žiadne varovania pri kompilácii, ale nastanú chyby počas behu programu. Pozrime sa na príklad:
// <input id="a" value="1">
// <div id="b" value="2"></div>
const a = document.getElementById("a") as HTMLInputElement
const b = document.getElementById("b") as HTMLInputElement
console.log(a.value) // 1
console.log(b.value) // undefined
Povedali sme TypeScriptu, že a a b sú typu HTMLInputElement a bude s nimi zaobchádzať ako s takými. Avšak, keďže b je typu HTMLDivElement, ktorý nemá vlastnosť value, b.value vráti undefined.
Zuzenie typu pomocou strážcov typu
Lepší spôsob je používanie typových stráží, ktoré umožňujú väčšiu kontrolu.
Typová ochrana je spôsob, ako zistiť typ premennej počas behu programu. Kým typové tvrdenia jednoducho hovoria: „premenná x je typu T“, typové ochrany hovoria: „premenná x je typu T, ak má tieto vlastnosti“. Poďme rýchlo pozrieť, ako vyzerá typová ochrana:
const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
// a == input element
} else {
// a != input element
}
Tento typ ochrany jednoducho skontroluje, či je a inštanciou HTMLInputElement. Ak áno, TypeScript to rozpozná a bude sa na a vo vnútri if bloku pozerať ako na tento typ - umožní prístup ku všetkým vlastnostiam vstupného elementu. Toto sa nazýva zužovanie typu.
Prečo by ste mali používať ochrany typov namiesto asercie typov? Z rovnakého dôvodu, prečo riešite chyby. Hoci ochrany typov stále nevygenerujú varovania počas kompilácie, dávajú vám kontrolu. Vy (a TypeScript) nemôžete zaručiť, či vlastnosť value existuje, ale s aserciami typov sa môžete rozhodnúť, čo urobiť v prípade, že neexistuje. Asercie typov sú ako ignorovanie chýb a ochrany typov sú ako ich riešenie.
Ako používať typové stráže
Ukážeme tri spôsoby, ako opraviť chybu „Vlastnosť neexistuje“. Každý príklad používa rovnaký HTML, ale je zahrnutý samostatne v každom príklade, aby sa to ľahšie čítalo.
- Verenia typov
// <input id="a" value="1">
// <div id="b" value="2">
const a = document.getElementById("a") as HTMLInputElement // correct type assertion
const b = document.getElementById("b") as HTMLInputElement // incorrect type assertion
const c = document.getElementById("c") as HTMLInputElement // element doesn't exist
console.log(a.value) // 1
console.log(b.value) // undefined
console.log(c.value) // Uncaught TypeError: Cannot read property 'value' of null
- Inline typové stráže
// <input id="a" value="1">
// <div id="b" value="2">
const a = document.getElementById("a")
const b = document.getElementById("b")
const c = document.getElementById("c")
if (a instanceof HTMLInputElement) {
console.log(a.value) // 1
}
if (b instanceof HTMLInputElement) {
console.log(b.value)
}
if (c instanceof HTMLInputElement) {
console.log(c.value)
}
b a c nezaznamenali nič, pretože nie sú vstupnými prvkami. Všimnite si, že sme nezaznamenali žiadne neočakávané správanie ako v príkladoch „typových tvrdení“.
Všimnite si, že toto je umelo vytvorený príklad, zvyčajne by ste riešili prípady, keď typy nie sú také, aké ste očakávali. Často vyhodím výnimku, ak typ nesedí, napríklad takto:
if (!(b instanceof HTMLInputElement)) {
throw new Error("b is not an input element")
}
// b == input element (for the rest of this block)
- Ochranné typy funkcií
Tento príklad je o niečo pokročilejší a pre tento prípad použitia trochu zbytočný. Napriek tomu však ukazuje ochrany typov funkcií a „flexibilnejšiu“ ochranu typov.
Funkčné typové stráže sú funkcie, ktoré určujú, či daná hodnota je určitého typu. Robia to jednoducho vrátením bool hodnoty. Aby TypeScript mohol pochopiť, že vytvárate typovú stráž, musíte použiť typový predikát (pozri komentár v príklade nižšie).
// <input id="a" value="1">
// <div id="b" value="2">
const a = document.getElementById("a")
const b = document.getElementById("b")
const c = document.getElementById("c")
if (hasValueProperty(a)) {
console.log(a.value) // 1
}
if (hasValueProperty(b)) {
console.log(b.value)
}
if (hasValueProperty(c)) {
console.log(c.value)
}
const d = {
"value": "d",
}
if (hasValueProperty(d)) {
console.log(d.value) // d
}
type WithValue = {
value: string
}
// hasValueProperty is a type guard function the determines whether x is of type
// WithValue.
//
// "x is WithValue" is a type predicate that tells TypeScript that this is a
// type guard.
function hasValueProperty(x: unknown): x is WithValue {
return typeof x === "object" && x !== null && typeof (x as WithValue).value === "string"
}
Všimnite si, že keďže naša ochrana typu kontroluje iba prítomnosť vlastnosti "value", môže byť použitá aj pre akýkoľvek iný objekt (nielen pre prvky).
Odpoveď č. 10
Existuje spôsob, ako to dosiahnuť bez typového tvrdenia, použitím generík namiesto toho, ktoré sú všeobecne o niečo príjemnejšie a bezpečnejšie na používanie.
Bohužiaľ, getElementById nie je generický, ale querySelector je:
const inputValue = document.querySelector<HTMLInputElement>('#greet')!.value;
Rovnako môžete použiť querySelectorAll na výber viacerých prvkov a použiť generiká, aby TypeScript rozumel, že všetky vybrané prvky sú konkrétneho typu:
const inputs = document.querySelectorAll<HTMLInputElement>('.my-input');
Toto vytvorí NodeListOf<HTMLInputElement>.
