---
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://rustwasm.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://rustwasm.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://rustwasm.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）_
