web-sys
web-sys
クレートは、Web API のバインディングを提供します。これはブラウザの WebIDL から手続き的に生成されるため、名前が非常に長かったり、型があいまいだったりする場合があります。
web-sys
の機能
すべての機能が有効になっている web-sys
クレートは、Wasm アプリケーションに多くの肥大化を引き起こす可能性があります。この問題を回避するために、ほとんどの型は機能ゲートで保護されており、アプリケーションに必要な型のみを含めることができます。Yew は web-sys
からいくつかの機能を有効にし、そのパブリック API でいくつかの型を公開します。多くの場合、web-sys
を依存関係として自分で追加する必要があります。
web-sys
における継承
継承のシミュレーションセクションでは、一般的に Rust が JavaScript で継承をシミュレートする方法を提供していることを説明しています。web-sys
では、型で利用可能なメソッドを理解するにはその継承を理解する必要があるため、これは非常に重要です。
このセクションでは、特定の要素を見て、値が JsValue
になるまで Deref::deref
を呼び出して Rust を使ってその継承をリストします。
use std::ops::Deref;
use web_sys::{
Element,
EventTarget,
HtmlElement,
HtmlTextAreaElement,
Node,
};
fn inheritance_of_text_area(text_area: HtmlTextAreaElement) {
// HtmlTextAreaElement is <textarea> in html.
let html_element: &HtmlElement = text_area.deref();
let element: &Element = html_element.deref();
let node: &Node = element.deref();
let event_target: &EventTarget = node.deref();
// Notice we have moved from web-sys types now into built-in
// JavaScript types which are in the js-sys crate.
let object: &js_sys::Object = event_target.deref();
// Notice we have moved from js-sys type to the root JsValue from
// the wasm-bindgen crate.
let js_value: &wasm_bindgen::JsValue = object.deref();
// Using deref like this means we have to manually traverse
// the inheritance tree, however, you can call JsValue methods
// on the HtmlTextAreaElement type.
// The `is_string` method comes from JsValue.
assert!(!text_area.is_string());
// empty function just to prove we can pass HtmlTextAreaElement as a
// &EventTarget.
fn this_function_only_takes_event_targets(targets: &EventTarget) {};
// The compiler will walk down the deref chain in order to match the types here.
this_function_only_takes_event_targets(&text_area);
// The AsRef implementations allow you to treat the HtmlTextAreaElement
// as an &EventTarget.
let event_target: &EventTarget = text_area.as_ref();
}
wasm-bindgen
ガイドの web-sys
における継承.
NodeRef
内の Node
Yew は、html!
マクロによって作成された Node
への参照を保持する方法を提供するために NodeRef
を使用します。NodeRef
の Node
部分は、web_sys::Node
を参照しています。NodeRef::get
メソッドは Option<Node>
値を返しますが、Yew ではほとんどの場合、この値を特定のエレメントにキャストして、その特定のエレメントのメソッドを使用したいと考えます。このキャストは、Node
値が存在する場合は JsCast
を使用して行うことができますが、Yew はこのキャストを簡単に行うために NodeRef::cast
メソッドを提供しており、JsCast
トレイトのために wasm-bindgen
依存関係を含める必要もありません。
以下の 2 つのコードブロックは基本的に同じことを行っています。1 つ目は NodeRef::cast
を使用しており、2 つ目は NodeRef::get
から返された web_sys::Node
で JsCast::dyn_into
を使用しています。
- NodeRef::cast の使用
- NodeRef::get の使用
use web_sys::HtmlInputElement;
use yew::NodeRef;
fn with_node_ref_cast(node_ref: NodeRef) {
if let Some(input) = node_ref.cast::<HtmlInputElement>() {
// do something with HtmlInputElement
}
}
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::NodeRef;
fn with_jscast(node_ref: NodeRef) {
if let Some(input) = node_ref
.get()
.and_then(|node| node.dyn_into::<HtmlInputElement>().ok()) {
// do something with HtmlInputElement
}
}
JavaScript の例から Rust へ
このセクションでは、Web API と対話する JavaScript コードを Rust の web-sys
で書き換える方法の例を示します。
JavaScript の例
document.getElementById('mousemoveme').onmousemove = (e) => {
// e = Mouse event.
var rect = e.target.getBoundingClientRect()
var x = e.clientX - rect.left //x position within the element.
var y = e.clientY - rect.top //y position within the element.
console.log('Left? : ' + x + ' ; Top? : ' + y + '.')
}
web-sys
の例
上記の JavaSciprt の例を web-sys
単独で使用して実装すると、次のようになります
[dependencies]
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"
# We need to enable all the web-sys features we want to use!
features = [
"console",
"Document",
"HtmlElement",
"MouseEvent",
"DomRect",
]
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{console, Document, HtmlElement, MouseEvent};
let mousemove = Closure::<dyn Fn(MouseEvent)>::wrap(Box::new(|e| {
let rect = e
.target()
.expect("mouse event doesn't have a target")
.dyn_into::<HtmlElement>()
.expect("event target should be of type HtmlElement")
.get_bounding_client_rect();
let x = (e.client_x() as f64) - rect.left();
let y = (e.client_y() as f64) - rect.top();
console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
}));
Document::new()
.expect("global document not set")
.get_element_by_id("mousemoveme")
.expect("element with id `mousemoveme` not present")
.unchecked_into::<HtmlElement>()
.set_onmousemove(mousemove.as_ref().dyn_ref());
// we now need to save the `mousemove` Closure so that when
// this event fires the closure is still in memory.
このバージョンははるかに冗長ですが、その一部は、これらの関数呼び出しの一部には、保持する必要がある不変条件があり、そうでない場合は Rust でパニックが発生することを思い出させる失敗タイプによるものであることに気づくでしょう。冗長さのもう 1 つの原因は、特定のエレメントのメソッドを呼び出すことができるように、異なる型にキャストするための JsCast
の呼び出しです。
Yew の例
Yew では、ほとんどの場合、html!
マクロで使用するために Callback
を作成するため、上記の例を完全にコピーする代わりにこのアプローチを使用します
[dependencies.web-sys]
version = "0.3"
# We need to enable the `DomRect` feature to use the
# `get_bounding_client_rect` method.
features = [
"console",
"HtmlElement",
"MouseEvent",
"DomRect",
]
use web_sys::{console, HtmlElement, MouseEvent};
use yew::{
html,
Callback, TargetCast,
};
let onmousemove = Callback::from(|e: MouseEvent| {
if let Some(target) = e.target_dyn_into::<HtmlElement>() {
let rect = target.get_bounding_client_rect();
let x = (e.client_x() as f64) - rect.left();
let y = (e.client_y() as f64) - rect.top();
console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
}
});
html! {
<div id="mousemoveme" {onmousemove}></div>
};
外部ライブラリ
web-sys
は Web API への生のバインディングであるため、Rust でのいくつかの苦痛を伴います。これは、Rust や強力な型システムを念頭に置いて設計されたものではないためです。ここで、コミュニティクレートが web-sys
の抽象化を提供し、より慣用的な Rust API を提供します。