11个不稳定版本 (3个重大变更)
0.3.2 | 2024年6月2日 |
---|---|
0.3.1 | 2024年6月1日 |
0.3.0 | 2024年5月31日 |
0.2.2 | 2024年5月25日 |
0.0.2 | 2024年4月28日 |
#119 在 WebAssembly
每月196次下载
29KB
377 行
Dioxus Web Component
此crate提供了一种将Dioxus组件暴露为web组件的桥梁。
此crate支持web组件属性和自定义事件。您还可以为您的web组件添加CSS样式。
查看示例,了解如何在完整项目中使用: https://github.com/ilaborie/dioxus-web-component/tree/main/examples
如果您是Rust和WebAssembly的新手,请先查看Rust WebAssembly书籍。
使用宏的使用
理想情况下,您只需要将Dioxus的#[component]
替换为#[web_component]
。然后您应该使用wasm-bindgen注册web组件。最后,您可以使用npm和wasm-pack创建npm包。
use dioxus::prelude::*;
use dioxus_web_component::web_component;
#[web_component]
fn MyWebComponent(
attribute: String,
on_event: EventHandler<i64>,
) -> Element {
todo!()
}
// Function to call from the JS side
#[wasm_bindgen]
pub fn register() {
// Register the web component (aka custom element)
register_my_web_component();
}
然后在JS侧调用该函数。
Web组件的定制
#[web_component]
注解可以通过以下方式配置
tag
来设置HTML自定义元素标签名。默认情况下,它是函数名的短横线版本。style
提供InjectedStyle
到您的组件。
组件的参数可以是
- 一个属性,如果您想将参数作为HTML属性传递,
- 一个属性,如果您只想将参数作为JavaScript
HTMLElement
的属性读取/写入, - 或一个事件,如果参数是Dioxus
EventHandler
。
💡提示:如果您使用这两个注解,则可以将属性和特性同时使用。
属性
您可以使用 #[attribute]
注解来定制属性,并使用
name
来设置 HTML 属性名称。默认情况下,它是参数名称的短横线命名法。option
用于标记属性为可选。如果类型是Option<...>
。initial
用于在 HTML 属性缺失时设置默认值。默认使用该类型的std::default::Default
实现来设置默认值。parse
用于提供 HTML 属性值(字符串)与类型值之间的转换。默认使用std::str::FromStr
实现进行转换,如果转换失败则使用默认值。
特性
要声明特性,您需要使用 #[property]
注解。
我们使用 wasm-bindgen 将 Rust 端的值转换为 JavaScript 值。
⚠️ 重要:getter 返回一个 JavaScript Promise
。
您可以使用以下属性来定制特性
name
用于设置特性的 JavaScript 名称。默认情况下,它是参数名称的驼峰命名法。readonly
用于仅生成自定义 getterinitial
用于在 HTML 属性缺失时设置默认值。默认使用该类型的std::defaultDefault
实现来设置默认值。try_from_js
用于从JsValue
转换到参数类型。默认使用std::convert::TryInto
实现进行转换。错误情况被忽略(不会设置值)try_into_js
用于从参数类型转换到JsValue
。默认使用std::convert::TryInto
实现进行转换。在错误情况下返回undefined
事件
事件是具有 Dioxus EventHandler<...>
类型的参数。您可以使用以下属性来定制事件
name
用于设置 HTML 事件名称。默认情况下,使用不带on
前缀的参数名称(如果有的话)no_bubble
用于禁止自定义事件冒泡no_cancel
用于移除取消自定义事件的能力
此示例使用所有注解
use dioxus::prelude::*;
use dioxus_web_component::{web_component, InjectedStyle};
#[web_component(tag = "my-component", style = InjectedStyle::css(include_str!("./style.css")))]
fn MyWebComponent(
#[attribute(name = "attr1", option = false, initial = String::new(), parse = |value| Some(value.to_string()))]
attr1: String,
#[attribute(name = "attr-option", option = true, initial = None, parse = |value| Some(value.to_string()))]
attr_option: Option<String>,
// Readonly property
#[property(readonly)]
prop: Option<String>,
// Property with custom conversion
#[property(
initial = MyProp(true),
try_into_js = |prop| {
let js_value = if prop.0 {
JsValue::TRUE
} else {
JsValue::FALSE
};
Ok::<_, Infallible>(js_value)
},
try_from_js= |value| Ok::<_, Infallible>(MyProp(value.is_truthy())),
)]
prop2: MyProp,
#[event(name = "event", no_bubble = false, no_cancel = false)] event: EventHandler<i64>,
) -> Element {
todo!()
}
#[derive(Clone, PartialEq)]
struct MyProp(bool);
有关更多详细信息,请参阅 dioxus-web-component-macro 文档。
无宏的使用方法
目前,当您使用宏时,我们旨在避免破坏性更改,但您应该预计在 API 中会有一些更改。
不建议使用无宏的使用方法
您可以为 DioxusWebComponent
提供您的手动实现,并调用 register_dioxus_web_component
来注册您的网页组件。
关键点是使用 dioxus 上下文中的 Shared
元素。
例如,问候示例可以写成
use dioxus::prelude::*;
use dioxus_web_component::{
register_dioxus_web_component, DioxusWebComponent, InjectedStyle, Message, Property, Shared,
};
use wasm_bindgen::prelude::*;
/// Install (register) the web component
///
/// # Errors
///
/// Registering the web-component may fail
#[wasm_bindgen(start)]
pub fn register() -> Result<(), JsValue> {
register_greetings();
Ok(())
}
#[component]
fn Greetings(name: String) -> Element {
rsx! { p { "Hello {name}!" } }
}
fn register_greetings() {
let properties = vec![Property::new("name", false)];
let style = InjectedStyle::css(include_str!("./style.css"));
register_dioxus_web_component(
"plop-greeting",
vec!["name".to_string()],
properties,
style,
greetings_builder,
);
}
#[derive(Clone, Copy)]
struct GreetingsWebComponent {
name: Signal<String>,
}
impl DioxusWebComponent for GreetingsWebComponent {
fn set_attribute(&mut self, attribute: &str, value: Option<String>) {
match attribute {
"name" => {
let new_value = value.and_then(|attr| attr.parse().ok()).unwrap_or_default();
self.name.set(new_value);
}
_ => {
// nop
}
}
}
fn set_property(&mut self, property: &str, value: JsValue) {
match property {
// we allow to set the name as a property
"name" => {
if let Ok(new_value) = Ok(value).and_then(|value| value.try_into()) {
self.name.set(new_value);
}
}
_ => {
// nop
}
}
}
fn get_property(&mut self, property: &str) -> JsValue {
match property {
// we allow to get the name as a property
"name" => Ok(self.name.read().clone())
.and_then(|value| value.try_into())
.unwrap_or(::wasm_bindgen::JsValue::NULL),
_ => JsValue::undefined(),
}
}
}
fn greetings_builder() -> Element {
let mut wc = use_context::<Shared>();
let name = use_signal(String::new);
let mut greetings = GreetingsWebComponent { name };
let corountine = use_coroutine::<Message, _, _>(move |mut rx| async move {
use dioxus_web_component::StreamExt;
while let Some(msg) = rx.next().await {
greetings.handle_message(msg);
}
});
use_effect(move || {
wc.set_tx(corountine.tx());
});
rsx! {
Greetings {
name
}
}
}
计数器示例看起来像这样
use dioxus::prelude::*;
use dioxus_web_component::{
custom_event_handler, register_dioxus_web_component, CustomEventOptions, DioxusWebComponent,
};
use dioxus_web_component::{InjectedStyle, Message, Property, Shared};
use wasm_bindgen::prelude::*;
/// Install (register) the web component
///
/// # Errors
///
/// Registering the web-component may fail
#[wasm_bindgen(start)]
pub fn register() -> Result<(), JsValue> {
// The register counter is generated by the `#[web_component(...)]` macro
register_counter();
Ok(())
}
/// The Dioxus component
#[component]
fn Counter(label: String, on_count: EventHandler<i32>) -> Element {
let mut counter = use_signal(|| 0);
rsx! {
span { "{label}" }
button {
onclick: move |_| {
counter += 1;
on_count(counter());
},
"+"
}
output { "{counter}" }
}
}
fn register_counter() {
let properties = vec![Property::new("label", false)];
let style = InjectedStyle::stylesheet("./style.css");
register_dioxus_web_component("plop-counter", vec![], properties, style, counter_builder);
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct CounterWebComponent {
label: Signal<String>,
on_count: EventHandler<i32>,
}
impl DioxusWebComponent for CounterWebComponent {
#[allow(clippy::single_match_else)]
fn set_property(&mut self, property: &str, value: JsValue) {
match property {
"label" => {
let new_value = String::(value).unwrap_throw();
self.label.set(new_value);
}
_ => {
// nop
}
}
}
#[allow(clippy::single_match_else)]
fn get_property(&mut self, property: &str) -> JsValue {
match property {
"label" => {
let value = self.label.read().clone();
value.into()
}
_ => JsValue::undefined(),
}
}
}
fn counter_builder() -> Element {
let mut wc = use_context::<Shared>();
let label = use_signal(String::new);
let on_count = custom_event_handler(wc.event_target(), "count", CustomEventOptions::default());
let mut counter = CounterWebComponent { label, on_count };
let corountine = use_coroutine::<Message, _, _>(move |mut rx| async move {
use dioxus_web_component::StreamExt;
while let Some(msg) = rx.next().await {
counter.handle_message(msg);
}
});
use_effect(move || {
wc.set_tx(corountine.tx());
});
rsx! {
Counter {
label,
on_count
}
}
}
限制
- 仅扩展
HTMLElement
- 仅作为 Dioxus
#[component]
注释的替代品(不与手工Props
一起使用) - 无法在网页组件中添加可从 JavaScript 调用的方法。(解决方案:使用属性)
贡献
欢迎贡献 ❤️。
依赖
~17–26MB
~400K SLoC