跳到主要内容
版本:0.2.x

简介

在本章中,我们会使用 Ribir 语法撰写一些简单例子。你只需要理解大意即可,不必深究。我们会在后面的章节中详细介绍。

Ribir 是什么?

Ribir 是一款用于构建现代界面的 Rust 开源框架。一次编写,即可以编译成不同平台的原生应用。

Ribir 使用了一种非侵入的声明式编程模型,让你可以将用户界面作为一个独立模块来开发和设计。

它的核心设计理念是:

界面是对数据交互的再描述,并持续响应数据的变化。

我们强调“再”是我们认为 API 是数据交互的第一描述,而使用 Ribir 构建交互界面只需基于数据的 API。

为什么选择 Ribir ?

非侵入的编程模型

Ribir 只和你数据的 API 交互,不需要你的数据为用户界面做任何预先设计:

  • 不用额外的状态
  • 不用额外的通知机制
  • 不用继承任何基类
  • 没有其它任何预先约束

它不会破坏你已有数据的逻辑和结构,也无需注入任何额外的对象。在做应用核心部分开发时,你可以专注于设计应用的数据、逻辑和 API,而完全不用考虑 UI。

界面直接操作数据,数据变更直接驱动界面更新,没有其它中间层和概念。

多端一致体验,且易于扩展到新的平台

Ribir 可以用来开发桌面端、移动端、Web 端和服务端渲染应用,它生成高效的二进制代码或 WASM 程序,无需依赖任何运行时环境。它输出极简单的、平台无关的绘制结果,让你可以选择完全由 GPU 或 CPU 进行渲染。你甚至可以很容易地实现自己的渲染后端来扩展到未覆盖的平台。

易于与 Rust 交互的声明式语法

Ribir 提供了一套易于与 Rust 交互的声明式语法,它不是一个新的语言,而是一组 Rust 宏。因此它能很好的与 Rust 交互,使得你的代码兼具清晰的视图描述和强大的逻辑表达,同时没有任何环境和工具依赖。

点对点的视图更新策略

Ribir 会根据你对数据的描述映射出一颗视图树,视图则会响应数据的修改保持更新——该更新并不是重建整个视图,而是点对点的更新视图中依赖被修改数据的部分。

更新逻辑在编译期就已经确定,不会有任何通用的 diff 或 patch 算法在运行时执行。

“Pay-as-you-go” 的设计原则

因为要面对各种复杂的真实场景,一个通用的 GUI 框架往往会有复杂的设计和丰富的能力,因此很难做到轻量。而 Ribir 来平衡这一问题的方式是:提供足够的能力来保证开发效率,同时要求所有能力都只有在用到的时才需要被了解和产生开销。几个例子:

纯组合:Ribir 通过 widget 来构建界面,与常见的基于面向对象的 GUI 框架不一样,Ribir widget 不需要继承一个基类或持有一个基础对象。它是一个纯组合的模型,即使是父子关系和内建的字段也是通过组合的方式完成的。这样的好处是, widget 只需专注自己本身提供的能力,因此 可以做的非常小,提高复用。比方 Ribir 有很多非常 mini 的内建 widget,并用这些内建 widget 将普通 widget 扩展的能力强大,但并不会给它们带来开销。举个例子:

use ribir::prelude::*;

fn_widget!{
@Text {
// `margin` 不属于 `Text`,它是内建 widget `Margin` 的字段,
// 但仍可以直接被 `Text` 使用。
margin: EdgeInsets::all(8.),
text: "Hello world!"
}
};

上面的例子展示了内建 widget 组合的方式,即使 Text 没有一个 margin 字段,但仍可以使用 Margin::margin 字段,并和它组合成一个新的 widget 。当一个 widget 使用了 margin 字段,Margin 才会被创建,否则不会有任何开销。

消化 compose widget: 在描述数据的视图时,除了一些基础 widget,多数情况 widget 是由其它 widget 组合而成。例如一个 Button,它由 TextIconBoxDecoration 等 widget 组合而成, Button 本身不是一个视图元素,我们称这类 widget 为 compose widget 。Compose widget 在视图构建时会被消化掉,它们就像一个函数一样,在构建视图的时候被调用一次,构建出最终的视图并创建好相应的更新逻辑,并不存在于最终的视图中。

没有写入源的状态会退化成数据: 和其它的声明式框架在 widget 中增加字段来控制 widget 的更新不一样。Ribir 是非侵入的,Ribir 将整个 widget 作为一个状态来控制更新。同时提供了状态分裂的能力,使视图的局部可以直接依赖部分数据的变更来更新(后续教程中具体介绍)。另一个大的差别在于——有状态和无状态的是可以互相转换的。如果一个状态没有任何写入源,它将退化成无状态的,因为不会有任何人去更新它。例如:

use ribir::prelude::*;

fn_widget!{
let show_hi = Stateful::new(true);
@Text {
visible: pipe!(*$show_hi),
text: "Hello world!"
}
};

在上面的例子中,我们声明了一个 Text,并使用 pipe! 宏将 Text 的可见性直接与 show_hi 关联起来。但这个关联在构建视图时会被消除,因为 show_hi 始终保持不变 —— 它没有任何写入源。因此,Ribir 构建出的将是一个简单的静态视图。

可靠性

与一般的 GUI 框架通过继承的方式,除了基类继承外没有任何类型约束不同,Ribir 基于 widget 组合的方式来构建视图,并依赖父子 widget 之间的类型来约束能否组合和如何组合,父类性可以规范自己的孩子类型,所以很多错误可以在编译期给出报错,而非运行时检查。

Ribir 的当前状态?

稳定性

Ribir 核心框架已经处于一个基本稳定的状态,API 和语法会以一个谨慎的态度迭代。 widget 库虽然已有不少可用的 widget 但仍处于一个非常粗糙的状态,每个版本仍会有较大的变动。

平台覆盖

0.1 版本只覆盖 Mac、Linux 和 Windows 平台。你可以尝试将工程编译到对应的移动端和 Web 端,不过它们尚未经过任何验证。

性能

在整个框架的所有重要设计中,性能都是我们考虑的重要因素。根据我们观察到的真实开发项目的表现,整体体验符合预期。我们预计最终它将具有出色的性能表现。但坦率地说,我们从未进行过任何详细的性能测量和分析,因此我们还没有进行过针对性的代码优化工作,我们预计这项工作将在全平台覆盖和 widget 库相对稳定后全面展开——或者我们在这之前遇到了明细体验上的性能瓶颈点。

谁在使用 Ribir?

Polestar Chat:

Sisyphus: 一个用来编辑可交互文档的编辑器,这是一个长期项目,目前仍然处在早期的设计和开发阶段,正是这个项目的构想导致了 Ribir 的诞生。