#web #web-interface #javascript

kobold_macros

Kobold宏

12个版本 (破坏性更改)

0.9.0 2023年5月26日
0.7.1 2023年4月18日
0.6.0 2023年3月28日
0.4.0 2022年6月30日
0.1.0 2021年8月8日

#1318 in WebAssembly


用于 2 个crate(通过 kobold

MPL-2.0 许可证

125KB
3K SLoC

Kobold logo

Kobold

Join Discord Test Docs Crates.io MPL-2.0

简单的声明式Web界面。

关键特性

  • 使用类似HTML语法的声明式宏 view!,包含可选的关闭标签。
  • 具有可选参数的函数式组件。
  • 状态管理和事件处理。
  • 高性能,在Rust生态系统中的Wasm占用空间始终最低。

零成本静态HTML

view! 宏生成不透明的 impl View 类型,默认不进行分配。所有静态 DOM 元素编译成内联JavaScript代码,用于构建它们。表达式在首次渲染时注入到构建的DOM中。Kobold跟踪这些表达式的DOM节点引用。

由于表达式评估的确切类型是Rust编译器所知的,更新调用可以通过值(或指针)进行diff,并在它们更改时进行外科手术式的DOM更新。更改字符串或整数仅更新渲染该字符串或整数的确切 Text 节点

如果 view! 宏调用包含没有表达式的DOM元素,则构建的 View 类型将为零大小,其 View::update 方法将为空,使静态DOM的更新实际上零成本。

你好,世界!

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! 宏生成唯一的临时类型,因此调用该宏的 ifmatch 表达式将无法编译。

#[component] 属性上使用 auto_branch 标志时,Kobold 将扫描组件渲染函数的主体,并将所有位于 ifmatch 表达式内的 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

依赖关系

~170KB