wasm-bindgen
wasm-bindgen
是一个库和工具,旨在促进 Wasm 模块和 JavaScript 之间的高级交互;它是由
The Rust and WebAssembly Working Group 使用 Rust 构建的。
Yew 使用 wasm-bindgen
来与浏览器进行交互通过一系列 creates:
本节将从高层次上探讨其中一些包,以便更容易地理解和使用与 Yew 相关的 wasm-bindgen
API。如需更深入地了解 wasm-bindgen
及其相关的包,请查看 wasm-bindgen 指南。
要查看上述 crates 的文档,请查看 wasm-bindgen docs.rs
。
使用 wasm-bindgen
的 doc.rs 搜索功能,可以找到已经通过 wasm-bindgen
导入的浏览器 API 和 JavaScript 类型。
wasm-bindgen
该库为上述其他 crate 提供了许多构建块。在本节中,我们只会涵盖 wasm-bindgen
crate 的两个主要方面,即宏和一些你会一遍又一遍看到的类型/特性。
#[wasm_bindgen]
宏
#[wasm_bindgen]
宏提供了 Rust 和 JavaScript 之间的接口,为它们之间的相互转换提供了一个系统。
使用这个宏比较高级,除非你想使用一个外部的 JavaScript 库,否则不应该使用它。js-sys
和 web-sys
crate
提供了针对内置 JavaScript 类型和浏览器 API 的 wasm-bindgen
定义。
让我们通过一个简单的例子来使用 #[wasm-bindgen]
宏导入一些特定版本的 console.log
函数。
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` 具有多种形式,因此我们可以绑定多个签名。
// 请注意,我们需要使用 `js_name`,以确保我们始终在 JS 中调用 `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 Using console.log of The wasm-bindgen
Guide。
Simulating inheritance
JavaScript 类之间的继承是 JavaScript 语言的一个核心特性,DOM(文档对象模型)就是围绕它设计的。当使用 wasm-bindgen
导入类型时,您还可以添加描述其继承关系的属性。
在Rust中,这种继承关系使用 Deref
和 AsRef
trait 来表示。举个例子,假设你有三个类型 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
,将在下面的章节中介绍。
extends section in The wasm-bindgen
Guide
JsValue
JsValue 是一个由 JavaScript 拥有的对象的表示,它是 wasm-bindgen
的一个根基础类型。所有来自 wasm-bindgen
的类型都是 JsValue
类型,
这是因为 JavaScript 没有一个强类型系统,所以任何接受一个名为 x
的变量的函数并不定义它的类型,因此 x
可以是一个有效的 JavaScript 值;因此使用 JsValue
。
如果您正在使用接受 JsValue
的导入函数或类型,那么任何导入的值都是 技术上 有效的。
JsValue
可以被函数接受,但是这个函数仍然可能只接受特定的类型,这可能导致 panic - 因此,在使用原始的 wasm-bindgen
API 时,
需要检查导入的 JavaScript 的文档,以确定如果该值不是某种类型是否会引发异常 (panic)。
JsCast
Rust有一个强大的类型系统,而JavaScript却没有😞。为了让Rust保持这种强类型的特性并且仍然方便,
WebAssembly小组想出了一个很棒的trait:JsCast
。它的作用是帮助您从一个JavaScript“类型”转换为另一个,
这听起来很模糊,但实际上意味着,如果您有一个类型,您知道它是另一个类型,那么您可以使用 JsCast
的函数从一个类型跳到另一个类型。
当使用web-sys
、wasm_bindgen
、js-sys
时,了解这个很好的trait会很有帮助 - 您会注意到很多类型从这些crate中实现了 JsCast
。
JsCast
提供了两种类型的转换方法——检查型和不检查型。因此,如果在运行时你不确定某个对象的类型,你可以尝试进行类型转换,
这将返回可能的错误类型,例如 Option
和
Result
。
在 web-sys
中,一个常见的例子是你想获取一个事件的目标元素。你可能知道目标元素是什么,
但 web_sys::Event
API总是返回一个 Option<web_sys::EventTarget>
。你需要将它转换为元素类型,以便调用其方法。
// need to import the 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!");
// maybe the target is a select element?
if let Some(select_element) = target.dyn_ref::<HtmlSelectElement>() {
// do something amazing here
return;
}
// if it wasn't a select element then I KNOW it's a input element!
let input_element: HtmlInputElement = target.unchecked_into();
}
dyn_ref
方法是一种检查转换方法,
它返回一个 Option<&T>
,这意味着如果转换失败,原始类型仍然可以使用,因此返回 None
。
dyn_into
方法会消耗self,
按照Rust中的into方法的约定,返回类型是 Result<T, Self>
。如果转换失败,原始的 Self
值将在 Err
中返回。您可以再试一次或对原始类型进行其他操作。
Closure
Closure
类型提供了一种将 Rust 闭包传递到 JavaScript 的方法,传递到 JavaScript 的闭包必须具有 'static
生命周期以确保安全。
这种类型是一个"handle",因为当它被丢弃时,它将使其引用的JS闭包无效。在 Closure 被丢弃之后,任何在 JS 中使用该闭包的操作都将引发异常。
Closure
类型通常在使用 js-sys
或 web-sys
API 时使用,这些 API 接受类型为 &js_sys::Function
的参数。
在 Yew 中使用 Closure
的示例可以在 Events 页面的 Using Closure
section 中找到。
js-sys
js-sys
crate提供了JavaScript标准内置对象的绑定/导入,包括它们的方法和属性。
这不包括任何Web API,因为这是 web-sys
的用途!
wasm-bindgen-futures
wasm-bindgen-futures
crate 为 Rust 的 Future
类型提供了与 JavaScript Promise 类型的桥接,
并包含将 Rust Future 转换为 JavaScript Promise 的工具。当在 Rust (wasm) 中处理异步或其他阻塞工作时,这将非常有用,并提供了与 JavaScript 事件和 JavaScript I/O 原语进行交互的能力。
目前该 crate 中有三个主要接口:
JsFuture
- 该类型用Promise
构造, 可以被用作Future<Output=Result<JsValue, JsValue>>
。当Promise
被解析时,该Future
将解析为Ok
, 当Promise
被拒绝时,该Future
将解析为Err
,分别包含Promise
的解析值或拒绝值。future_to_promise
- 将一个 Rust 的Future<Output=Result<JsValue, JsValue>>
转换为一个 JavaScript 的Promise
。 Future 的结果将转换为 JavaScript 中的一个 resolved 或 rejected 的Promise
。spawn_local
- 在当前线程上启动一个Future<Output = ()>
。这是在 Rust 中运行Future
而不将其发送到 JavaScript 的最佳方法。
wasm-bindgen-futures
documentation.
spawn_local
spawn_local
是在 Yew 中使用 wasm-bindgen-futures crate
时最常用的部分,因为它在使用具有异步 API 的库时很有用。
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!");
// console log "Hello, world!"
console::log_1(&string.into());
});
Yew 还在某些 API 中添加了对 Futures 的支持,最值得注意的是您可以创建一个 callback_future
,它接受一个 async
块 - 它在内部使用了 spawn_local
。