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
360KB
10K SLoC
Spair
Spair 是一个用于 Rust 中单页应用的增量且细粒度的渲染前端框架。
该项目处于 早期阶段,一些功能可能尚未完善,且可能会出现 频繁的破坏性更改。
Spair 既小巧又快速。Spair 的性能与 Leptos、Dominator 和 Sycamore 相当。 请在此查看基准测试。Spair 的应用程序大小小于一些其他框架。 请在此查看比较。
特性
- 增量渲染
- 渲染方法在第一次运行时创建 DOM 树,然后在后续运行中更新它。
- 队列渲染以进行细粒度渲染。
- 必须在 Cargo.toml 中启用,例如:
spair = { version="x.y.z", features = ["queue-render"] }
- (它只是一个队列渲染,不使用任何类型的信号)
- 您必须将您的值包裹在
spair::QrVal
或spair::QrVec
中 - 当前版本的队列渲染可能不太高效,因为每个细粒度渲染都需要单独借用组件状态。
- 必须在 Cargo.toml 中启用,例如:
- 组件状态可以在渲染代码的任何部分访问。
- 构建 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渲染的列表总是带键的。
运行示例
先决条件
在示例文件夹中
trunk serve
或者,如果它很慢,使用:(尤其是 examples/boids
或 examples/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)));
}
}
Render
和 StaticRender
特性
您可以通过在数据类型上实现 Render
或 StaticRender
来将代码拆分成小块,并将值传递给 .rupdate()
或 .rstatic()
。
Render
和 StaticRender
已用于原始数据类型(i8
,...,u64
,f32
,f64
,bool
,usize
,isize
)。它们被简单地转换为字符串并作为文本节点渲染。
访问组件状态。
在实现 Render
、StaticRender
或 ElementRender
时,您可能需要访问组件的状态
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#type
、r#for
...
具有相同名称的元素和属性/属性。
存在名为 <span>
、<label>
... 的元素,也存在名为 span
、label
... 的属性,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