wasm-bindgen
wasm-bindgen
是一个库和工具,用于促进 Wasm 模块和 JavaScript 之间的高级交互;它由 Rust 和 WebAssembly 工作组 使用 Rust 构建。
Yew 使用 wasm-bindgen
通过多个板条箱与浏览器交互
本节将从高层次探讨其中一些板条箱,以便更容易理解并使用 wasm-bindgen
API 与 Yew。有关 wasm-bindgen
及其关联板条箱的更深入指南,请查看 wasm-bindgen
指南。
有关上述板条箱的文档,请查看 wasm-bindgen docs.rs
。
使用 wasm-bindgen
doc.rs 搜索,以查找已使用 wasm-bindgen
导入的浏览器 API 和 JavaScript 类型。
wasm-bindgen
此板条箱为上述所有其他板条箱提供了许多构建模块。在本节中,我们仅将涵盖 wasm-bindgen
板条箱的两个主要区域,即宏以及您将反复看到的某些类型/特征。
#[wasm_bindgen]
宏
#[wasm_bindgen]
宏提供了 Rust 和 JavaScript 之间的一个接口,提供了一个在两者之间进行转换的系统。使用此宏比较高级,除非您尝试使用外部 JavaScript 库,否则您不需要使用它。js-sys
和 web-sys
板条箱公开了用于内置 JavaScript 类型和浏览器 API 的 wasm-bindgen
定义。
让我们通过一个简单的示例来了解如何使用 #[wasm-bindgen]
宏来导入 console.log
函数的某些特定风格。
use wasm_bindgen::prelude::*;
// First up let's take a look of binding `console.log` manually, without the
// help of `web_sys`. Here we're writing the `#[wasm_bindgen]` annotations
// manually ourselves, and the correctness of our program relies on the
// correctness of these annotations!
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
// The `console.log` is quite polymorphic, so we can bind it with multiple
// signatures. Note that we need to use `js_name` to ensure we always call
// `log` in JS.
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);
// Multiple arguments too!
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}
// using the imported functions!
log("Hello from Rust!");
log_u32(42);
log_many("Logging", "many values!");
此示例改编自 wasm-bindgen
指南的 1.2 使用 console.log.
模拟继承
JavaScript 类之间的继承是 JavaScript 语言的核心特性,并且 DOM(文档对象模型)就是围绕它设计的。当使用 wasm-bindgen
导入类型时,您还可以添加描述其继承的属性。
在 Rust 中,这种继承使用 Deref
和 AsRef
特性来表示。一个示例可能会有所帮助;假设你有三个类型 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
这是 JavaScript 拥有的对象的表示形式,这是 wasm-bindgen
的根捕获所有类型。任何来自 wasm-bindgen
的类型都是 JsValue
,这是因为 JavaScript 没有强类型系统,因此接受变量 x
的任何函数都不会定义其类型,因此 x
可以是有效的 JavaScript 值;因此 JsValue
。如果你正在使用接受 JsValue
的导入函数或类型,那么任何导入的值在技术上都是有效的。
JsValue
可以被函数接受,但该函数可能仍然只接受某些类型,这可能导致恐慌 - 因此,在使用原始 wasm-bindgen
API 时,请检查要导入的 JavaScript 的文档,以了解如果该值不是特定类型,是否会引发异常(恐慌)。
JsCast
Rust 具有强大的类型系统,而 JavaScript 则没有 😞。为了让 Rust 既能保持这些强大的类型,又能保持便利性,WebAssembly 组想出了一个非常简洁的特征 JsCast
。它的作用是帮助你从一种 JavaScript“类型”转换到另一种类型,这听起来很模糊,但它的意思是,如果你有一种类型你知道是另一种类型,那么你可以使用 JsCast
的函数从一种类型跳到另一种类型。在使用 web-sys
、wasm_bindgen
、js-sys
时,这是一个值得了解的特性——你会注意到很多类型将从这些模块中实现 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
生命周期。
这种类型在某种意义上是一个“句柄”,每当它被丢弃时,它都会使它所引用的 JS 闭包失效。在闭包被丢弃后在 JS 中使用闭包会引发异常。
当您使用接受类型 &js_sys::Function
的 js-sys
或 web-sys
API 时,经常使用 Closure
。可以在 使用 Closure
部分 中找到在 Yew 中使用 Closure
的示例,该部分位于 事件 页面上。
js-sys
js-sys
crate 提供了 JavaScript 标准内置对象的绑定/导入,包括它们的方法和属性。
这不包括任何 Web API,因为这是 web-sys
的用途!
wasm-bindgen-futures
wasm-bindgen-futures
crate 提供了一个桥梁,用于将 JavaScript Promise 类型作为 Rust Future
使用,并包含将 Rust Future 转换为 JavaScript Promise 的实用程序。这在使用 Rust (wasm) 中的异步或其他阻塞工作时很有用,并提供了与 JavaScript 事件和 JavaScript I/O 原语进行交互的能力。
此 crate 中当前有三个主要接口
-
JsFuture
- 使用Promise
构造的类型,然后可以用作Future<Output=Result<JsValue, JsValue>>
。如果Promise
已解决,则此Future
将解析为Ok
;如果Promise
被拒绝,则解析为Err
,分别包含Promise
中已解决或已拒绝的值。 -
future_to_promise
- 将 RustFuture<Output=Result<JsValue, JsValue>>
转换为 JavaScriptPromise
。Future 的结果将在 JavaScript 中转换为已解决或已拒绝的Promise
。 -
spawn_local
- 在当前线程上生成Future<Output = ()>
。这是在 Rust 中运行 Future 的最佳方式,而无需将其发送到 JavaScript。
spawn_local
spawn_local
将成为 Yew 中 wasm-bindgen-futures
箱子最常用的部分,因为它在使用具有异步 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 添加了对 future 的支持,最值得注意的是你可以创建一个接受 async
块的 callback_future
- 它在内部使用 spawn_local
。