Yew是一个Rust/Wasm框架,用来创建前端页面。
使用html!
宏可以写HTML,这个宏的内部会将其转化为代表DOM的Rust代码:
use yew::prelude::*;
let my_header: Html = html! {
<img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600" />
};
在html!
中可以使用大括号来嵌入周围的变量:
use yew::prelude::*;
let header_text = "Hello world".to_string();
let header_html: Html = html! {
<h1>{header_text}</h1>
};
let count: usize = 5;
let counter_html: Html = html! {
<p>{"My age is: "}{count}</p>
};
let combined_html: Html = html! {
<div>{header_html}{counter_html}</div>
};
这个嵌入应该是移动语义,并且主要到可以嵌入其他Html
。
html!
比较重要的一个规则是其内部只能有一个node,如果要写多个node需要使用fragments,就是没有name的那个tag,应该只是语法上的形式:
use yew::html;
// fixed: using html fragments
html! {
<>
<div></div>
<p></p>
</>
};
Rust要使用Javascript其实是Wasm与Javascript交互,因为Rust代码要编译成Wasm。Wasm与Javascript交互的基础是wasm-bindgen
这个库,直接用wasm-bindgen
还是太麻烦了,js-sys
和web-sys
两个crate可以简化使用,js-sys
提供了对Javascript原生API的绑定,web-sys
则提供web相关API的绑定。一个使用web-sys
的例子如下:
// 对应的JS代码为let document = window.document
use wasm_bindgen::UnwrapThrowExt;
use web_sys::window;
let document = window()
.expect_throw("window is undefined")
.document()
.expect_throw("document is undefined");
wasm-bindgen
有一个很重要的地方是模拟JS中的继承的,这对使用web-sys
也很重要。wasm-bindgen
中使用Rust的Deref
和AsRef
两个trait来模拟继承。比如C继承自B,B继承自A,则有:
Deref
成BDeref
成AAsRef
B和AAsRef
A这种实现可以使C以&B或者&A的身份使用其对应方法。另外所有的类型有一个基类JsValue
。yew docs中有一个示例讲了从一个HtmlTextAreaElement
一直deref
到JaValue
的过程,我把英文注释翻译成中文:
use std::ops::Deref;
use web_sys::{
Element,
EventTarget,
HtmlElement,
HtmlTextAreaElement,
Node,
};
fn inheritance_of_text_area(text_area: HtmlTextAreaElement) {
// HtmlTextAreaElement在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();
// 注意这里我们从web-sys中的类型deref到了js-sys中的JS原生类型
let object: &js_sys::Object = event_target.deref();
// 注意我们从js-sys中的JS原生类型deref到了wasm-bindgen中的根类型JsValue
let js_value: &wasm_bindgen::JsValue = object.deref();
// 实际使用中你并不需要像上面一样手动deref,由于Rust有自动deref规则
// 所以HtmlTextAreaElement可以直接使用JsValue的方法
// is_string这个方法是从JsValue继承的
assert!(!text_area.is_string());
// HtmlTextAreaElement作为函数参数也可以被自动deref,下面的代码可以证明
fn this_function_only_takes_event_targets(targets: &EventTarget) {};
this_function_only_takes_event_targets(&text_area);
// 这是因为AsRef trait可以让我们像对待&EventTarget一样对待HtmlTextAreaElement
let event_target: &EventTarget = text_area.as_ref();
}
下面放一个较长的例子,展示了JS代码如何使用web-sys
以及yew
来写
JS例子
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
例子
[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.
yew例子
[dependencies.web-sys]
version = "0.3"
# We need to enable the `DomRect` feature in order 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
之上也有其他的库封装,如Malvolio
、Weblog
、Gloo
等。