为什么 getAttribute 在“Node”类型上不存在?
我正在将我编写的一些 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)
更令人困惑的是,无论我选择什么类型声明,代码仍然可以完全正常执行。
这是
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 中的等效代码 。