---
title: 'web-sys'
description: 'web-sys crate 为 Web API 提供绑定。'
---

import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'

[`web-sys` crate](https://crates.io/crates/web-sys) 为 Web API 提供绑定。这是从浏览器 WebIDL 生成的，这就是为什么有些名称如此之长，有些类型如此模糊的原因。

## `web-sys` 中的特性 (features)

`web-sys` crate 中启用了所有特性可能会给 Wasm 应用程序增加很多冗余。为了解决这个问题，大多数类型都是通过启用 features 进行控制的，这样你只需要包含你的应用程序所需的类型。Yew 启用了 `web-sys` 的几个特性，并在其公共 API 中公开了一些类型。你通常需要自行将 `web-sys` 添加为依赖项。

## `web-sys` 中的继承

在[模拟继承](./wasm-bindgen.mdx#simulating-inheritance)部分，你可以了解到 Rust 通常提供了一种模拟 JavaScript 中继承的方法。这在 `web-sys` 中非常重要，因为了解一个类型上有哪些方法意味着了解它的继承。

这一部分将查看一个特定的元素，并使用 Rust 调用 [`Deref::deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html#tymethod.deref) 列出其继承，直到该值为 [`JsValue`](./wasm-bindgen.mdx#jsvalue)。

```rust
use std::ops::Deref;
use web_sys::{
    Element,
    EventTarget,
    HtmlElement,
    HtmlTextAreaElement,
    Node,
};

fn inheritance_of_text_area(text_area: HtmlTextAreaElement) {
    // HtmlTextAreaElement 是 HTML 中的 <textarea>。
    let html_element: &HtmlElement = text_area.deref();

    let element: &Element = html_element.deref();

    let node: &Node = element.deref();

    let event_target: &EventTarget = node.deref();

    // 注意我们现在已经从 web-sys 类型转移到内置的 JavaScript 类型，
    // 这些类型在 js-sys crate 中。
    let object: &js_sys::Object = event_target.deref();

    // 注意我们现在已经从 js-sys 类型转移到 wasm-bindgen crate 中的根 JsValue。
    let js_value: &wasm_bindgen::JsValue = object.deref();

    // 这样使用 deref 意味着我们必须手动遍历继承树。
    // 但是，您可以在 HtmlTextAreaElement 类型上调用 JsValue 方法。
    assert!(!text_area.is_string());

    // 这个空函数只是为了证明我们可以将 HtmlTextAreaElement 作为 &EventTarget 传递。
    fn this_function_only_takes_event_targets(targets: &EventTarget) {};

    // 编译器将沿着 deref 链向下走，以匹配这里的类型。
    this_function_only_takes_event_targets(&text_area);

    // AsRef 实现允许您将 HtmlTextAreaElement 视为 &EventTarget。
    let event_target: &EventTarget = text_area.as_ref();

}
```

_[`wasm-bindgen` 指引中的 `web-sys` 继承](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/inheritance.html)_

## `NodeRef` 中的 `Node`

Yew 使用 [`NodeRef`](concepts/function-components/node-refs.mdx) 来提供一种方式来保留由 [`html!`](concepts/html/introduction.mdx) 宏创建的 `Node` 的引用。`NodeRef` 中的 `Node` 指的是 [`web_sys::Node`](https://wasm-bindgen.github.io/wasm-bindgen/api/web_sys/struct.Node.html)。`NodeRef::get` 方法将返回一个 `Option<Node>` 值，但是，在 Yew 中，大多数情况下，您希望将此值转换为特定元素，以便使用其特定方法。如果存在，可以使用 [`JsCast`](./wasm-bindgen.mdx#JsCast) 对 `Node` 值进行转换，但是 Yew 提供了 `NodeRef::cast` 方法来执行此转换，以方便使用，因此您不一定需要为 `JsCast` 特性包含 `wasm-bindgen` 依赖项。

下面的两个代码块本质上是相同的，第一个使用 `NodeRef::cast`，第二个使用 [`JsCast::dyn_into`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into) 在 `NodeRef::get` 返回的 `web_sys::Node` 上。

<Tabs>
  <TabItem value="Using NodeRef::cast" label="Using NodeRef::cast">

```rust
use web_sys::HtmlInputElement;
use yew::NodeRef;

fn with_node_ref_cast(node_ref: NodeRef) {
    if let Some(input) = node_ref.cast::<HtmlInputElement>() {
        // 在这里处理 HtmlInputElement
    }
}
```

  </TabItem>
  <TabItem value="Using NodeRef::get" label="Using NodeRef::get">

```rust
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::NodeRef;

fn with_jscast(node_ref: NodeRef) {
    if let Some(input) = node_ref
        .get()
        .and_then(|node| node.dyn_into::<HtmlInputElement>().ok()) {
        // 在这里处理 HtmlInputElement
    }
}
```

  </TabItem>
</Tabs>

## JavaScript 重构为 Rust 的示例

这一节展示了如何将与 Web API 交互的 JavaScript 代码示例重写为 Rust 中的 `web-sys`。

### JavaScript 示例

```js
document.getElementById('mousemoveme').onmousemove = (e) => {
    // e 为鼠标事件对象
    var rect = e.target.getBoundingClientRect()
    var x = e.clientX - rect.left // 元素内的 x 位置。
    var y = e.clientY - rect.top // 元素内的 y 位置。
    console.log('Left? : ' + x + ' ; Top? : ' + y + '.')
}
```

### 用 `web-sys` 重写的示例

仅使用 `web-sys`，上面的 JavaScript 示例可以这样实现：

```toml title=Cargo.toml
[dependencies]
wasm-bindgen = "0.2"

[dependencies.web-sys]
version = "0.3"
# 需要启用所有我们想要使用的 web-sys 特性！
features = [
    "console",
    "Document",
    "HtmlElement",
    "MouseEvent",
    "DomRect",
]
```

```rust ,no_run
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{console, Document, HtmlElement, MouseEvent};

let mousemove = Closure::<dyn Fn(MouseEvent)>::wrap(Box::new(|e| {
    let rect = e
        .target()
        .expect("mouse event doesn't have a target")
        .dyn_into::<HtmlElement>()
        .expect("event target should be of type HtmlElement")
        .get_bounding_client_rect();
    let x = (e.client_x() as f64) - rect.left();
    let y = (e.client_y() as f64) - rect.top();
    console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
}));

Document::new()
    .expect("global document not set")
    .get_element_by_id("mousemoveme")
    .expect("element with id `mousemoveme` not present")
    .unchecked_into::<HtmlElement>()
    .set_onmousemove(mousemove.as_ref().dyn_ref());

// 我们现在需要保存 `mousemove` 闭包，以便在事件触发时闭包仍然在内存中。
```

这个版本更加冗长，但你可能会注意到其中的一部分是由于失败类型提醒我们，一些函数调用有必须保持的不变量，否则将在 Rust 中引发 panic。另一个冗长的部分是调用 `JsCast` 来将不同类型转换为特定类型，以便调用其特定方法。

### 用 Yew 重写的示例

在 Yew 中，您将主要创建 [`Callback`](concepts/function-components/callbacks.mdx) 以在 [`html!`](concepts/html/introduction.mdx) 宏中使用，因此示例将使用这种方法，而不是完全复制上面的方法：

```toml title=Cargo.toml
[dependencies.web-sys]
version = "0.3"
# 我们需要启用 `DomRect` 特性以使用 `get_bounding_client_rect` 方法。
features = [
    "console",
    "HtmlElement",
    "MouseEvent",
    "DomRect",
]

```

```rust
use web_sys::{console, HtmlElement, MouseEvent};
use yew::{
    html,
    Callback, TargetCast,
};

let onmousemove = Callback::from(|e: MouseEvent| {
    if let Some(target) = e.target_dyn_into::<HtmlElement>() {
        let rect = target.get_bounding_client_rect();
        let x = (e.client_x() as f64) - rect.left();
        let y = (e.client_y() as f64) - rect.top();
        console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
    }
});

html! {
    <div id="mousemoveme" {onmousemove}></div>
};
```

## 补充依赖库

`web-sys` 是 Web API 的原始绑定，因此在 Rust 中会有一些痛苦，因为它并不是为 Rust 或甚至强类型系统设计的，这就是社区 crate 提供了对 `web-sys` 的抽象，以提供更符合 Rust 习惯的 API。

_[补充依赖库清单](/community/external-libs)_
