#data-model #frontend-framework #container #virtual-dom #ui #object #dodrio

afterglow

一个基于typed-html和dodrio构建的Rust前端框架。使用特对象来解耦视图/突变行为和数据模型。

5个版本

0.3.0 2020年10月28日
0.1.3 2020年5月30日
0.1.2 2020年5月30日
0.1.1 2020年5月30日
0.1.0 2020年5月30日

432WebAssembly

每月 22 次下载

Apache-2.0/MIT

29KB
771

Afterglow

Afterglow是一个基于 dodriotyped-html 的实验性Rust前端框架

特性

  1. 虚拟DOM

    使用dodrio提供的vdom来处理重渲染。

  2. 类似JSX的语法

    使用typed-html提供的类似JSX的宏来创建视图。

  3. 受Elm启发的容器

    每个数据容器都有自己的生命周期,只有在选择的情况下才会触发重渲染。

  4. 使用特对象来鼓励在UI探索上的自由度

    虽然拥有严格的数据模型并提供与严格视图/突变集紧密结合的视图是维护组件整洁概览的好方法,但很多时候构建前端将需要更灵活的工作流程。这意味着对于相同的数据模型,项目中的不同用例可能有多个,并且每个用例都需要一组专有的数据突变。在Renderer和Messenger上使用特对象允许容器对渲染和突变的时间和方式有更少的约束。

  5. 依赖于消息总线在异构容器之间共享事件

    通过将容器的消息发送者注册到一个集中式总线,容器可以为其设计对总线事件的响应。因此可以实现父子、兄弟通信。

示例

假设你想要将相同的数据作为视觉元素重用,

use crate::prelude::*;

#[derive(Default)]
pub struct Model {
    status: bool,
    clicked: i32,
}

impl LifeCycle for Model {
    fn new(render_tx: Sender<()>) -> Self {
        Model::default()
    }
}

同时保持一个处理数据突变和业务逻辑的单个容器,我们可以创建多组具有自身UI逻辑封装的视图。

struct TableView;
impl Renderer for TableView {
    type Target = Model;
    type Data = Model;

    fn view<'a>(
        &self,
        target: &Self::Target,
        ctx: &mut RenderContext<'a>,
        sender: MessageSender<Self::Data>,
    ) -> Node<'a> {
        let bump = ctx.bump;
        dodrio!(bump,
            <table class="table">
                <thead>
                    <tr>
                        <th>"counts"</th>
                        <th>"status"</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>{ text(bf!(in bump, "clicked: {} times", target.clicked).into_bump_str())}</td>
                        <td>{ text(bf!(in bump, "status: {}", target.status).into_bump_str())}</td>
                    </tr>
                </tbody>
            </table>
        )
    }
}


struct ButtonView;
impl Renderer for ButtonView {
    type Target = Model;
    type Data = Model;

    fn view<'a>(
        &self,
        target: &Self::Target,
        ctx: &mut RenderContext<'a>,
        sender: MessageSender<Self::Data>,
    ) -> Node<'a> {
        let bump = ctx.bump;
        dodrio!(bump,
            <div
            onclick={ consume(|e: web_sys::Event| { ClickMsg::Clicked }, &sender)}
            class="button">"clicked to increase stats"</div>
        )
    }
}

并且不需要定义一个巨大的枚举让容器处理所有传入的事件,我们可以有一组仅包含相关变体的Messenger集合。以后可以定义新的,而不必更改模型的实现。


pub enum ClickMsg {
    Clicked,
}
impl Messenger for ClickMsg {
    type Target = Model;

    fn update(
        &self,
        target: &mut Self::Target,
        sender: MessageSender<Self::Target>,
        render_tx: Sender<()>,
    ) -> bool {
        match self {
            ClickMsg::Clicked => {
                target.clicked += 1;
                target.status = !target.status;
                true
            }
        }
    }
}

通过视图提供者,即Renderer,与数据模型解耦,我们可以更自由地组合页面。用户可以将其UI逻辑分离到其自己的视觉元素中。

#[derive(Default)]
struct MainView;
impl Renderer for MainView {
    type Target = Model;
    type Data = Model;

    fn view<'a>(
        &self,
        target: &Self::Target,
        ctx: &mut RenderContext<'a>,
        sender: MessageSender<Self::Data>,
    ) -> Node<'a> {
        let bump = ctx.bump;

        dodrio!(bump,
            <div>
                <div class="hero is-light">
                    <div class="hero-body">
                        <div class="container">
                            <div class="card">
                                <div class="card-content">
                                { TableView.view(target, ctx, sender.clone())}
                                </div>
                                <div class="card-footer">
                                    <div class="card-footer-item">
                                    { ButtonView.view(target, ctx, sender)}
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <link rel="stylesheet" 
                    href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.css"/>
            </div>
        )
    }
}

因此,根据定义,一个应用程序是一个给定的数据模型,它有一个默认的Renderer。

pub fn init_example() {
    Entry::init_app::<Model, MainView>("app");
}

依赖项

~12–24MB
~360K SLoC