2025-08-26 22:03:06 +09:00

178 lines
11 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://rustwasm.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://rustwasm.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://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html)。 _
### [`JsCast`](https://rustwasm.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://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html) API 總是會回傳一個[` Option<web_sys::EventTarget>`](https://rustwasm.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://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_ref) 方法是一個檢查的轉換,回傳一個`Option<&T>`,這表示如果轉換失敗,則可以再次使用原始類型,因此傳回`None`。 [`dyn_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into) 方法將消耗`self`這是Rust 中`into` 方法的約定,傳回的類型是`Result<T, Self>`。如果轉換失敗,則原始的 `Self` 值將在 `Err` 中傳回。您可以再試一次或對原始類型進行其他操作。
_[`JsCast` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html)._
### [`Closure`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html)
`Closure` 類型提供了一種將 Rust 閉包傳遞到 JavaScript 的方法,出於健全性原因,傳遞給 JavaScript 的閉包必須具有 `'static` 生命週期。
這種類型是一個“句柄”,這意味著每當它被丟棄時,它將使其引用的 JS 閉包無效。在 `Closure` 被丟棄後,對 JS 中閉包的任何使用都會引發異常。
當您使用接受型別[`&js_sys::Function`](https://rustwasm.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://rustwasm.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://rustwasm.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://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/struct.JsFuture.html) -
一個使用[`Promise`](https://rustwasm.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://rustwasm.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://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html) -
在目前執行緒上產生一個 `Future<Output = ()>`。這是在 Rust 中運行 Future 的最佳方法,而不是將其發送到 JavaScript。
_[`wasm-bindgen-futures` 文件](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/index.html)._
### [`spawn_local`](https://rustwasm.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://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html)._