【Freya UI 0.4 基础语法学与练】 第1课:Builder 模式从零搭建桌面应用
2026/9/8 15:59:46 网站建设 项目流程

引言

Freya 0.4 和 0.3 有着本质区别

Freya 0.4 是一次彻底重写。最大的变化是:
对比项:
v0.3(稳定版)
v0.4(开发版)
底层依赖:
依赖 Dioxus(VDOM、RSX 宏、Hooks)
完全独立,自带响应式核心
UI 写法:
rsx! { rect { … } } 宏语法
rect().width(…).child(…) Builder 链式调用
状态管理:
use_signal
use_state(自带 read() / write())
依赖数量:
需要同时引入 freya + dioxus
只需 freya 一个依赖
宏:
大量使用 rsx!、#[component] 等
完全不用宏,纯 Rust 代码

简单说:0.4 的写法就是纯 Rust 方法链,没有任何宏魔法,IDE 补全更友好,编译报错也更清晰。

⚠️ Freya 0.4 截至 2026 年 3 月仍处于 alpha 阶段(0.4.0-alpha.3),API 可能继续变化。本教程基于该版本的公开示例和文档编写。

环境准备

前提条件: 已安装 Rust 工具链(rustup)

平台额外依赖:
平台 需要安装
Windows Visual Studio Build Tools(推荐 MSVC 工具链)
macOS Xcode 命令行工具:xcode-select --install
Linux fontconfig、libxcb、libxkbcommon、mesa 开发库

创建项目

cargonew freya-hellocdfreya-hello

编辑 Cargo.toml,添加依赖。由于 0.4 尚未发布到 crates.io 稳定版,需从 Git 仓库引入:

[dependencies] freya = { git = "https://github.com/marc2332/freya", branch = "main" }

如果网络访问 GitHub 较慢,可配置 Git 代理或使用镜像。

Hello World —— 第一个窗口

打开 src/main.rs,写入以下代码:

usefreya::prelude::*;fnmain(){launch(LaunchConfig::new().with_window(WindowConfig::new(app).with_size(400.0,300.0)));}fnapp()->implIntoElement{rect().width(Size::fill()).height(Size::fill()).background((30,50,90)).color((255,255,255)).center().font_size(32.0).child("Hello Freya 0.4!")}

运行:

cargorun

编译完成后会弹出一个 400×300 的窗口,深蓝色背景上居中显示白色文字 “Hello Freya 0.4!”。

核心语法逐一拆解

(1)启动流程:launch + LaunchConfig + WindowConfig

