33 个版本

0.1.14 2024年8月3日
0.1.12 2024年7月30日
0.0.18 2023年9月10日
0.0.17 2022年12月27日
0.0.14 2022年6月27日

#261 in 网页编程

Download history 2/week @ 2024-05-27 920/week @ 2024-06-10 217/week @ 2024-06-17 493/week @ 2024-06-24 25/week @ 2024-07-01 8/week @ 2024-07-22 639/week @ 2024-07-29 33/week @ 2024-08-05

每月680次下载

Apache-2.0

190KB
5K SLoC

Respo 在 Rust 中

Respo Crate

Rust 的微型玩具虚拟 DOM 基础框架。

状态:实验中,没有 HMR (热代码替换) 就不开心。

Respo最初是为与持久数据一起工作的动态语言以及HMR(热代码替换)而设计的,这与Rust截然不同。因此,这更像是一个实验。

使用方法

以下是 DOM 语法的预览

Ok(
  div()
    .class(ui_global())
    .style(respo_style().padding(12.0))
    .children([
      comp_counter(&states.pick("counter"), store.counted)?,
      comp_panel(&states.pick("panel"))?,
      comp_todolist(memo_caches, &states.pick("todolist"), &store.tasks)?,
    ]),
)

Rust 中的 CSS

static_styles!(
  style_remove_button,
  (
    "&",
    respo_style()
      .width(16.px())
      .height(16.px())
      .margin(4.)
      .cursor("pointer")
      .margin4(0.0, 0.0, 0.0, 16.0)
      .color(CssColor::Hsl(0, 90, 90)),
  ),
  ("&:hover", respo_style().color(CssColor::Hsl(0, 90, 80))),
);

内置样式,演示

函数 用法
ui_global 全局样式
ui_fullscreen 全屏样式
ui_button 按钮样式
ui_input 输入样式
ui_textarea 文本区域样式
ui_link 链接样式
ui_flex flex:1 样式
ui_expand flex:1 样式,带滚动条
ui_center flexbox 居中样式
ui_row flexbox 行样式
ui_column flexbox 列样式
ui_row_center flexbox 行居中样式
ui_column_center flexbox 列居中样式
ui_row_around flexbox 行环绕样式
ui_column_around flexbox 列环绕样式
ui_row_evenly flexbox 行平均样式
ui_column_evenly flexbox 列平均样式
ui_row_parted flexbox 行分隔样式
ui_column_parted flexbox 列分隔样式
ui_row_middle flexbox 行分隔样式
ui_column_middle flexbox 列分隔样式
ui_font_code 代码字体家族
ui_font_normal 正常字体家族(Hind)
ui_font_fancy 花体字体家族(Josefin Sans)

演示中包含几个对话框组件。语法还不够优雅,所以我不做宣传。但它们的工作效果相对不错。

更多组件,请阅读 src/app/ 中的代码,它们只是像 RespoNode::Component(..) 这样的变体。将来可能会进行简化,目前尚未确定。

存储抽象

声明存储

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Store {
  pub states: RespoStatesTree,
  // TODO you app data
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ActionOp {
  // TODO
  StatesChange(RespoUpdateState),
}

impl RespoAction for ActionOp {
  type Intent = (); // Intent is optional, it's for async actions.
  fn states_action(a: RespoUpdateState) -> Self {
    Self::StatesChange(a)
  }
}

impl RespoStore for Store {
  type Action = ActionOp;

  fn update(&mut self, op: Self::Action) -> Result<(), String> {
    match op {
      // TODO
    }
    Ok(())
  }
}

声明应用

struct App {
  store: Rc<RefCell<Store>>,
  mount_target: Node,

}

impl RespoApp for App {
  type Model = Store;

  fn get_store(&self) -> Rc<RefCell<Self::Model>> {
    self.store.to_owned()
  }
  fn get_mount_target(&self) -> &web_sys::Node {
    &self.mount_target
  }

  fn dispatch(store: &mut RefMut<Self::Model>, op: Self::Action) -> Result<(), String> {
    store.update(op)
  }

  fn view(store: Ref<Self::Model>, memo_caches: MemoCache<RespoNode<Self::Action>>) -> Result<RespoNode<Self::Action>, String> {
    let states = &store.states;
    // util::log!("global store: {:?}", store);

    Ok(
      div()
        .class(ui_global())
        .style(respo_style().padding(12.0))
        .children([
          comp_counter(&states.pick("counter"), store.counted)?,
          comp_panel(&states.pick("panel"))?,
          comp_todolist(memo_caches, &states.pick("todolist"), &store.tasks)?,
        ]),
    )
  }
}

挂载应用

let app = App {
    mount_target: query_select_node(".app").expect("mount target"),
    store: Rc::new(RefCell::new(Store {
      counted: 0,
      states: RespoStatesTree::default(),
      tasks: vec![],
    })),
  };

  app.render_loop().expect("app render");

许可证

Apache 许可证 2.0。

依赖项

~12MB
~202K SLoC