14 个版本 (9 个破坏性更改)
0.9.1 | 2023 年 5 月 26 日 |
---|---|
0.8.1 | 2023 年 4 月 30 日 |
0.6.0 | 2023 年 3 月 28 日 |
0.4.0 | 2022 年 6 月 30 日 |
0.1.0 | 2021 年 8 月 8 日 |
#56 在 WebAssembly
用于 kobold_qr
130KB
2.5K SLoC
Kobold
简单的声明式 Web 接口。
主要功能
零成本静态 HTML
view!
宏生成不透明的 impl View
类型,默认情况下不进行分配。所有静态 DOM 元素都编译为内联 JavaScript 代码来构建它们。表达式在首次渲染时注入到构建的 DOM 中。Kobold 跟踪这些表达式的 DOM 节点引用。
由于表达式评估的确切类型为 Rust 编译器所知,更新调用可以通过值(或指针)来差异它们,并且如果它们发生变化,可以外科手术地更新 DOM。
如果 view!
宏调用包含没有表达式的 DOM 元素,则构建的 View
类型将为零大小,并且其 View::update
方法将为空,使静态 DOM 的更新实际上为零成本。
Hello World!
Kobold 中的组件通过在 渲染函数 上注释 #[component]
属性来创建。
use kobold::prelude::*;
#[component]
fn Hello(name: &str) -> impl View + '_ {
view! {
<h1>"Hello "{ name }"!"</h1>
}
}
fn main() {
kobold::start(view! {
<Hello name="Kobold" />
});
}
组件函数必须返回一个实现了 View
特性的类型。由于 view!
宏会产生临时的本地定义类型,因此最佳做法是始终使用不透明的 impl View
返回类型。
这里所有内容都是静态类型化的,宏在操作令牌流时不会删除任何信息,因此 Rust 编译器可以告诉您何时出错
error[E0560]: struct `Hello` has no field named `nam`
--> examples/hello_world/src/main.rs:12:16
|
12 | <Hello nam="Kobold" />
| ^^^ help: a field with a similar name exists: `name`
您甚至可以使用 rust-analyzer 来重构组件或字段名称,并且它会为您更改宏内部的调用。
状态管理
可以使用 stateful
函数创建拥有并操作其状态的观点
use kobold::prelude::*;
#[component]
fn Counter(init: u32) -> impl View {
stateful(init, |count| {
bind! { count:
// Create an event handler with access to `&mut u32`
let onclick = move |_event| *count += 1;
}
view! {
<p>
"You clicked the "
// `{onclick}` here is shorthand for `onclick={onclick}`
<button {onclick}>"Button"</button>
" "{ count }" times."
</p>
}
})
}
fn main() {
kobold::start(view! {
<Counter init={0} />
});
}
stateful
函数接受两个参数
- 实现
IntoState
特性的状态构造函数。 Kobold 为大多数原始类型提供了默认实现,因此我们在这里可以使用u32
。 - 使用构建的状态的匿名渲染函数,在我们的例子中其参数是
&Hook<u32>
。
这里的 Hook
是指向状态的智能指针,它允许对状态的非可变访问。可以为任何 Hook
调用 bind!
宏以创建具有 &mut
对底层状态的引用的闭包。
有关更多详细信息,请访问 stateful
模块文档。
可选参数
使用 #[component(<param>?)]
语法将组件参数设置为默认值
// `code` will default to `200` if omitted
#[component(code?: 200)]
fn Status(code: u32) -> impl View {
view! {
<p> "Status code was "{ code }
}
}
view! {
// Status code was 200
<Status />
// Status code was 404
<Status code={404} />
}
有关更多详细信息,请访问 #[component]
宏文档。
条件渲染
由于 view!
宏产生唯一的临时类型,因此调用宏的 if
和 match
表达式将自然无法编译。
在 #[component]
属性上使用 auto_branch
标志时,Kobold 将扫描组件渲染函数的主体,并将所有在 if
或 match
表达式中的 view!
宏调用包装在一个枚举中,使它们具有相同的类型
#[component(auto_branch)]
fn Conditional(illuminatus: bool) -> impl View {
if illuminatus {
view! { <p> "It was the year when they finally immanentized the Eschaton." }
} else {
view! { <blockquote> "It was love at first sight." }
}
}
有关更多详细信息,请访问 branching
模块文档。
列表和迭代器
要渲染一个迭代器,请使用 for
关键字
// `ListIteratorExt` is included in the prelude
use kobold::prelude::*;
#[component]
fn IterateNumbers(count: u32) -> impl View {
view! {
<ul>
{
for (1..=count).map(|n| view! { <li> "Item #"{n} })
}
}
}
在更新时,迭代器仅消耗一次,并且所有项目都与上一个版本进行比较。在更新此类列表时,Kobold 不会进行任何分配,除非渲染的列表需要超出其原始容量。
有关关键字的更多信息,请访问 keywords
模块文档。
借用值
View
类型实际上是瞬时的,只需要在初始渲染期间或后续更新期间存在。这意味着您可以通过不必要克隆轻松且经济地渲染借用状态。
#[component]
fn Users<'a>(names: &'a [&'a str]) -> impl View + 'a {
view! {
<ul>
{
for names.iter().map(|name| view! { <li> { name } })
}
}
}
具有子组件的组件
如果您想从父 view!
调用中捕获子组件,只需将 #[component]
更改为 #[component(children)]
use kobold::prelude::*;
#[component(children)]
fn Header(children: impl View) -> impl View {
view! {
<header><h1>{ children }</h1></header>
}
}
fn main() {
kobold::start(view! {
<Header>"Hello Kobold"</Header>
});
}
您还可以更改参数名称,甚至将其设置为具体的名称。
use kobold::prelude::*;
// Capture children into the argument `n`
#[component(children: n)]
fn AddTen(n: i32) -> i32 {
// integers implement `View` so they can be passed by value
n + 10
}
fn main() {
kobold::start(view! {
<p>
"Meaning of life is "
<AddTen>{ 32 }</AddTen>
</p>
});
}
更多示例
要运行 Kobold,您需要安装 trunk
(如有问题,请查看 完整说明)
cargo install --locked trunk
您可能还需要将 Wasm 目标添加到 Rust
rustup target add wasm32-unknown-unknown
然后只需运行示例
## Go to an example
cd examples/todomvc
## Run with trunk
trunk serve
致谢
- Pedrors 为 Kobold 提供了标志。
许可证
Kobold 是免费软件,根据 Mozilla 公共许可证 2.0 版本发布。请参阅 LICENSE。
依赖项
~7–9.5MB
~175K SLoC