Skip to content

Commit

Permalink
init translation of dom manipulation (#77)
Browse files Browse the repository at this point in the history
* init translation of dom manipulation

* Apply suggestions from code review

Co-authored-by: Enoch Gao <[email protected]>

* Apply suggestions from code review

---------

Co-authored-by: Xavi Lee <[email protected]>
Co-authored-by: Enoch Gao <[email protected]>
  • Loading branch information
3 people authored Jun 1, 2024
1 parent a663375 commit ab584cd
Showing 1 changed file with 55 additions and 55 deletions.
110 changes: 55 additions & 55 deletions docs/documentation/zh/tutorials/DOM Manipulation.md
Original file line number Diff line number Diff line change
@@ -1,93 +1,93 @@
---
title: DOM Manipulation
title: DOM 的操作
layout: docs
permalink: /zh/docs/handbook/dom-manipulation.html
oneline: Using the DOM with TypeScript
oneline: 使用 TypeScript 操作 DOM
translatable: true
---

## DOM Manipulation
## DOM 的操作

### _An exploration into the `HTMLElement` type_
### _探索 `HTMLElement` 类型_

In the 20+ years since its standardization, JavaScript has come a very long way. While in 2020, JavaScript can be used on servers, in data science, and even on IoT devices, it is important to remember its most popular use case: web browsers.
自从标准化以来的 20 多年里,JavaScript 已经取得了长足的进步。尽管在 2020 年,JavaScript 可以在服务器、数据科学甚至物联网设备上使用,但重要的是要记住它最广泛的应用场景:web 浏览器。

Websites are made up of HTML and/or XML documents. These documents are static, they do not change. The *Document Object Model (DOM)* is a programming interface implemented by browsers to make static websites functional. The DOM API can be used to change the document structure, style, and content. The API is so powerful that countless frontend frameworks (jQuery, React, Angular, etc.) have been developed around it to make dynamic websites even easier to develop.
网站由 HTML 和/或 XML 文档组成。这些文档是静态的,不会改变。*文档对象模型(DOM*是浏览器实现的一个编程接口,用于使静态网站变得可操作。DOM API 可用于更改文档结构、样式和内容。该 API 十分强大,强大到无数前端框架(jQueryReact 以及 Angular 等)都是围绕它开发的,其使得动态网站的开发变得更加容易。

TypeScript is a typed superset of JavaScript, and it ships type definitions for the DOM API. These definitions are readily available in any default TypeScript project. Of the 20,000+ lines of definitions in _lib.dom.d.ts_, one stands out among the rest: `HTMLElement`. This type is the backbone for DOM manipulation with TypeScript.
TypeScript JavaScript 的一个类型超集,并且它提供了 DOM API 的类型定义。这些定义在任何默认的 TypeScript 项目中都可以使用。在 _lib.dom.d.ts_ 文件的两万多行定义中,有一个脱颖而出:`HTMLElement`。这个类型是使用 TypeScript 进行 DOM 操作的关键。

> You can explore the source code for the [DOM type definitions](https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts)
> 你可以探索 [DOM 类型定义](https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts)的源代码
## Basic Example
## 基本示例

Given a simplified _index.html_ file:
给定一个简化的 _index.html_ 文件:

```html
<!DOCTYPE html>
<html lang="en">
<head><title>TypeScript Dom Manipulation</title></head>
<html lang="zh-CN">
<head><title>TypeScript DOM 操作</title></head>
<body>
<div id="app"></div>
<!-- Assume index.js is the compiled output of index.ts -->
<!-- 假设 index.js index.ts 的编译输出 -->
<script src="index.js"></script>
</body>
</html>
```

Let's explore a TypeScript script that adds a `<p>Hello, World!</p>` element to the `#app` element.
让我们探讨一个使用 TypeScript 编写的脚本,该脚本将 `<p>Hello, World!</p>` 元素添加到了 `#app` 元素中。

```ts
// 1. Select the div element using the id property
// 1. 使用 id 属性选择 div 元素
const app = document.getElementById("app");

// 2. Create a new <p></p> element programmatically
// 2.以编程方式创建一个新的 <p></p> 元素
const p = document.createElement("p");

// 3. Add the text content
// 3. 添加文本内容
p.textContent = "Hello, World!";

// 4. Append the p element to the div element
// 4. 将 p 元素附加到 div 元素中
app?.appendChild(p);
```

After compiling and running the _index.html_ page, the resulting HTML will be:
编译并运行 _index.html_ 页面后,生成的 HTML 将为:

```html
<div id="app">
<p>Hello, World!</p>
</div>
```

## The `Document` Interface
## `Document` 接口

The first line of the TypeScript code uses a global variable `document`. Inspecting the variable shows it is defined by the `Document` interface from the _lib.dom.d.ts_ file. The code snippet contains calls to two methods, `getElementById` and `createElement`.
TypeScript 代码的第一行使用了全局变量 `document`。检查这个变量显示它是由 _lib.dom.d.ts_ 文件中的 `Document` 接口定义的。代码片段包含对两个方法的调用:`getElementById` `createElement`

### `Document.getElementById`

The definition for this method is as follows:
该方法的定义如下:

```ts
getElementById(elementId: string): HTMLElement | null;
```

Pass it an element id string and it will return either `HTMLElement` or `null`. This method introduces one of the most important types, `HTMLElement`. It serves as the base interface for every other element interface. For example, the `p` variable in the code example is of type `HTMLParagraphElement`. Also, take note that this method can return `null`. This is because the method can't be certain pre-runtime if it will be able to actually find the specified element or not. In the last line of the code snippet, the new _optional chaining_ operator is used to call `appendChild`.
向它传递一个元素 id 字符串,它将返回 `HTMLElement` `null`。这个方法引入了最重要的类型之一:`HTMLElement`。它是每个其他元素接口的基础接口。例如,代码示例中的 `p` 变量的类型为 `HTMLParagraphElement`。另外,请注意这个方法可能返回 `null`。这是因为该方法在运行时无法确定是否能找到指定的元素。在代码片段的最后一行,我们使用了新的*可选链接(optional chaining)*运算符来调用 `appendChild`

### `Document.createElement`

The definition for this method is (I have omitted the _deprecated_ definition):
这个方法的定义如下(我省略了*已弃用*的定义):

```ts
createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];
createElement(tagName: string, options?: ElementCreationOptions): HTMLElement;
```
This is an overloaded function definition. The second overload is simplest and works a lot like the `getElementById` method does. Pass it any `string` and it will return a standard HTMLElement. This definition is what enables developers to create unique HTML element tags.
这是一个重载的函数定义。第二个重载是最简单的,与 `getElementById` 方法的工作方式很相似。向它传递任何 `string`,它将返回标准的 HTMLElement。这个定义使开发者能够创建独特的 HTML 元素标签。
For example `document.createElement('xyz')` returns a `<xyz></xyz>` element, clearly not an element that is specified by the HTML specification.
例如,`document.createElement('xyz')` 返回一个 `<xyz></xyz>` 元素,显然这不是 HTML 规范中指定的元素。
> For those interested, you can interact with custom tag elements using the `document.getElementsByTagName`
> 对于感兴趣的人来说,你可以使用 `document.getElementsByTagName` 与自定义标签元素进行交互。
For the first definition of `createElement`, it is using some advanced generic patterns. It is best understood broken down into chunks, starting with the generic expression: `<K extends keyof HTMLElementTagNameMap>`. This expression defines a generic parameter `K` that is _constrained_ to the keys of the interface `HTMLElementTagNameMap`. The map interface contains every specified HTML tag name and its corresponding type interface. For example here are the first 5 mapped values:
对于 `createElement` 的第一个定义,它使用了一些高级的泛型模式。将其拆解成几小部分来分析会更容易理解,从泛型表达式开始: `<K extends keyof HTMLElementTagNameMap>`。这个表达式定义了一个泛型参数 `K`,它被*限制*为符合 `HTMLElementTagNameMap` 接口的键。这个映射接口包含了每个指定的 HTML 标签名及其对应的类型接口。例如,这里是前 5 个映射值:
```ts
interface HTMLElementTagNameMap {
Expand All @@ -100,27 +100,27 @@ interface HTMLElementTagNameMap {
}
```

Some elements do not exhibit unique properties and so they just return `HTMLElement`, but other types do have unique properties and methods so they return their specific interface (which will extend from or implement `HTMLElement`).
一些元素并没有独特的属性,因此它们只返回 `HTMLElement`,但其他类型确实有独特的属性和方法,所以它们返回特定的接口(这些接口将继承或实现 `HTMLElement`)。

Now, for the remainder of the `createElement` definition: `(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]`. The first argument `tagName` is defined as the generic parameter `K`. The TypeScript interpreter is smart enough to _infer_ the generic parameter from this argument. This means that the developer does not have to specify the generic parameter when using the method; whatever value is passed to the `tagName` argument will be inferred as `K` and thus can be used throughout the remainder of the definition. This is exactly what happens; the return value `HTMLElementTagNameMap[K]` takes the `tagName` argument and uses it to return the corresponding type. This definition is how the `p` variable from the code snippet gets a type of `HTMLParagraphElement`. And if the code was `document.createElement('a')`, then it would be an element of type `HTMLAnchorElement`.
现在,我们探索一下 `createElement` 定义的其余部分:`(tagName: K options?: ElementCreationOptions): HTMLElementTagNameMap[K]`。第一个参数 `tagName` 被定义为泛型参数 `K`TypeScript 解释器足够智能,能够从这个参数*推断*出泛型参数。这意味着开发者在使用这个方法时不必指定泛型参数;传递给 `tagName` 参数的任何值都将被推断为 `K`,因此可以在定义的其余部分中使用。实际情况也正是如此;返回值 `HTMLElementTagNameMap[K]` 获取 `tagName` 参数,并使用它返回相应的类型。这个定义就是代码片段中 `p` 变量获得 `HTMLParagraphElement` 类型的方式。如果代码是 `document.createElement('a')`,那么它将是 `HTMLAnchorElement` 类型的元素。

## The `Node` interface
## `Node` 接口

The `document.getElementById` function returns an `HTMLElement`. `HTMLElement` interface extends the `Element` interface which extends the `Node` interface. This prototypal extension allows for all `HTMLElements` to utilize a subset of standard methods. In the code snippet, we use a property defined on the `Node` interface to append the new `p` element to the website.
`document.getElementById` 函数会返回 `HTMLElement``HTMLElement` 接口继承自 `Element` 接口,而 `Element` 又继承自 `Node` 接口。这种原型扩展允许所有 `HTMLElements` 使用一组标准方法的子集。在代码片段中,我们使用定义在 `Node` 接口上的属性来将新的 `p` 元素附加到网站上。

### `Node.appendChild`

The last line of the code snippet is `app?.appendChild(p)`. The previous, `document.getElementById`, section detailed that the _optional chaining_ operator is used here because `app` can potentially be null at runtime. The `appendChild` method is defined by:
代码片段的最后一行是 `app?.appendChild(p)`。前面的 `document.getElementById` 部分详细介绍了使用*可选链接*运算符的原因,因为 `app` 在运行时可能为 null`appendChild` 方法的定义如下:

```ts
appendChild<T extends Node>(newChild: T): T;
```
This method works similarly to the `createElement` method as the generic parameter `T` is inferred from the `newChild` argument. `T` is _constrained_ to another base interface `Node`.
这个方法的工作方式与 `createElement` 方法类似,因为泛型参数 `T` 是从 `newChild` 参数推断出来的。`T` 被*限制*为符合另一个基础接口 `Node`
## Difference between `children` and `childNodes`
## `children` `childNodes` 的区别
Previously, this document details the `HTMLElement` interface extends from `Element` which extends from `Node`. In the DOM API there is a concept of _children_ elements. For example in the following HTML, the `p` tags are children of the `div` element
之前,本文详细介绍了 `HTMLElement` 接口继承自 `Element`,而 `Element` 又继承自 `Node`。在 DOM API 中,有一个称为*子级(children)*元素的概念。例如,在以下 HTML 中,`p` 标签是 `div` 元素的子级
```tsx
<div>
Expand All @@ -137,9 +137,9 @@ div.childNodes;
// NodeList(2) [p, p]
```

After capturing the `div` element, the `children` prop will return an `HTMLCollection` list containing the `HTMLParagraphElements`. The `childNodes` property will return a similar `NodeList` list of nodes. Each `p` tag will still be of type `HTMLParagraphElements`, but the `NodeList` can contain additional _HTML nodes_ that the `HTMLCollection` list cannot.
在获取 `div` 元素后,`children` 属性将返回一个包含 `HTMLParagraphElements``HTMLCollection` 列表。`childNodes` 属性将返回一个类似的包含节点的 `NodeList` 列表。每个 `p` 标签仍会是 `HTMLParagraphElements` 类型,但 `NodeList` 可以包含 `HTMLCollection` 列表无法包含的其他 *HTML节点*

Modify the HTML by removing one of the `p` tags, but keep the text.
修改 HTML 代码,删除其中一个 `p` 标签,但保留文本内容。

```tsx
<div>
Expand All @@ -156,48 +156,48 @@ div.childNodes;
// NodeList(2) [p, text]
```

See how both lists change. `children` now only contains the `<p>Hello, World</p>` element, and the `childNodes` contains a `text` node rather than two `p` nodes. The `text` part of the `NodeList` is the literal `Node` containing the text `TypeScript!`. The `children` list does not contain this `Node` because it is not considered an `HTMLElement`.
观察两个列表的变化。`children` 现在只包含 `<p>Hello World</p>` 元素,而 `childNodes` 包含一个 `text` 节点,而不是两个 `p` 节点。`NodeList` 中的 `text` 部分是包含 `TypeScript!` 文本的 `Node``children` 列表中不包含这个 `Node`,因为它不被视为 `HTMLElement`

## The `querySelector` and `querySelectorAll` methods
## `querySelector` `querySelectorAll` 方法

Both of these methods are great tools for getting lists of dom elements that fit a more unique set of constraints. They are defined in _lib.dom.d.ts_ as:
这两个方法都是很好的工具,可以获取符合更多独特约束条件的 DOM 元素列表。它们在 _lib.dom.d.ts_ 中定义如下:

```ts
/**
* Returns the first element that is a descendant of node that matches selectors.
* 返回 node 的后代中匹配选择器的第一个元素。
*/
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;

/**
* Returns all element descendants of node that match selectors.
* 返回 node 的所有后代元素中匹配选择器的元素。
*/
querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
```
The `querySelectorAll` definition is similar to `getElementsByTagName`, except it returns a new type: `NodeListOf`. This return type is essentially a custom implementation of the standard JavaScript list element. Arguably, replacing `NodeListOf<E>` with `E[]` would result in a very similar user experience. `NodeListOf` only implements the following properties and methods: `length`, `item(index)`, `forEach((value, key, parent) => void)`, and numeric indexing. Additionally, this method returns a list of _elements_, not _nodes_, which is what `NodeList` was returning from the `.childNodes` method. While this may appear as a discrepancy, take note that interface `Element` extends from `Node`.
`querySelectorAll` 的定义类似于 `getElementsByTagName`,不同之处在于它返回一个新的类型:`NodeListOf`。这个返回类型本质上是标准 JavaScript 列表元素的自定义实现。可以说,将 `NodeListOf<E>` 替换为 `E[]` 将会得到非常相似的用户体验。`NodeListOf` 只实现了以下属性和方法:`length``item(index)``forEach((value key parent) => void)` 和数字索引。此外,这个方法返回*元素*列表,而不是从 `.childNodes` 方法返回的*节点*列表。虽然这可能看起来有些矛盾,但请注意 `Element` 接口继承自 `Node` 接口。
To see these methods in action modify the existing code to:
要查看这些方法的实际应用,我们可以修改现有的代码如下:
```tsx
<ul>
<li>First :)</li>
<li>Second!</li>
<li>Third times a charm.</li>
<li>第一个 :)</li>
<li>第二个!</li>
<li>第三个也不错。</li>
</ul>;

const first = document.querySelector("li"); // returns the first li element
const all = document.querySelectorAll("li"); // returns the list of all li elements
const first = document.querySelector("li"); // 返回第一个 li 元素
const all = document.querySelectorAll("li"); // 返回所有 li 元素的列表
```
## Interested in learning more?
## 想了解更多?
The best part about the _lib.dom.d.ts_ type definitions is that they are reflective of the types annotated in the Mozilla Developer Network (MDN) documentation site. For example, the `HTMLElement` interface is documented by this [HTMLElement page](https://developer.mozilla.org/docs/Web/API/HTMLElement) on MDN. These pages list all available properties, methods, and sometimes even examples. Another great aspect of the pages is that they provide links to the corresponding standard documents. Here is the link to the [W3C Recommendation for HTMLElement](https://www.w3.org/TR/html52/dom.html#htmlelement).
_lib.dom.d.ts_ 类型定义的最大好处就是它们反映了 Mozilla Developer Network (MDN) 文档网站中的类型注释。例如,`HTMLElement` 接口在 MDN 上有专门的 [HTMLElement 页面](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement)进行了记录。这些页面列出了所有可用的属性、方法,有时还会提供示例。这些页面的另一个优点是它们提供了指向相应标准文档的链接。这是 [W3C HTMLElement 的建议](https://www.w3.org/TR/html52/dom.html#htmlelement)
Sources:
参考资料:
- [ECMA-262 Standard](http://www.ecma-international.org/ecma-262/10.0/index.html)
- [Introduction to the DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction)
- [ECMA-262 标准](http://www.ecma-international.org/ecma-262/10.0/index.html)
- [DOM 简介](https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model/Introduction)

0 comments on commit ab584cd

Please sign in to comment.