213 lines
7.8 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)_