属性'值'类型为'HTMLElement'

我玩周围的typescript,并试图创建一个脚本,将更新一个p元素作为文本输入在一个输入框。

html看起来如下所示:

<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>

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);
}

当我用tsc编译时,我得到以下“错误”:

/home/bjarkef/sandbox/greeter.ts(8,53): The property 'value' does not exist on value of type 'HTMLElement'

然而,编译器会输出一个javascript文件,这在chrome中工作得很好。

我怎么得到这个错误?我该怎么解决呢?

另外,根据typescript,我可以在哪里查找'HTMLElement'上有效的属性?

请注意,我对javascript和typescript很陌生,所以我可能会遗漏一些明显的东西。:)

438418 次浏览

问题就在这里:

document.getElementById(elementId).value

你知道从getElementById()返回的HTMLElement实际上是从它继承的HTMLInputElement实例,因为你传递的是输入元素的ID。类似地,在静态类型的Java中,这不会编译:

public Object foo() {
return 42;
}


foo().signum();

signum()Integer的一个方法,但是编译器只知道foo()的静态类型,即Object。而且Object没有signum()方法。

但是编译器不能知道这一点,它只能基于静态类型,而不是代码的动态行为。而且据编译器所知,document.getElementById(elementId)表达式的类型没有value属性。只有输入元素才有价值。

对于MDN中的HTMLElementHTMLInputElement的参考检查。我猜Typescript或多或少和这些是一致的。

一个快速的修复方法是使用[]来选择属性。

function greet(elementId) {
var inputValue = document.getElementById(elementId)["value"];
if(inputValue.trim() == "") {
inputValue = "World";
}
document.getElementById("greet").innerText = greeter(inputValue);
}
我只是尝试了一些方法,找到了这个解决方案,

.我不知道你原来的剧本有什么问题

你可以参考Tomasz Nurkiewicz的文章。

尝试将你想要更新的元素转换为HTMLInputElement。正如在其他答案中所述,你需要提示编译器,这是一个特定类型的HTMLElement:

var inputElement = <HTMLInputElement>document.getElementById('greet');
inputElement.value = greeter(inputValue);

根据Tomasz nurkiewicz的回答,“问题”;typescript是类型安全的。:)所以document.getElementById()返回类型HTMLElement,不包含value属性。然而,子类型HTMLInputElement确实包含value属性。

因此,一个解决方案是将getElementById()的结果强制转换为HTMLInputElement,如下所示:

var inputValue = (<HTMLInputElement>document.getElementById(elementId)).value;

<>是typescript中的强制转换操作符。参见问题TypeScript:转换HTMLElement

如果你在.tsx文件中,上面的强制转换语法会抛出一个错误。你会想要使用下面的语法:

(document.getElementById(elementId) as HTMLInputElement).value

上面这行代码的结果是这样的:

inputValue = (document.getElementById(elementId)).value;

即不包含类型信息。

问题:

属性“text”在类型“HTMLElement”上不存在

解决方案:在typeScript中,我们需要强制转换document.getElementById(),它在< HTMLScriptElement >中返回类型HTMLElement

因此,我们可以通过以下方法来解决typescript.js所期望的错误

Code: var content: string = ( < HTMLScriptElement > document.getElementById(contentId)).text;

这对我很管用。希望这对你也有用。

如果你正在使用react,你可以使用as操作符。

let inputValue = (document.getElementById(elementId) as HTMLInputElement).value;

同样对于任何人使用属性,如Props或Refs没有你的“DocgetId’s”,那么你可以:

("" as HTMLInputElement).value;

这里的倒引号是你的props值,所以一个例子是这样的:

var val = (this.refs.newText as HTMLInputElement).value;
alert("Saving this:" + val);

如果你使用的是angular,你可以使用-

const element = document.getElementById('elemId') as HTMLInputElement;

这对我来说很有用:

let inputValue = (swal.getPopup().querySelector('#inputValue ')as HTMLInputElement).value

如果你有动态元素ID,你需要分配动态值,你可以使用这个:

//element_id = you dynamic id.
//dynamic_val = you dynamic value.
let _el = document.getElementById(element_id);
_el.value = dynamic_val.toString();

这对我很有用。

有一种方法可以实现这个没有类型的断言,即使用泛型来代替,它通常使用起来更好、更安全。

不幸的是,getElementById不是泛型的,但querySelector是:

const inputValue = document.querySelector<HTMLInputElement>('#greet')!.value;

类似地,你可以使用querySelectorAll来选择多个元素,并使用泛型,这样TS就可以理解所有选择的元素都是特定类型的:

const inputs = document.querySelectorAll<HTMLInputElement>('.my-input');

这将产生一个NodeListOf<HTMLInputElement>

对于那些可能仍在挣扎的人来说,另一种选择是使用(document.getElementById(elementId) as HTMLInputElement).value = ''

如果你仍然面临问题,然后尝试将其提取为如下函数:

function myInput() {
(document.getElementById(elementId) as HTMLInputElement).value = ''
}

这可能看起来很老套,但这对我来说很管用:

yarn add -D @types/web

我想我一定是有一个过时的类型定义。

我一直有一个类似的问题(JS文件中的TS警告:“属性X在类型X上不存在”:是否有可能编写更干净的JavaScript?)

