9 个版本

0.0.9 2022 年 11 月 20 日
0.0.8 2022 年 11 月 19 日
0.0.7 2022 年 9 月 29 日
0.0.5 2022 年 5 月 23 日
0.0.2 2020 年 4 月 17 日

#1 in #spa

MPL-2.0 许可证

360KB
10K SLoC

Spair

Crates.io docs.rs Build

Spair 是一个用于 Rust 中单页应用的增量且细粒度的渲染前端框架。

该项目处于 早期阶段,一些功能可能尚未完善,且可能会出现 频繁的破坏性更改

Spair 既小巧又快速。Spair 的性能与 Leptos、Dominator 和 Sycamore 相当。 请在此查看基准测试。Spair 的应用程序大小小于一些其他框架。 请在此查看比较

特性

  • 增量渲染
    • 渲染方法在第一次运行时创建 DOM 树,然后在后续运行中更新它。
  • 队列渲染以进行细粒度渲染。
    • 必须在 Cargo.toml 中启用,例如: spair = { version="x.y.z", features = ["queue-render"] }
    • (它只是一个队列渲染,不使用任何类型的信号)
    • 您必须将您的值包裹在 spair::QrValspair::QrVec
    • 当前版本的队列渲染可能不太高效,因为每个细粒度渲染都需要单独借用组件状态。
  • 组件状态可以在渲染代码的任何部分访问。
    • Spair 的组件通常较大,Render 用于代码分割。
    • 您可以在每个 Render 中访问组件状态,而不必传递它。
    • 也可以从队列渲染中访问组件状态。
  • 构建 DOM 时不需要(几乎)任何宏。
    • 但是因为这一点,Spair相当冗长。
    • (但可以在Spair之上构建宏)。
    • spair::set_arm!() 是唯一需要使用的宏,它可以节省你的时间,(没有它, match_if 对于维护者来说就像一场噩梦)
  • 路由
    • 目前只是基本支持,您必须自己实现路由逻辑。
  • 异步命令
  • svg
  • 这里那里缺少一些东西...
    • 嗯,这显然不是一个功能。我只是把它放在这里,提醒潜在用户不要对缺少的功能感到惊讶 :D。
    • 例如,Spair目前仅实现了少量事件。

不支持(目前),你想要贡献吗?

这些功能在我的待办事项列表中极其低(甚至没有)。

  • #[derive(spair::Routes)]
  • 事件委托,在 features = ["event-delegation"] 如果它被实现的话。
    • 但您始终可以手动完成
  • SSR(在功能标志下,也是)
  • RSX宏(在功能标志下,也是)

Cargo功能

您可以在Cargo.toml中这样启用一个功能:spair = { version="x.y.z", features = ["feature-name"] }

feature-name description
keyed-list 支持 keyed-list 的增量模式
svg 支持svg元素
queue-render 支持细粒度渲染 (*)

(*) 通过queue-render渲染的列表总是带键的。

运行示例

先决条件

  • Rustwasm32-unknown-unknown 目标。
  • 主干

在示例文件夹中

trunk serve