launch(LaunchConfig::new()// 创建启动配置.with_window(WindowConfig::new(app)// 指定根组件函数.with_size(400.0,300.0)// 窗口宽高));

三层结构很清晰:

  • LaunchConfig:全局启动配置(后续可加主题、日志等)
  • WindowConfig::new(app):绑定根组件函数 app
  • .with_size(w, h):设置窗口尺寸

(2)根组件函数签名

fnapp()->implIntoElement{...}
  • 返回类型是 impl IntoElement,不是 0.3 的 Element
  • 不需要任何 #[component] 宏标注
  • 就是一个普通的 Rust 函数

(3)Builder 模式构建 UI

0.4 的核心变化——所有 UI 元素都通过函数调用 + 方法链构建:

rect()// 创建一个矩形容器.width(Size::fill())// 宽度撑满父容器.height(Size::fill())// 高度撑满父容器.background((30,50,90))// 背景色 RGB.color((255,255,255))// 文字颜色.center()// 内容水平+垂直居中.font_size(32.0)// 字体大小.child("Hello Freya 0.4!")// 添加子元素(文本)

对比 0.3 的 RSX 宏写法:

// 0.3 宏写法(已淘汰)

rsx!{rect{width:"100%",height:"100%",background:"rgb(30, 50, 90)",color:"white",main_align:"center",cross_align:"center",font_size:"32","Hello Freya 0.4!"}}

Builder 模式的好处:**

  • 纯 Rust 语法,不需要学习特殊的 DSL
  • IDE 自动补全所有可用方法
  • 编译错误信息更精准,直接指向具体方法调用

##(4)常用尺寸类型 Size**
写法 含义
Size::fill() 撑满父容器(等同于 0.3 的 “100%”)
Size::percent(50.0) 占父容器的 50%
Size::flex(1.0) 弹性分配剩余空间
Size::Pixels(100.0) 固定 100 像素

(5)颜色表示

0.4 直接用元组表示颜色,不需要字符串:

.background((30,50,90))// RGB.background((30,50,90,200))// RGBA(第4个是透明度).color((255,255,255))// 白色文字

(6)布局方向与对齐

rect().horizontal()// 子元素水平排列(默认是垂直).spacing(12.0)// 子元素间距 12px.center()// 内容居中.child(...).child(...)

状态管理:use_state

0.4 用 use_state 替代了 0.3 的 use_signal。核心区别是读写分离:

letmutcount=use_state(||0);// 创建状态,初始值为 0count.read()// 读取当前值(返回引用)count.write()// 获取可写引用,修改后自动触发 UI 刷新

完整计数器示例:

usefreya::prelude::*;fnmain(){launch(LaunchConfig::new().with_window(WindowConfig::new(app).with_size(400.0,350.0)));}fnapp()->implIntoElement{letmutcount=use_state(||0);// 上半部分:显示数字letdisplay=rect().width(Size::fill()).height(Size::percent(50.0)).center().background((15,163,242)).color((255,255,255)).font_size(75.0).font_weight(FontWeight::BOLD).shadow((0.0,4.0,20.0,4.0,(0,0,0,80))).child(count.read().to_string());// 下半部分:按钮区域letactions=rect().horizontal().width(Size::fill()).height(Size::percent(50.0)).center().spacing(8.0).child(Button::new().on_press(move|_|{*count.write()+=1;}).child("增加")).child(Button::new().on_press(move|_|{*count.write()-=1;}).child("减少"));// 根容器:上下堆叠rect().child(display).child(actions)}

关键语法点解读:

  • count.read().to_string():读取状态值并转为字符串,作为文本子元素显示
  • *count.write() += 1:解引用可写引用,直接修改值。修改后框架自动检测到变化,重新调用 app() 刷新 UI
  • Button::new().on_press(…):内置按钮组件,.on_press() 绑定点击回调
  • .child(“增加”):给按钮添加文本内容

自定义组件

在 0.4 中,任何实现了 Component trait 的类型都可以作为组件:

#[derive(PartialEq)]structGreetingCard;implComponentforGreetingCard{fnrender(&self)->implIntoElement{rect().width(Size::fill()).height(Size::fill()).background((40,40,60)).color((255,255,255)).center().font_size(28.0).child("这是一个自定义组件")}}

然后在其他地方使用:

fnapp()->implIntoElement{rect().width(Size::fill()).height(Size::fill()).child(GreetingCard)}

注意:

  • 组件结构体需要 #[derive(PartialEq)]
  • 根组件 app 可以只是一个普通函数,不需要实现 trait
  • 子组件通过 .child(组件名) 嵌入

常用样式方法速查

方法 作用 示例
.width(Size) 宽度 .width(Size::fill())
.height(Size) 高度 .height(Size::percent(50.0))
.background(color) 背景色 .background((30, 50, 90))
.color(color) 文字颜色 .color((255, 255, 255))
.font_size(f32) 字号 .font_size(24.0)
.font_weight(FontWeight) 字重 .font_weight(FontWeight::BOLD)
.center() 内容居中 .center()
.horizontal() 水平布局 .horizontal()
.spacing(f32) 子元素间距 .spacing(12.0)
.shadow(…) 阴影 .shadow((0., 4., 20., 4., (0,0,0,80)))
.child(…) 添加子元素 .child(“文本”) 或 .child(rect())

课后练习

练习 1:基础修改
把 Hello World 的背景色改成深红色 (180, 40, 40),文字改成 “你好,Freya 0.4!”,字号改为 28。

练习 2:简单交互
创建一个窗口,中间显示文字 “未点击”。点击文字所在区域后变成 “已点击”,再次点击变回 “未点击”。

提示:给 rect 添加 .onclick(move |_| { … }) 事件,用 use_state 管理一个布尔值。

练习 3:三色灯
实现一个窗口,垂直排列三个按钮,分别标记 “红”、“绿”、“蓝”。点击任意按钮后,整个窗口背景色变为对应颜色。

**提示:**用 use_state 存储一个颜色元组,三个按钮的 on_press 分别写入不同的颜色值。

常见问题

  • 编译报 unresolved import freya::prelude:确认 Cargo.toml 中依赖指向的是 main 分支,且 cargo update 拉取了最新代码
  • 窗口弹不出来:确认 main 函数中调用了 launch(…),且传入了 WindowConfig::new(app)
  • 状态修改后 UI 不刷新:确认是通过 *count.write() = 新值 修改的,而不是只调用了 count.read()
  • Button 找不到:Button 是 freya::prelude:😗 中导出的内置组件,确保 use freya::prelude:😗; 在文件顶部

第 1 课的内容就到这里。完成练习后可以继续学习第 2 课的布局进阶(Flex 布局、嵌套容器、滚动视图等),有任何问题随时问我。

贴代码

Freya UI 0.4 第1课 —— 课后练习答案与知识点总结

练习 1:基础修改

题目要求: 背景色改成深红色 (180, 40, 40),文字改成 “你好,Freya 0.4!”,字号改为 28。

usefreya::prelude::*;fnmain(){launch(LaunchConfig::new().with_window(WindowConfig::new(app).with_size(400.0,300.0)));}fnapp()->implIntoElement{rect().width(Size::fill()).height(Size::fill()).background((180,40,40))// ← 深红色背景.color((255,255,255)).center().font_size(28.0)// ← 字号 28.child("你好,Freya 0.4!")// ← 中文文字}

考查点:

  • background() 接收 RGB 元组,三个值分别对应红、绿、蓝通道(0~255)
  • font_size() 接收 f32 类型,不是字符串
  • .child(“文本”) 直接传入字符串即可创建文本节点

练习 2:简单交互

题目要求: 中间显示 “未点击”,点击后变成 “已点击”,再次点击变回 “未点击”。

usefreya::prelude::*;fnmain(){launch(LaunchConfig::new().with_window(WindowConfig::new(app).with_size(400.0,300.0)));}fnapp()->implIntoElement{// 创建一个布尔状态,初始值为 false(未点击)letmutclicked=use_state(||false);// 根据状态决定显示的文字lettext=if*clicked.read(){"已点击"}else{"未点击"};rect().width(Size::fill()).height(Size::fill()).background((50,50,80)).color((255,255,255)).center().font_size(32.0)// 绑定点击事件:每次点击取反布尔值.onclick(move|_|{letmutval=clicked.write();*val=!*val;}).child(text)}

考查点:

  • use_state(|| false) 创建布尔状态,闭包返回初始值
  • clicked.read() 返回 &bool,用 * 解引用后做 if 判断
  • clicked.write() 返回 &mut bool,用 *val = !*val 取反
  • .onclick(move |_| { … }) 绑定点击回调,move 关键字把 clicked 的所有权移入闭包
  • 状态变化后框架自动重新调用 app() 函数,UI 随之刷新

易错点:

  • 忘记 move 关键字 → 编译器报生命周期错误
  • 用 clicked.read() 去修改值 → 编译报错,读引用不能写
  • 直接写 clicked = !clicked → clicked 不是 bool,是状态句柄

练习 3:三色灯

题目要求: 三个按钮分别标记 “红”、“绿”、“蓝”,点击后整个窗口背景变为对应颜色。

usefreya::prelude::*;fnmain(){launch(LaunchConfig::new().with_window(WindowConfig::new(app).with_size(400.0,400.0)));}fnapp()->implIntoElement{// 状态存储当前背景色,默认为深灰色letmutbg_color=use_state(||(50u8,50u8,50u8));// 三个按钮letbtn_red=Button::new().on_press({letbg=bg_color.clone();move|_|{*bg.write()=(200,50,50);}}).child("红");letbtn_green=Button::new().on_press({letbg=bg_color.clone();move|_|{*bg.write()=(50,180,50);}}).child("绿");letbtn_blue=Button::new().on_press({letbg=bg_color.clone();move|_|{*bg.write()=(50,80,200);}}).child("蓝");// 根容器:背景色跟随状态rect().width(Size::fill()).height(Size::fill()).background(*bg_color.read()).center().spacing(16.0).child(btn_red).child(btn_green).child(btn_blue)}

考查点:

  • use_state(|| (50u8, 50u8, 50u8)) 用元组存储 RGB 颜色值
  • bg_color.clone() 克隆状态句柄,让多个闭包各自持有独立引用
  • 每个闭包内部通过 *bg.write() = 新颜色 修改状态
  • .background(*bg_color.read()) 解引用读取当前颜色值,应用到根容器背景
  • 状态变化 → app() 重新执行 → .background() 拿到新颜色 → 画面刷新

易错点:

  • 不 clone 直接在多个闭包中使用 bg_color → 所有权被第一个闭包夺走,后续编译报错
  • background() 传入的是值不是引用,需要 *bg_color.read() 解引用

知识点总结

核心概念一览
**知识点 :**说明
Builder 模式 :所有 UI 元素通过 rect() / label() 等构造函数创建,再用 .方法() 链式设置属性
启动流程:launch() → LaunchConfig → WindowConfig::new(根组件) → .with_size(w, h)
根组件签名:fn app() -> impl IntoElement,纯 Rust 函数,无需宏标注
状态管理:use_state( 初始值) 创建,.read() 读,.write() 写,修改后自动刷新 UI
事件绑定:.onclick(closure) / .on_press(closure),闭包需要 move 关键字
子元素添加:.child(元素) 添加单个子元素,可链式调用多次添加多个
颜色表示:(r, g, b) 或 (r, g, b, a) 元组,值域 0~255
尺寸类型:Size::fill() 撑满、Size::percent(n) 百分比、Size::Pixels(n) 固定像素

和 0.3 版本的关键对照
场景:
0.3(宏写法)
0.4(Builder 写法)
创建容器:
rsx! { rect { … } }
rect().child(…)
设置属性:
width: “100%”
.width(Size::fill())
创建状态:
use_signal( 0)
use_state( 0)
读取状态:
*count()
*count.read()
修改状态:
*count.write() += 1
*count.write() += 1
事件绑定:
onclick: move _ …
.onclick(move _ …)
组件标注:
#[component]
不需要

状态修改的完整流程

用户操作(点击按钮)

闭包执行 *state.write() = 新值

框架检测到状态变化

自动重新调用 app() 函数

Builder 链重新执行,生成新的 UI 描述

框架对比差异,只更新变化的部分

屏幕刷新

这个流程就是 Freya 0.4 的响应式核心:你只管改状态,UI 自动跟着变。

如果三道练习都跑通了,说明第 1 课的基础已经打牢,可以继续第 2 课的布局进阶内容了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询