跳至主要内容
版本:0.21

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-sysweb-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 中,这种继承使用 DerefAsRef 特性来表示。一个示例可能会有所帮助;假设你有三个类型 ABC,其中 C 扩展 B,而 B 又扩展 A

导入这些类型时,#[wasm-bindgen] 宏将以以下方式实现 DerefAsRef 特性

  • C 可以 DerefB
  • B 可以 DerefA
  • C 可以 AsRefB
  • CB 都可以 AsRefA

这些实现允许你在 C 的实例上调用 A 的方法,并将 C 用作 &B&A

需要注意的是,使用 #[wasm-bindgen] 导入的每个类型都有相同的根类型,你可以将其视为上面示例中的 A,此类型是 JsValue,它在下面有自己的部分。

wasm-bindgen 指南中的 extends 部分

JsValue

这是 JavaScript 拥有的对象的表示形式,这是 wasm-bindgen 的根捕获所有类型。任何来自 wasm-bindgen 的类型都是 JsValue,这是因为 JavaScript 没有强类型系统,因此接受变量 x 的任何函数都不会定义其类型,因此 x 可以是有效的 JavaScript 值;因此 JsValue。如果你正在使用接受 JsValue 的导入函数或类型,那么任何导入的值在技术上都是有效的。

JsValue 可以被函数接受,但该函数可能仍然只接受某些类型,这可能导致恐慌 - 因此,在使用原始 wasm-bindgen API 时,请检查要导入的 JavaScript 的文档,以了解如果该值不是特定类型,是否会引发异常(恐慌)。

JsValue 文档.

JsCast

Rust 具有强大的类型系统,而 JavaScript 则没有 😞。为了让 Rust 既能保持这些强大的类型,又能保持便利性,WebAssembly 组想出了一个非常简洁的特征 JsCast。它的作用是帮助你从一种 JavaScript“类型”转换到另一种类型,这听起来很模糊,但它的意思是,如果你有一种类型你知道是另一种类型,那么你可以使用 JsCast 的函数从一种类型跳到另一种类型。在使用 web-syswasm_bindgenjs-sys 时,这是一个值得了解的特性——你会注意到很多类型将从这些模块中实现 JsCast

JsCast 提供了经过检查和未经检查的强制转换方法——这样,在运行时,如果你不确定某个对象是什么类型,你可以尝试强制转换它,它会返回可能的失败类型,如 OptionResult

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 的形式返回。你可以重试或对原始类型执行其他操作。

JsCast 文档.

Closure

Closure 类型提供了一种将 Rust 闭包传输到 JavaScript 的方法,出于健全性原因,传递到 JavaScript 的闭包必须具有 'static 生命周期。

这种类型在某种意义上是一个“句柄”,每当它被丢弃时,它都会使它所引用的 JS 闭包失效。在闭包被丢弃后在 JS 中使用闭包会引发异常。

当您使用接受类型 &js_sys::Functionjs-sysweb-sys API 时,经常使用 Closure。可以在 使用 Closure 部分 中找到在 Yew 中使用 Closure 的示例,该部分位于 事件 页面上。

Closure 文档.

js-sys

js-sys crate 提供了 JavaScript 标准内置对象的绑定/导入,包括它们的方法和属性。

这不包括任何 Web API,因为这是 web-sys 的用途!

js-sys 文档.

wasm-bindgen-futures

wasm-bindgen-futures crate 提供了一个桥梁,用于将 JavaScript Promise 类型作为 Rust Future 使用,并包含将 Rust Future 转换为 JavaScript Promise 的实用程序。这在使用 Rust (wasm) 中的异步或其他阻塞工作时很有用,并提供了与 JavaScript 事件和 JavaScript I/O 原语进行交互的能力。

此 crate 中当前有三个主要接口

  1. JsFuture - 使用 Promise 构造的类型,然后可以用作 Future<Output=Result<JsValue, JsValue>>。如果 Promise 已解决,则此 Future 将解析为 Ok;如果 Promise 被拒绝,则解析为 Err,分别包含 Promise 中已解决或已拒绝的值。

  2. future_to_promise - 将 Rust Future<Output=Result<JsValue, JsValue>> 转换为 JavaScript Promise。Future 的结果将在 JavaScript 中转换为已解决或已拒绝的 Promise

  3. spawn_local - 在当前线程上生成 Future<Output = ()>。这是在 Rust 中运行 Future 的最佳方式,而无需将其发送到 JavaScript。

wasm-bindgen-futures 文档.

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

spawn_local 文档.