我玩周围的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>

还有迎宾员。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/ setter .ts(8,53):属性“value”在类型为“HTMLElement”的值上不存在

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

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

此外,我可以在哪里查找哪些属性是有效的“HTMLElement”根据typescript?

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


当前回答

有一种方法可以在不使用类型断言的情况下实现这一点,即使用泛型,泛型通常使用起来更好、更安全。

不幸的是,getElementById不是通用的,但querySelector是:

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

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

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

这将产生一个NodeListOf<HTMLInputElement>。

其他回答

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

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

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

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

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

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

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

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

即不包含类型信息。

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

("" as HTMLInputElement).value;

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

var val = (this.refs.newText as HTMLInputElement).value;
alert("Saving this:" + val);
  const handleMyEvent = (event : React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    
    const input = (event.target as HTMLInputElement).value;
    console.log('handleMyEvent input -> ', input)

  }

这对我来说很有用:

let inputValue = (swal.getPopup().querySelector('#inputValue ')as HTMLInputElement).value
const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
    // a.value is valid here
    console.log(a.value)
}

更安全的方法

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

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

类型断言就像告诉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 a和b是HTMLInputElement类型,它也会这样对待它们。然而,由于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,但每个示例都单独包含,因此更容易阅读。

类型的断言

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

内联类型保护

// <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和c没有记录任何东西,因为它们不是输入元素。请注意,在“类型断言”示例中,我们没有得到任何意想不到的行为。

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

if (!(b instanceof HTMLInputElement)) {
    throw new Error("b is not an input element")
}
// b == input element (for the rest of this block)

函数类型保护

这个例子稍微高级一些,对于这个用例来说有点不必要。但是,它展示了函数类型保护和更“灵活”的类型保护。

函数类型保护是确定给定值是否为特定类型的函数。它们通过返回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”属性的存在,它也可以用于任何其他对象(不仅仅是元素)。