或者,如果它很慢,使用:(尤其是 examples/boidsexamples/game_of_life

trunk serve --release

在您的浏览器中打开 https://127.0.0.1:8080

文档

尚未。现在最好的起点是 /examples/*

以下部分提供了Spair的第一印象。

静态模式和更新模式

Spair通过迭代当前DOM中的每个元素和属性/属性来工作,在第一次渲染之前DOM是空的,创建新项目或修改现有项目,这是更新模式。但是有一些元素或属性永远不会改变。您可以告诉Spair只创建它们,但在以后迭代它们时忽略它们,通过开启静态模式。

items update-mode static-mode notes
attributes / properties default .static_attributes() 在完成更新模式属性后调用 .static_attributes()
elements default.update_nodes() .static_nodes() 仅适用于元素(包括 .relement()),适用于文本/可渲染项
文本/可渲染项 .rupdate() .rstatic() 不受由 .update_nodes().static_nodes() 引入的模式的影响
  • .update_nodes().static_nodes() 可以根据需要多次切换。
  • 再次提醒,.relement() 受到 .update_nodes().static_nodes() 的影响。
element
    // default to update-mode attributes
    .value(&some_value) // will be checked and updated if changed
    .class_if("class-name", bool_value)
    .static_attributes() // we are done with update-mode attributes!
    .class("class-name") // class="class-name" is added on creation, but ignored on subsequence renders
    // just add child-elements, default to update mode.
    .p(|p| {}) // create and update a <p>
    .rupdate(value) // create and update a text
    .rstatic(value) // a create-only text - not affected by update-mode (default).
    .static_nodes()
    .div(|d| {}) // a create-only <div> (because creating in static-mode)
    .rupdate(value) // an updatable text - not affected by `.static_nodes()`
    .rstatic(value) // a create-only text - because of `rstatic`, not cause by `static_nodes`

  • 重要说明:当以静态模式创建元素时,所有内容在首次渲染后都将被忽略(不更新)。
element
    .static_nodes() // Elements append after this will be in static-mode
    .p(|p| {
        // This closure only execute once on the creation of <p>.
        // In the future update, this closure will be IGNORED,
        // therefore, all child-nodes of <p> will NOT be updated despite
        // being created in update-mode.
        p.span(|s| {})
            .rupdate(value); // NEW VALUE OF `value` WILL NEVER BE RENDERED.
    });

示例

/examples 中查找完整示例

这是 render 方法,位于 examples/counter

impl spair::Component for State {
    type Routes = ();
    fn render(&self, element: spair::Element<Self>) {
        let comp = element.comp();
        element
            .static_nodes()
            .p(|p| {
                p.static_nodes()
                    .rstatic("The initial value is ")
                    .rstatic(self.value);
            })
            .rstatic(Button("-", comp.handler(State::decrement)))
            .rupdate(self.value)
            .rstatic(Button("+", comp.handler(State::increment)));
    }
}

RenderStaticRender 特性

您可以通过在数据类型上实现 RenderStaticRender 来将代码拆分成小块,并将值传递给 .rupdate().rstatic()

RenderStaticRender 已用于原始数据类型(i8,...,u64f32f64boolusizeisize)。它们被简单地转换为字符串并作为文本节点渲染。

访问组件状态。

在实现 RenderStaticRenderElementRender 时,您可能需要访问组件的状态

impl spair::Render<State> for &YourType {
    fn render(self, nodes: spair::Nodes<State>) {
        let state = nodes.state(); // type of `state` is `&State`

        nodes.rupdate(state.value);
    }
}

协调?- 不,您必须使用 .match_if()

Spair 不执行对账,用户必须自行进行。当找不到预期的元素时,Spair 会创建它,但如果 Spair 在预期的索引处找到了元素,Spair 就假设它就是预期的元素。因此,当您要根据条件渲染不同的元素时,必须通过 .match_if() 来告诉 Spair 进行操作。

以下代码是从 examples/fetch/src/lib.rs 中提取的。

element
    .match_if(|mi| match self.branch.as_ref() {
        Some(branch) => spair::set_arm!(mi) // `spair::set_arm!()` use `line!()` internally to set `render_on_arm_index()`
            // Render the content of `Some(branch)`
            .rupdate(branch)
            // some code removed
            .done(),
        None => spair::set_arm!(mi)
            // There is no value: `None`? Then just render a button
            .button(|b| {/* some code removed */})
            .done(),
    })

不要这样做,它不起作用

if some_condition {
    element.div(|d| {})
} else {
    element.p(|p| {})
}

子组件

示例:examples/components

注意事项

名称与 Rust 关键词冲突

Spair 中将 HTML 的标签和属性实现为方法。与 Rust 关键词冲突的名称使用原始标识符实现,例如 r#typer#for...

具有相同名称的元素和属性/属性。

存在名为 <span><label>... 的元素,也存在名为 spanlabel... 的属性,Spair 将它们都实现为方法。显然,它们无法在同一个对象上实现。实际上,Spair 使用特性来实现这些功能,但仍然存在冲突。

因此,要立即添加具有此类名称的元素,请调用 .update_nodes().static_nodes()

要设置具有此类名称的属性/属性,您必须首先调用 .attributes_only().static_attributes_only()。设置属性/属性后,您必须显式使用 .update_nodes().static_nodes() 切换到节点模式。

示例

element.span(); // => Error

element
    .update_nodes()
    .span(); // Element <span>  

element
    .attributes_only()
    .span() // attribute
    .update_nodes()
    .span(); // Element <span>  

常见错误

使用 Spair,您可能会遇到本节中列出的常见错误。这些问题真的很烦人。如何避免这些问题?

static_attributes()static_nodes()

如果在静态模式下设置属性或添加节点,它将永远不会更新。很容易将更新模式的项目错误地放置在静态模式下。例如,您有一个应用程序,并且已经将所有认为静态的内容转换为静态模式。过了一段时间后,您决定添加一些您希望在更改时更新的内容。但是,您没有注意到这个 DOM 树的分支位于静态模式下。最后,当您测试应用程序的新版本时,最初您可能会挠头并多次检查,因为它已经被渲染,但值永远不会更新。

依赖关系

~7–9.5MB
~177K SLoC