跳到主要内容

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-sysweb-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中,这种继承关系使用 DerefAsRef trait 来表示。举个例子,假设你有三个类型 ABC,其中 C 扩展 B,而 B 又扩展 A

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

  • C 可以 DerefB
  • B 可以 DerefA
  • 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)。

JsValue documentation.

JsCast

Rust有一个强大的类型系统,而JavaScript却没有😞。为了让Rust保持这种强类型的特性并且仍然方便, WebAssembly小组想出了一个很棒的trait:JsCast。它的作用是帮助您从一个JavaScript“类型”转换为另一个, 这听起来很模糊,但实际上意味着,如果您有一个类型,您知道它是另一个类型,那么您可以使用 JsCast 的函数从一个类型跳到另一个类型。 当使用web-syswasm_bindgenjs-sys时,了解这个很好的trait会很有帮助 - 您会注意到很多类型从这些crate中实现了 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>,这意味着如果转换失败,原始类型仍然可以使用,因此返回 Nonedyn_into 方法会消耗self, 按照Rust中的into方法的约定,返回类型是 Result<T, Self>。如果转换失败,原始的 Self 值将在 Err 中返回。您可以再试一次或对原始类型进行其他操作。

JsCast documentation.

Closure

Closure 类型提供了一种将 Rust 闭包传递到 JavaScript 的方法,传递到 JavaScript 的闭包必须具有 'static 生命周期以确保安全。

这种类型是一个"handle",因为当它被丢弃时,它将使其引用的JS闭包无效。在 Closure 被丢弃之后,任何在 JS 中使用该闭包的操作都将引发异常。

Closure 类型通常在使用 js-sysweb-sys API 时使用,这些 API 接受类型为 &js_sys::Function 的参数。 在 Yew 中使用 Closure 的示例可以在 Events 页面的 Using Closure section 中找到。

Closure documentation.

js-sys

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

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

js-sys documentation.

wasm-bindgen-futures

wasm-bindgen-futures crate 为 Rust 的 Future 类型提供了与 JavaScript Promise 类型的桥接, 并包含将 Rust Future 转换为 JavaScript Promise 的工具。当在 Rust (wasm) 中处理异步或其他阻塞工作时,这将非常有用,并提供了与 JavaScript 事件和 JavaScript I/O 原语进行交互的能力。

目前该 crate 中有三个主要接口:

  1. JsFuture - 该类型用 Promise 构造, 可以被用作 Future<Output=Result<JsValue, JsValue>>。当 Promise 被解析时,该 Future 将解析为 Ok, 当 Promise 被拒绝时,该 Future 将解析为 Err,分别包含 Promise 的解析值或拒绝值。

  2. future_to_promise - 将一个 Rust 的 Future<Output=Result<JsValue, JsValue>> 转换为一个 JavaScript 的 Promise。 Future 的结果将转换为 JavaScript 中的一个 resolved 或 rejected 的 Promise

  3. 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

spawn_local documentation.