开发者问题收集

为什么 getAttribute 在“Node”类型上不存在?

2023-07-04
1428

我正在将我编写的一些 Javascript 转换为 Deno TypeScript 模块。该脚本使用 deno-dom 从页面中获取所有“a[href]”链接,并返回链接列表。该脚本运行正常,但是,当我使用 deno test 测试代码时,它会报错

“TS2339 [错误]:属性‘getAttribute’在类型‘Node’上不存在”

我的理解是 Element 类型是 Node 类型的“类型”——但我尝试智能地声明这一点失败了。我在这里误解了什么?

这是有问题的完整函数:

import { DOMParser } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";

export async function getAllLinks(
  url: string,
  slice = 5,
): Promise<{ source: string; links: string[] }> {
  try {
    const response = await fetch(url);
    const content: string = await response.text();

    // Parse the HTML content using JSDOM
    const document = new DOMParser().parseFromString(content, "text/html");

    if (document === null) throw new Error("Failed to parse HTML");
    const baseUrl = response.url;

    // Find all links on the page
    const linkSelector = document.querySelectorAll(
      'a[href^="http:"], a[href^="https:"]',
    );

    if (linkSelector === undefined || linkSelector === null){
      throw new Error("No links found");
    }
    const links = Array.from(linkSelector, (link: Element) => link.getAttribute("href"))
      .map((link: string) => {
        // Convert relative links to absolute URLs
        const absoluteURL = new URL(link, baseUrl);
        return absoluteURL.href;
      })
      .filter((link: string) => {
        // Filter out links that are on the same domain as the URL
        const parsedUrl = new URL(url);
        const parsedLink = new URL(link);
        return parsedUrl.hostname !== parsedLink.hostname;
      });

    return {
      source: baseUrl,
      links: links.slice(0, slice),
    };
  } catch (error) {
    console.error(error);
    throw error;
  }
}

谢谢。

编辑:将 (link) 声明为 (link: Element) 会导致 Array.from 中它之前的 linkSelector 抱怨此错误:

No overload matches this call.
  Overload 1 of 4, '(iterable: Iterable<Element> | ArrayLike<Element>, mapfn: (v: Element, k: number) => string | null, thisArg?: any): (string | null)[]', gave the following error.
    Argument of type 'NodeList' is not assignable to parameter of type 'Iterable<Element> | ArrayLike<Element>'.
      Type 'NodeList' is not assignable to type 'ArrayLike<Element>'.
        'number' index signatures are incompatible.
          Type 'Node' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 127 more.  Overload 2 of 4, '(arrayLike: ArrayLike<Element>, mapfn: (v: Element, k: number) => string | null, thisArg?: any): (string | null)[]', gave the following error.
    Argument of type 'NodeList' is not assignable to parameter of type 'ArrayLike<Element>'.deno-ts(2769)

并且还会破坏下一行 (link: string) 声明并出现此抱怨:

argument of type '(link: string) => string' is not assignable to parameter of type '(value: string | null, index: number, array: (string | null)[]) => string'.
  Types of parameters 'link' and 'value' are incompatible.
    Type 'string | null' is not assignable to type 'string'.
      Type 'null' is not assignable to type 'string'.deno-ts(2345)

更令人困惑的是,无论我选择什么类型声明,代码仍然可以完全正常执行。

1个回答

这是 deno_dom 库中的一个未解决的问题,并且它仍然存在(至少在我写这个答案时 - 它的版本是 0.1.38 )。有关更多信息,请参阅以下 GitHub 问题:

b-fuze/deno-dom#4 - 错误:querySelectorAll 返回 NodeListOf<Node>而不是 NodeListOf<ELement>

该线程包含所有信息和链接资源,但这里是摘要:

deno_dom 没有完全或正确地实现 DOM 规范 ,其类型也与 TypeScript 中的 DOM 类型不一致。

在 TS DOM 库中, document.querySelectorAll 的返回类型是 NodeListOf<E> ,其中 E 是一个限制为 Element ,它是实现 getAttribute 方法的接口。

deno_dom 中, document.querySelectorAll 的返回类型为 NodeList ,它是 Node 的可迭代对象(不包含 getAttribute 方法 - 所有 Element 都是 Node ,但反之则不然)。

NodeList 的实际元素确实是 Element ,但类型不同,因此 - 在库修复之前 - 您必须使用 类型断言 来通知编译器情况。这是一个独立的示例:

example.ts

import { assert } from "https://deno.land/[email protected]/testing/asserts.ts";

import {
  DOMParser,
  type Element,
} from "https://deno.land/x/[email protected]/deno-dom-wasm.ts";

const html = `
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Ice cream flavors</title>
</head>
<body>
  <h1>Ice cream</h1>
  <h2>Basic flavors</h2>
  <ul class="flavors basic">
    <li data-flavor="vanilla">Vanilla</li>
    <li data-flavor="chocolate">Chocolate</li>
    <li data-flavor="strawberry">Strawberry</li>
  </ul>
</body>
</html>
`;

const document = new DOMParser().parseFromString(html, "text/html");
//    ^? const document: HTMLDocument | null
assert(document, "The document could not be parsed");

const flavorElements = document.querySelectorAll(
  "ul.flavors.basic > li",
) as Iterable<Element>; /*
  ^^^^^^^^^^^^^^^^^^^^
Use a type assertion here */

const flavors = [...flavorElements].map((element) => {
  const flavor = element.getAttribute("data-flavor");
  //    ^? const flavor: string | null
  assert(flavor, "Flavor attribute not found");
  return flavor;
});

console.log(flavors); // ["vanilla", "chocolate", "strawberry"]

在终端中:

% deno --version
deno 1.35.0 (release, aarch64-apple-darwin)
v8 11.6.189.7
typescript 5.1.6

% deno run --check example.ts
Check file:///Users/deno/example.ts
[ "vanilla", "chocolate", "strawberry" ]

为了进行比较, 这是 TypeScript Playground 中的等效代码

jsejcksn
2023-07-06