6个版本 (重大变更)
0.9.0 | 2023年5月26日 |
---|---|
0.8.0 | 2023年4月28日 |
0.7.1 | 2023年4月18日 |
0.6.0 | 2023年3月28日 |
0.5.0 | 2023年3月22日 |
#866 在 WebAssembly 中
每月31次下载
130KB
2.5K SLoC
Kobold
易用的声明性Web界面。
主要功能
零成本静态HTML
view!
宏生成不透明的 impl View
类型,默认不进行分配。所有静态 DOM 元素都编译为内联JavaScript代码来构造它们。表达式在首次渲染时注入到构造的DOM中。Kobold会跟踪这些表达式的DOM节点引用。
由于Rust编译器知道表达式评估的确切类型,更新调用可以通过值(或指针)来diff它们,并在它们更改时进行外科手术式的DOM更新。更改字符串或整数仅更新渲染的字符串或整数的确切 Text
节点。
如果 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} })
}
}
}
在更新时,迭代器会消耗一次,并且所有项目都会与先前版本进行 diff。除非渲染的列表需要超出其原始容量,否则 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。
依赖关系
~10MB
~182K SLoC