当你的 POS 系统越来越慢,是时候考虑用 Rust 重构了!
一、为什么要重构?
老系统的痛点
我们一个合作伙伴的 POS(销售点)系统是用传统技术栈开发的,随着业务增长,问题越来越明显。
最要命的是: 高峰期系统一卡,后面排长队的顾客就开始抱怨了。
原开发商表示解决不了问题,需要升级硬件设备:客户现在还有同时支持多端的需求。关键是要成本低。于是找到了我们,尝试去解决下问题。
为什么选 Dioxus?
Dioxus 是一个 Rust 写的前端框架,语法类似 React,但性能更好:
┌─────────────────────────────────────────────────────────────┐
│ Dioxus vs 其他框架 │
├─────────────────┬─────────────┬─────────────┬───────────────┤
│ │ Dioxus │ React │ Electron │
├─────────────────┼─────────────┼─────────────┼───────────────┤
│ 启动速度 │ ⚡ 极快 │ 较快 │ 🐌 慢 │
│ 内存占用 │ ~30MB │ ~100MB │ ~500MB │
│ 打包体积 │ ~5MB │ ~50MB │ ~150MB │
│ 学习曲线 │ 类似React │ 基准 │ 需学多技术 │
│ 性能 │ 原生级 │ 虚拟DOM │ Chromium │
└─────────────────┴─────────────┴─────────────┴───────────────┘一句话总结: Dioxus 让你用 React 的写法,获得原生级的性能。
二、Dioxus 是什么?
核心概念
如果你熟悉 React,Dioxus 上手非常快:
// React 写法
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
// Dioxus 写法(几乎一样!)
fn Counter() -> Element {
let mut count = use_signal(|| 0);
rsx! {
div {
p { "Count: {count}" }
button { onclick: move |_| count += 1, "+1" }
}
}
}关键特性
rsx! | |
use_signal | |
use_effect | |
use_resource | |
三、POS 系统架构设计
整体架构
┌─────────────────────────────────────────────────────────────────┐
│ POS 系统(Dioxus) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 界面层(UI) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 收银台 │ │ 商品管理 │ │ 订单列表 │ │ 数据统计 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 状态层(State) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 购物车 │ │ 商品列表 │ │ 当前用户 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 数据层(Data) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 本地存储 │ │ API 调用 │ │ 打印服务 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘核心模块
1. 收银台 - 扫码、加购、结算 2. 商品管理 - 增删改查商品 3. 订单列表 - 查看历史订单 4. 数据统计 - 销售报表
四、核心代码实现
1. 项目结构
pos-system/
├── Cargo.toml
├── src/
│ ├── main.rs # 入口
│ ├── app.rs # 主应用
│ ├── components/ # 组件
│ │ ├── cashier.rs # 收银台
│ │ ├── products.rs # 商品管理
│ │ └── orders.rs # 订单列表
│ ├── models/ # 数据模型
│ │ ├── product.rs
│ │ └── order.rs
│ └── state/ # 状态管理
│ └── store.rs
└── assets/
└── style.css2. 主入口(main.rs)
use dioxus::prelude::*;
fn main() {
// 启动桌面应用
dioxus::launch(App);
}
#[component]
fn App() -> Element {
// 全局状态
let mut current_page = use_signal(|| Page::Cashier);
rsx! {
div { class: "app",
// 顶部导航
nav { class: "navbar",
button {
onclick: move |_| current_page.set(Page::Cashier),
"收银台"
}
button {
onclick: move |_| current_page.set(Page::Products),
"商品管理"
}
button {
onclick: move |_| current_page.set(Page::Orders),
"订单"
}
}
// 页面内容
main { class: "content",
match current_page() {
Page::Cashier => rsx! { Cashier {} },
Page::Products => rsx! { Products {} },
Page::Orders => rsx! { Orders {} },
}
}
}
}
}
#[derive(Clone, Copy, PartialEq)]
enum Page {
Cashier,
Products,
Orders,
}3. 收银台组件(cashier.rs)
这是 POS 的核心功能:
use dioxus::prelude::*;
#[component]
pub fn Cashier() -> Element {
// 购物车状态
let mut cart = use_signal(Vec::<CartItem>::new);
// 商品列表(实际项目从 API 获取)
let products = use_signal(|| vec![
Product { id: 1, name: "可乐".into(), price: 3.0 },
Product { id: 2, name: "薯片".into(), price: 5.0 },
Product { id: 3, name: "面包".into(), price: 8.0 },
]);
// 计算总价
let total: f64 = cart.iter().map(|item| item.subtotal()).sum();
rsx! {
div { class: "cashier",
// 左侧:商品列表
div { class: "products",
h2 { "商品列表" }
for product in products() {
ProductCard {
product: product.clone(),
on_add: move |_| {
// 添加到购物车
cart.write().push(CartItem::from(product.clone()));
}
}
}
}
// 右侧:购物车
div { class: "cart",
h2 { "购物车" }
if cart.is_empty() {
p { "购物车是空的" }
} else {
for item in cart() {
CartItemRow { item: item.clone() }
}
div { class: "total",
strong { "总计: ¥{total:.2}" }
}
button {
class: "checkout-btn",
onclick: move |_| {
// 结算逻辑
println!("结算: ¥{total}");
cart.clear();
},
"结算"
}
}
}
}
}
}
// 商品卡片组件
#[component]
fn ProductCard(product: Product, on_add: EventHandler<()>) -> Element {
rsx! {
div { class: "product-card",
span { "{product.name}" }
span { "¥{product.price}" }
button { onclick: move |_| on_add.call(()), "+" }
}
}
}4. 状态管理(store.rs)
use dioxus::prelude::*;
use std::sync::Arc;
/// 全局状态管理
pub struct Store {
pub products: Signal<Vec<Product>>,
pub orders: Signal<Vec<Order>>,
pub current_user: Signal<Option<User>>,
}
impl Store {
pub fn new() -> Self {
Self {
products: Signal::new(Vec::new()),
orders: Signal::new(Vec::new()),
current_user: Signal::new(None),
}
}
/// 添加商品到购物车
pub fn add_to_cart(&mut self, product: Product) {
// 实现添加逻辑
}
/// 创建订单
pub fn create_order(&mut self, items: Vec<CartItem>) -> Order {
let order = Order {
id: uuid::Uuid::new_v4().to_string(),
items,
total: items.iter().map(|i| i.subtotal()).sum(),
created_at: chrono::Local::now(),
};
self.orders.write().push(order.clone());
order
}
}
// 数据模型
#[derive(Clone, Debug)]
pub struct Product {
pub id: u32,
pub name: String,
pub price: f64,
}
#[derive(Clone, Debug)]
pub struct CartItem {
pub product: Product,
pub quantity: u32,
}
impl CartItem {
pub fn subtotal(&self) -> f64 {
self.product.price * self.quantity as f64
}
}
#[derive(Clone, Debug)]
pub struct Order {
pub id: String,
pub items: Vec<CartItem>,
pub total: f64,
pub created_at: chrono::DateTime<chrono::Local>,
}五、重构效果对比
性能提升
| 16x | |||
| 14x | |||
| 18x | |||
| 20x |
开发体验
六、踩坑记录
坑 1:异步数据加载
问题: 直接在组件里调用异步函数会报错。
解决: 使用 use_resource 钩子:
#[component]
fn ProductList() -> Element {
// 正确的异步数据加载方式
let products = use_resource(move || async move {
fetch_products().await.unwrap_or_default()
});
rsx! {
match &*products.read() {
Some(data) => rsx! {
for product in data {
ProductCard { product: product.clone() }
}
},
None => rsx! { p { "加载中..." } },
}
}
}坑 2:状态更新不触发渲染
问题: 修改了数据但界面没更新。
解决: 确保使用 Signal 而不是普通变量:
// ❌ 错误:不会触发更新
let items = use_signal(Vec::new);
items.write().push(new_item); // 这样可以,但要用 write()
// ✅ 正确:使用 Signal
let mut items = use_signal(Vec::<Item>::new);
items.write().push(new_item);坑 3:样式不生效
问题: CSS 类名写了但样式没应用。
解决: Dioxus 使用 class 属性,确保样式文件正确引入:
// 在 main.rs 中引入样式
fn main() {
dioxus::launch(App);
}
// 组件中使用
rsx! {
div { class: "container", // 注意是 class 不是 className
"内容"
}
}七、总结
为什么选择 Dioxus 重构?
1. 性能大幅提升 - 启动快、内存小、响应快 2. 开发效率高 - 类似 React 的写法,上手快 3. 类型安全 - Rust 编译时检查,减少 Bug 4. 跨平台 - 一套代码,桌面、Web、移动端都能跑
适合什么场景?
• ✅ 需要高性能的桌面应用 • ✅ 对安装包大小敏感 • ✅ 团队熟悉 React 想尝试 Rust • ✅ 需要长期维护的项目
不适合什么场景?
• ❌ 需要大量现成 UI 组件库 • ❌ 团队完全没有 Rust 经验 • ❌ 项目周期非常紧
八、下一步
如果你也想尝试 Dioxus,推荐的学习路径:
1. 官方教程 - https://dioxuslabs.com/learn 2. 写个小项目 - 待办事项、计数器等 3. 阅读源码 - 看看官方示例项目 4. 加入社区 - Discord 上有很多热心人
Rust 火箭工坊 🚀
用 Rust 重塑你的应用
夜雨聆风