213 lines
7.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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_