wasm-bindgen
wasm-bindgen
是個函式庫和工具,能促進 Wasm 模組和 JavaScript 之間的高階互動;它由 Rust 和 WebAssembly 工作小組 使用 Rust 編寫。
Yew 使用 wasm-bindgen
透過多個 crate 來與瀏覽器互動。
本節將概略介紹其中一些 crate,藉此讓理解和使用 wasm-bindgen
API 與 Yew 更為容易。如果您需要有關 wasm-bindgen
及其相關 crate 的更深入指南,請查看 wasm-bindgen
使用指南。
如需上述 crate 的說明文件,請查看 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 語言的核心功能,Document Object Model (DOM) 是圍繞著它設計的。當使用 wasm-bindgen
匯入類型時,您也可以加入描述其繼承的屬性。
在 Rust 中,此繼承是使用 Deref
和 AsRef
特質表示的。舉例來說:假設您有三個類型 A
、B
和 C
,其中 C
擴充 B
,而 B
則擴充 A
。
在匯入這些類型時,#[wasm-bindgen]
巨集將以下列方式實作 Deref
和 AsRef
特質
C
可以Deref
到B
B
可以Deref
到A
C
可以對B
進行AsRef
C
和B
都可以對A
進行AsRef
這些實作允許您對 C
的實例呼叫 A
的方法,並像使用 &B
或 &A
一樣使用 C
。
請注意,使用 #[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 小組想出了一個很不錯的 Trait JsCast
。它的工作是幫助您從一個 JavaScript「類型」移動到另一個類型,這聽起來很含糊,但它的意思是如果您有一個已知的另一種類型,那麼您可以使用 JsCast
的函式從一種類型跳到另一種類型。在使用 web-sys
、wasm_bindgen
、js-sys
時,這是個不錯的 Trait - 您會注意到很多類型會從那些板條箱實作 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 封包失效。在 Closure 已放棄後,於 JS 中使用封包會引發例外狀況。
當使用接受 js_sys::Function 類型的 js-sys 或 web-sys API 時,會經常使用 Closure。可以在 事件 頁面的 使用 Closure 區段找到在 Yew 中使用 Closure 的範例。
js-sys
js-sys crate 提供了 JavaScript 標準內建物件的繫結/輸入,包括它們的方法和屬性。
這不包括任何網頁 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>>
使用。這個Future
會解析為Ok
,如果Promise
是已處理的,並解析為Err
如果Promise
是被拒絕的,分別包含已處理或被拒絕的值來自Promise
。 -
future_to_promise
- 將 RustFuture<Output=Result<JsValue, JsValue>>
轉換成 JavaScriptPromise
。這個將來的結果會轉譯到一個 JavaScript 已處理或被拒絕的Promise
。 -
spawn_local
- 在目前的執行緒產生Future<Output = ()>
。這是 Rust 中執行 Future 的最佳方式,不必把它送到 JavaScript。
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
- 它會在內部使用 spawn_local
。