虽然标记帮助删除了typescript文件中的警告,但在编译后,JavaScript文件中仍然会出现警告。

那么,我如何编写代码,这是干净的,并允许我操作。value ?

这花了我不少时间,但我用另一种方法找到了解决方法:

HTML代码:

<form id="my-form"
action="index.html"
method="get"
onsubmit="return showValue();">
<input type="text" name="username">
<input type="text" name="full-name">
<input type="password" name="password">
<button type="button" onclick="showValue();">Show value</button>
</form>

Javascript代码:

function showValue() {
const myForm = document.forms.my-form;
console.log(myForm?.username.value);
return 1;
}

document.forms.x有一个属性"value",它可以删除typescript文件和生成的JavaScript中的警告。

const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
// a.value is valid here
console.log(a.value)
}

更安全的方法

上面的代码片段是答案的要点;继续阅读推理。

大多数现有的答案建议使用类型的断言(类型转换),它可以完成这项工作,但有点像使用__abc0 -它禁用类型检查。有更好更安全的方法。

类型断言就像告诉TypeScript假装变量是我们所说的类型。因此,TypeScript将执行类型检查对于这种类型。如果我们犯了一个错误,告诉它错误的类型,我们将得到一种错误的安全感,因为不会有编译警告,但在运行时将会有错误。让我们来看一个例子:

// <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

我们已经告诉TypeScript ab是HTMLInputElement类型,TypeScript也会这样对待它们。然而,由于b是HTMLDivElement类型,没有value属性,所以b.value返回undefined

使用类型保护的类型缩小

更好的方法是使用类型保护,它允许更大的控制。

保护类型是在运行时确定变量类型的一种方法。虽然类型断言只是说:“变量x是类型T”,但类型守卫说:“变量x是类型T,如果它具有这些属性”。让我们快速看看类型守卫是什么样子的:

const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
// a == input element
} else {
// a != input element
}

这个类型保护只是检查a是否是HTMLInputElement的实例。如果是,TypeScript会识别它,并将If块内的a视为该类型——它将允许访问输入元素的所有属性。这称为类型窄化。

为什么应该使用类型保护而不是类型断言?与处理错误的原因相同。虽然类型保护仍然不会在编译时输出警告,但它们使您处于控制之中。你(和TypeScript)不能保证value属性是否存在,但通过类型断言,你可以决定在它不存在的情况下做什么。类型断言就像忽略错误,而类型保护就像处理错误。

如何使用类型保护

我们将展示三种解决“财产不存在”的方法。错误。每个示例都使用相同的HTML,但每个示例都单独包含,因此更容易阅读。

  1. 类型的断言
// <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
  1. 内联类型保护
// <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)
}

bc没有记录任何内容,因为它们不是输入元素。请注意,在“类型断言”中,我们没有得到任何意外的行为;的例子。

请注意,这是一个虚构的示例,通常您将处理类型不是您所期望的类型的情况。如果类型不匹配,我经常抛出,如下所示:

if (!(b instanceof HTMLInputElement)) {
throw new Error("b is not an input element")
}
// b == input element (for the rest of this block)
  1. 函数类型保护

这个例子稍微高级一些,对于这个用例来说有点不必要。然而,它展示了功能类型保护和更“灵活”;保护类型。

函数类型保护是确定给定值是否为特定类型的函数。它们通过返回bool值来实现。为了让TypeScript能够理解你在创建一个类型保护,你必须使用一个类型谓词(参见下面示例中的注释)。

// <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"
}

请注意,由于我们的类型守卫只检查&;value&;属性,它也可以用于任何其他对象(不仅仅是元素)。

我的原始代码:

<ion-input
type="number"
value=\{\{propertyValue}}
(change)="propertyValue = $event.target.value"
></ion-input>

使我的编辑器显示以下错误:

Property 'value' does not exist on type 'EventTarget'. ngtsc(2339)

不幸的是,我发现在模板中解决这个问题的唯一方法是使用$any()

我同意这里touchmarine回答,他主张使用保护类型而不是类型断言。

我想在回调中直接使用valuevalue被输入为string | number,这可能是正确的,但是this.propertyValuenumber,所以我仍然不能直接设置它:

(property) Components.IonInput["value"]?: string | number

在前面提到的答案中使用instanceof,会出现这样的number错误:

TS2693: 'number' only refers to a type, but is being used as a value here.

对于这样的Number错误(注意大写N):

TS2322: Type 'Number' is not assignable to type 'number'.

解决方案

  • 不建议使用强制类型
  • 我们需要一个回调函数,因为模板中不支持
  • #id选择器是目前Angular中从元素中获取值的首选方式。它能完成大部分的输入。
  • instanceof对原语不起作用,所以我们使用typeof
<ion-input
type="number"
value=\{\{propertyValue}}
#propertyValueInput
(change)="setPropertyValue(propertyValueInput.value)"
></ion-input>
public setPropertyValue (value) {
if (typeof value === 'number') {this.propertyValue = value}
}

终于!我们是类型安全的,没有错误!🎉🎉🎉

附注:这有一个额外的好处,如果输入的不是数字,就忽略用户输入。例如,在浏览器中,我也可以在输入字段中输入字母。

更多信息供参考:为什么'instanceof'在TypeScript中给我一个错误"'Foo'仅指类型,但在这里被用作值。