接下来的章节将带你深入了解这门语言背后的设计思路以及它所包含的核心概念。
正如上面视频所介绍的,Slint 声明式 UI 语言被设计为一种简单而强大的方式,用来创建 你能想象到的任何用户界面。
Text { text: "Hello World!"; font-size: 24px; color: #0044ff;}这个示例展示了 Slint 工作的核心。使用元素时,依次写出元素名称以及一对大括号, 例如 Text {}。然后在大括号内通过属性进行定制,例如 font-size: 24px。
要将元素相互嵌套,把它们放在父元素的大括号内即可。例如,下面这个 Rectangle 有一个 Text 元素作为其子元素。
Rectangle { width: 150px; height: 60px; background: white; border-radius: 10px;
Text { text: "Hello World!"; font-size: 24px; color: black; }}
最后一项核心内容是绑定表达式:
1property <int> counter: 0;2
3Rectangle {4 width: 150px;5 height: 60px;6 background: white;7 border-radius: 10px;8
9 Text {10 text: "Count: " + counter;11 font-size: 24px;12 color: black;13 }14
15 TouchArea {16 clicked => {17 counter += 1;18 }19 }20}在这个示例中,声明了一个名为 counter 的属性。然后 Rectangle 内部 有一个 TouchArea,它会自动填充其父元素并响应点击或轻触操作。点击时, 它会对 counter 进行自增。这正是细粒度响应式(fine grained reactivity)的魔力所在。
Text 元素的 text 属性依赖于 counter 属性。当 counter 发生变化时,UI 会自动 更新。无需显式启用。在 Slint 中,每个表达式都会自动被重新求值。 但这一过程是以一种高效的方式完成的,只会更新那些依赖项发生变化的部分。
Slint 使得通过组合内置元素或其他组件来创建你自己的组件变得轻而易举。 Text 和 Rectangle 可以变成按钮。按钮和输入字段可以组成表单或对话框。表单和对话框可以成为视图(Views)。 最终,视图组合在一起就形成了应用程序。
export component MyView { MyDialog { title: "Can UI Development Be Easy?";
MyButton { text: "Yes"; } }}通过不断练习,你可以将任意复杂度的内容简化为简单且易于维护的 UI 组件。
为什么选择 Slint?
本指南的“概念”部分旨在从高层面向你概述这门语言。如果你想 直接深入了解细节,那么请查看 编码章节 以及语言参考 部分。
Slint 语言以声明式的方式描述应用程序的用户界面。
用户界面与抽象代码截然不同。它由 文本、图像、颜色、动画等组成。尽管这看起来是一种幻象,按钮和元素在 物理世界中其实并不存在,但它们的目的是表现得仿佛真实存在一样。按钮具有按下效果, 列表可以被轻扫并表现得好像真的具有惯性一样。在设计时,它们是用 UI 组件、表单和视图来描述的。
与此同时,代码世界则是一种相当不同的抽象。它由函数、变量等组成。 即使某些 UI 元素确实存在,比如按钮和菜单,它们也伴随着大量代码来 管理其实现细节。
const button = document.createElement('button');button.textContent = 'Click me';document.body.appendChild(button);看看这个简单的 Web 示例。创建了一个按钮。然后为其设置一个显示“Click me”文本的属性。在这一点上 从技术上来说按钮是存在的,但由于它并未附加到任何位置,所以不会显示出来。因此最后一行 将其作为主视图的子元素添加进去。
const buttonWithListener = document.createElement('button');buttonWithListener.textContent = 'Click me';buttonWithListener.addEventListener('click', () => { console.log('Button clicked!');});document.body.appendChild(buttonWithListener);在第二个示例中,创建了一个按钮并添加了一个事件监听器。其中包含一个回调 函数,用于在按钮被按下时输出日志。用这么多代码来完成简单的事情是相当繁琐的。
这种方式相当抽象。例如一旦再添加几个按钮和组件,几乎不可能 去想象真实的界面会是什么样子。很难用这种代码来思考 UI 的设计。
这种方式也太复杂了,以至于不懂代码的人无法编辑。这意味着 UI 设计师无法 亲自参与以确保他们的设计意图被完整实现。他们被迫使用其他工具和框架 来创建原型和设计指南,而这些原型和指南的外观或行为可能与实际的 UI 实现有所不同。 事情本不必如此。
声明式风格
已经有一些尝试让用代码描述 UI 的过程变得更加声明式。例如 React 和 SwiftUI。
function ContentView() { return ( <p style={{ fontSize: '2rem', color: 'green' }}> Hello World </p> );}jsx
struct ContentView: View { var body: some View { Text("Hello World") .font(.title) .foregroundColor(.green) }}swift
这些语言采用常规代码,并以声明式的方式使用它。但它们 本质上仍然是带参数的函数,参数充当属性。它更简单,并且 父子关系等行为可以被自动推断。
对 Slint 而言,我们并未对一门常规语言进行修补以使其更具声明式,而是 从零开始打造了一种纯声明式语言。
业务逻辑问题
解决“在代码中描述 UI”这一问题的一种尝试是创建一种独立的静态标记语言。 Android 和 WPF 等平台就采用了这种方式。UI 以类似 XML 的格式进行描述。其余代码则使用一种独立的语言编写,并放在 单独的文件中——这被称为“代码隐藏文件”(code behind file)。问题在于,尽管有相反的说法,XML 实际上既不 人类可读,也难以编辑。它过于静态和死板,而且在 UI 文件和 单独的代码隐藏文件之间来回切换以描述 UI 行为令人非常沮丧。
与此同时,React Web 框架通过使用 JSX 解决了这个问题。HTML、CSS 和 JavaScript 可以混合在 同一个文件中。一方面这很好,因为它意味着你可以使用同一种语言来描述 UI 的布局和行为。另一方面,你可以向 JSX 文件中放入任何代码——处理 网络请求、处理数据的逻辑以及几乎所有内容很快就会与 UI 代码混杂在一起。这导致 的问题是,创建初始应用时可能很快,但维护起来会变得极其困难,以至于 对应用进行演进的成本或速度慢得让人望而却步。
真正的声明式 UI 语言
Slint 提供了一种声明式语言来描述应用程序的用户界面
1Rectangle {2 Button {3 text: "Click me!";4 clicked => {5 debug("Button clicked!");6 }7 }8}如果你能读懂这段 Slint 代码——那么你已经掌握了该语言大部分的用法。 在第 2 行我们声明了一个 Button。在第 3 行我们将其 text 属性设置为 "Click me!",在第 4 行 我们设置了一个回调,通过内置的 debug 函数将 "Button clicked!" 输出到控制台。
Slint 编译器会分析这段代码,并发现它需要生成一个以 Button 为子元素的 Rectangle。 类似地,当 clicked 回调被触发时,它会运行 debug 函数。无需关心 组件和事件监听器的生命周期。
使用 Slint 语言,你可以更贴近用户界面的外观和行为来思考问题。你不是 在描述“如何做”——即在传统代码中如何实现这些细节——而是“声明” 界面应有的外观与行为。因此,Slint 是一种“声明式 UI 语言”。
它不仅对软件开发人员来说更简单,现在设计师也可以参与编辑或 更轻松地为项目作出贡献。
Slint 并不是第一种声明式 UI 语言,但它汲取了早期那些更 复杂尝试的经验,这些尝试暗示了声明式 UI 语言的潜力,并最终形成了一个现代而完整的 系统。
乍看之下,它具有静态标记语言的简洁性,同时又采用了现代化的设计——去掉了尖括号和标签 这些繁文缛节。它还具备动态特性,例如复杂的属性表达式、函数、回调, 以及自动响应式机制。然而,这些特性只能用于有助于构建 UI 组件的范围内。如果你想 发起网络请求、处理数据或执行许多其他属于“业务逻辑”的操作,那么这些逻辑必须 放在用 Rust、C++、JavaScript 等语言编写的单独文件中。Slint 让你能够表达任何 UI,并且仅限于 UI。
随后它提供了一套适配器系统,使应用的业务逻辑一侧能够轻松地与 UI 进行通信, 反之亦然。