178 lines
12 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: 'wasm-bindgen'
sidebar_label: wasm-bindgen
---
[`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) 是一个在 JavaScript 和 Rust 函数之间建立调用桥梁的库和工具。它是由 [Rust 和 WebAssembly 工作组](https://rustwasm.github.io/) 使用 Rust 构建的。
Yew 使用 `wasm-bindgen` 通过一些 crate 与浏览器进行交互:
- [`js-sys`](https://crates.io/crates/js-sys)
- [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen)
- [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
- [`web-sys`](https://crates.io/crates/web-sys)
本节将从更抽象的层次上探讨这些 crate以便更容易地理解和使用 Yew 中的 `wasm-bindgen` API。要了解有关 `wasm-bindgen` 及其相关 crate 的更深入指南,请查看 [`wasm-bindgen` 指引](https://wasm-bindgen.github.io/wasm-bindgen/)。
有关上述 crate 的文档,请查看 [`wasm-bindgen docs.rs`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/index.html)。
:::tip
使用 `wasm-bindgen` doc.rs 搜索来查找已使用 `wasm-bindgen` 导入的浏览器 API 和 JavaScript 类型。
:::
## [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen)
这个 crate 为上面的其他 crate 提供了许多构建块。在本节中,我们只会涵盖 `wasm-bindgen` crate 的两个主要领域,即宏和一些您会一遍又一遍看到的类型/特性。
### `#[wasm_bindgen]` macro
`#[wasm_bindgen]` 宏提供了 Rust 和 JavaScript 之间的接口,提供了一个在两者之间进行转换的系统。使用这个宏更为高级,除非您要使用外部 JavaScript 库,否则不应该使用它。`js-sys` 和 `web-sys` crate 为内置 JavaScript 类型和浏览器 API 提供了 `wasm-bindgen` 定义。
让我们通过一个简单的示例来使用 `#[wasm-bindgen]` 宏来导入一些特定版本的 [`console.log`](https://developer.mozilla.org/en-US/docs/Web/API/Console/log) 函数。
```rust ,no_run
use wasm_bindgen::prelude::*;
// 首先让我们手动绑定 `console.log`,而不使用 `web_sys` 的帮助。
// 在这里,我们手动编写 `#[wasm_bindgen]` 注解,我们程序的正确性取决于这些注解的正确性!
#[wasm_bindgen]
extern "C" {
// 在这里使用 `js_namespace` 来绑定 `console.log(..)` 而不是只有 `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
// `console.log` 是多态的,所以我们可以使用多个签名绑定它。
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);
// 多个参数也是可以的!
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}
// 使用导入的函数!
log("Hello from Rust!");
log_u32(42);
log_many("Logging", "many values!");
```
_这个示例是基于 [1.2 使用 console.log 的 `wasm-bindgen` 指引](https://wasm-bindgen.github.io/wasm-bindgen/examples/console-log.html) 改编的。_
### 模拟继承
在 JavaScript 类之间的继承是 JavaScript 语言的核心特性DOM文档对象模型是围绕它设计的。当使用 `wasm-bindgen` 导入类型时,您还可以添加描述它们继承关系的属性。
在 Rust 中,这种继承关系使用 [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) 和 [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) 特性来表示。这里举个例子可能会有所帮助;假设您有三种类型 `A`、`B` 和 `C`,其中 `C` 扩展了 `B`,而 `B` 又扩展了 `A`。
在导入这些类型时,`#[wasm-bindgen]` 宏将按照以下方式实现 `Deref` 和 `AsRef` 特性:
- `C` 可以 `Deref` 到 `B`
- `B` 可以 `Deref` 到 `A`
- `C` 可以被 `AsRef` 到 `B`
- `C` 和 `B` 都可以被 `AsRef` 到 `A`
这些实现允许您在 `C` 的实例上调用 `A` 的方法,并将 `C` 用作 `&B` 或 `&A`。
需要注意的是,使用 `#[wasm-bindgen]` 导入的每种类型都有相同的根类型,您可以将其视为上面示例中的 `A`,这种类型是 [`JsValue`](#jsvalue),下面有它的部分。
_[`wasm-bindgen` 指引中的 extends 部分](https://wasm-bindgen.github.io/wasm-bindgen/reference/attributes/on-js-imports/extends.html)_
### [`JsValue`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html)
这是 JavaScript 拥有的对象的表示,这是 `wasm-bindgen` 的根捕获类型。任何来自 `wasm-bindgen` 的类型都是 `JsValue`,这是因为 JavaScript 没有强类型系统,因此接受变量 `x` 的任何函数都不定义其类型,因此 `x` 可以是有效的 JavaScript 值;因此 `JsValue`。如果您正在使用接受 `JsValue` 的导入函数或类型,那么任何导入的值在技术上都是有效的。
`JsValue` 可以被函数接受,但该函数可能仍然只接受某些类型,这可能会导致 panic - 因此在使用原始 `wasm-bindgen` API 时,请检查导入的 JavaScript 的文档以确定是否会在该值不是某种类型时引发异常panic
_[`JsValue` 文档](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html)。_
### [`JsCast`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html)
Rust 有一个强类型系统,而 JavaScript……没有😞。为了让 Rust 保持这些强类型但仍然方便WebAssembly 工作组提出了一个非常巧妙的特性 `JsCast`。它的工作是帮助您从一个 JavaScript "类型" 转换到另一个 "类型",这听起来很模糊,但它意味着如果您有一个类型,您知道它是另一个类型,那么您可以使用 `JsCast` 的函数从一个类型跳到另一个类型。当使用 `web-sys`、`wasm_bindgen`、`js-sys` 时,了解这个很好的特性 - 您会注意到许多类型将从这些 crate 中实现 `JsCast`。
`JsCast` 提供了转换的检查和不检查方法 - 因此在运行时,如果您不确定某个对象是什么类型,您可以尝试将其转换,这将返回可能的失败类型,如 [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) 和 [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)。
一个常见的例子是在 [`web-sys`](./web-sys.mdx) 中,当您尝试获取事件的目标时。您可能知道目标元素是什么,但 [`web_sys::Event`](https://wasm-bindgen.github.io/wasm-bindgen/api/web_sys/struct.Event.html) API 总是会返回一个 [`Option<web_sys::EventTarget>`](https://wasm-bindgen.github.io/wasm-bindgen/api/web_sys/struct.Event.html#method.target)。
您需要将其转换为元素类型,以便调用其方法。
```rust
// 需要先导入这个 Trait
use wasm_bindgen::JsCast;
use web_sys::{Event, EventTarget, HtmlInputElement, HtmlSelectElement};
fn handle_event(event: Event) {
let target: EventTarget = event
.target()
.expect("I'm sure this event has a target!");
// 也许目标是一个选择元素?
if let Some(select_element) = target.dyn_ref::<HtmlSelectElement>() {
// 做点别的
return;
}
// 如果它能确定不是一个选择元素,那么我可以肯定它是一个输入元素!
let input_element: HtmlInputElement = target.unchecked_into();
}
```
[`dyn_ref`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_ref) 方法是一个检查的转换,返回一个 `Option<&T>`,这意味着如果转换失败,则可以再次使用原始类型,因此返回 `None`。[`dyn_into`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into) 方法将消耗 `self`,这是 Rust 中 `into` 方法的约定,返回的类型是 `Result<T, Self>`。如果转换失败,则原始的 `Self` 值将在 `Err` 中返回。您可以再试一次或对原始类型进行其他操作。
_[`JsCast` documentation](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html)._
### [`Closure`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html)
`Closure` 类型提供了一种将 Rust 闭包传递到 JavaScript 的方法,出于健全性原因,传递给 JavaScript 的闭包必须具有 `'static` 生命周期。
这种类型是一个“句柄”,意味着每当它被丢弃时,它将使其引用的 JS 闭包无效。在 `Closure` 被丢弃后,对 JS 中闭包的任何使用都将引发异常。
当您使用接受类型 [`&js_sys::Function`](https://wasm-bindgen.github.io/wasm-bindgen/api/js_sys/struct.Function.html) 的 `js-sys` 或 `web-sys` API 时,通常会使用 `Closure`。在 [Events](../html/events.mdx) 页面的 [Using `Closure` 部分](../html/events.mdx#using-closure-verbose) 中可以找到在 Yew 中使用 `Closure` 的示例。
_[`Closure` 文档](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html)._
## [`js-sys`](https://crates.io/crates/js-sys)
`js-sys` crate 提供了 JavaScript 标准内置对象的绑定/导入,包括它们的方法和属性。
这不包括任何 Web API因为这是 [`web-sys`](./web-sys.mdx) 的作用!
_[`js-sys` 文档](https://wasm-bindgen.github.io/wasm-bindgen/api/js_sys/index.html)._
## [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
`wasm-bindgen-futures` crate 提供了一个桥梁,用于将 JavaScript Promise 类型作为 Rust [`Future`](https://doc.rust-lang.org/stable/std/future/trait.Future.html) 进行处理,并包含将 Rust Future 转换为 JavaScript Promise 的实用程序。当在 Rustwasm中处理异步或其他阻塞工作时这可能很有用并提供了与 JavaScript 事件和 JavaScript I/O 原语进行交互的能力。
目前这个 crate 中有三个主要接口:
1. [`JsFuture`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen_futures/struct.JsFuture.html) -
一个使用 [`Promise`](https://wasm-bindgen.github.io/wasm-bindgen/api/js_sys/struct.Promise.html) 构造的类型,然后可以用作 `Future<Output=Result<JsValue, JsValue>>`。如果 `Promise` 被解析,这个 `Future` 将解析为 `Ok`,如果 `Promise` 被拒绝,则解析为 `Err`,分别包含 `Promise` 的解析或拒绝值。
2. [`future_to_promise`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.future_to_promise.html) -
将 Rust `Future<Output=Result<JsValue, JsValue>>` 转换为 JavaScript `Promise`。未来的结果将转换为 JavaScript 中的已解析或已拒绝 `Promise`。
3. [`spawn_local`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html) -
在当前线程上生成一个 `Future<Output = ()>`。这是在 Rust 中运行 Future 的最佳方法,而不是将其发送到 JavaScript。
_[`wasm-bindgen-futures` 文档](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen_futures/index.html)._
### [`spawn_local`](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html)
`spawn_local` 将是 Yew 中 `wasm-bindgen-futures` crate 中最常用的部分,因为这有助于使用具有异步 API 的库。
```rust ,no_run
use web_sys::console;
use wasm_bindgen_futures::spawn_local;
async fn my_async_fn() -> String { String::from("Hello") }
spawn_local(async {
let mut string = my_async_fn().await;
string.push_str(", world!");
// 控制台输出 "Hello, world!"
console::log_1(&string.into());
});
```
Yew 还在某些 API 中添加了对 futures 的支持,最值得注意的是您可以创建一个接受 `async` 块的 `callback_future` - 这在内部使用了 `spawn_local`。
_[`spawn_local` 文档](https://wasm-bindgen.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html)._