本文档以护眼卫士为例,介绍如何使用Tauri、Rust和React技术栈开发跨平台桌面应用。目录为什么选择Tauri技术架构概览开发环境准备创建新项目Tauri核心概念前后端通信常见功能实现构建与发布最佳实践为什么选择TauriTaurivsElec
本文档以护眼卫士为例,介绍如何使用 Tauri、Rust 和 React 技术栈开发跨平台桌面应用。
| 特性 | Tauri | Electron |
|---|---|---|
| 打包体积 | ~10MB | ~100MB+ |
| 内存占用 | 低 | 高 |
| 性能 | 接近原生 | 较低 |
| 后端语言 | Rust | Node.js |
| 安全性 | 高 | 中等 |

架构说明:
my-app/
├── src/ # 前端源码
│ ├── main.jsx # React 入口
│ ├── App.jsx # 主应用组件
│ └── components/ # 组件目录
│
├── src-tauri/ # Rust 后端
│ ├── src/
│ │ ├── main.rs # Rust 入口
│ │ └── lib.rs # 应用初始化和命令注册
│ ├── Cargo.toml # Rust 依赖
│ └── tauri.conf.json # 应用配置
│
├── package.json # 前端依赖
└── vite.config.js # Vite 构建配置
# macOS/Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# 验证安装
rustc --version
cargo --version
# 安装 Node.js(推荐使用 nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
nvm install 22
nvm use 22
# 安装 pnpm(推荐)或 npm
npm install -g pnpm
# 验证安装
node --version
pnpm --version
macOS:
xcode-select --install
Linux (Ubuntu/Debian):
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
Windows:
# 创建新项目
pnpm create tauri-app my-app
# 选择模板:
# - React (推荐)
# - Vue
# - Svelte
# - Solid
# 进入项目目录
cd my-app
# 安装依赖
pnpm install
# 创建项目目录
mkdir my-app && cd my-app
# 初始化前端项目
pnpm create vite@latest frontend -- --template react
cd frontend
pnpm install
# 初始化 Tauri
pnpm add -D @tauri-apps/cli
pnpm tauri init
# 配置 Tauri
# - 应用名称
# - 窗口标题
# - 前端开发服务器 URL(默认 http://localhost:5173)
Tauri 命令是前端调用后端 Rust 代码的桥梁。
定义命令(Rust):
// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
调用命令(React):
import { invoke } from '@tauri-apps/api/core';
function App() {
const [greeting, setGreeting] = useState('');
const handleClick = async () => {
const result = await invoke('greet', { name: 'World' });
setGreeting(result);
};
return (
<div>
<button onClick={handleClick}>Greet</button>
<p>{greeting}</p>
</div>
);
}
事件系统用于后端向前端推送数据。
发送事件(Rust):
use tauri::Emitter;
#[tauri::command]
fn start_timer(app_handle: tauri::AppHandle) {
// 发送事件到前端
app_handle.emit("timer-update", { count: 1 }).unwrap();
}
监听事件(React):
import { listen } from '@tauri-apps/api/event';
useEffect(() => {
const unlisten = listen('timer-update', (event) => {
console.log('Received:', event.payload);
});
return () => {
unlisten.then(fn => fn());
};
}, []);
使用 Arc 和 Mutex 实现跨命令的状态共享。
use std::sync::{Arc, Mutex};
// 定义状态结构
#[derive(Debug, Clone)]
pub struct AppState {
pub counter: Mutex<i32>,
}
// 在 main 中初始化
fn main() {
let state = Arc::new(AppState {
counter: Mutex::new(0),
});
tauri::Builder::default()
.manage(state) // 注册状态
.invoke_handler(tauri::generate_handler![increment])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// 在命令中使用状态
#[tauri::command]
fn increment(state: State<'_, Arc<AppState>>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
| Rust | JavaScript |
|---|---|
String |
string |
i32,i64 |
number |
f32,f64 |
number |
bool |
boolean |
Vec<T> |
Array |
HashMap<K, V> |
Object |
Option<T> |
T \| null |
Result<T, E> |
T(错误抛出异常) |
使用 serde 库处理数据序列化。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Config {
pub theme: String,
pub language: String,
pub notifications: bool,
}
#[tauri::command]
fn save_config(config: Config) -> Result<(), String> {
// 保存配置
Ok(())
}
use tauri::{Manager, tray::{TrayIconBuilder, TrayIconEvent, MouseButtonState, MouseButton}};
fn setup_tray(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let show_item = MenuItemBuilder::with_id("show", "显示").build(app)?;
let quit_item = MenuItemBuilder::with_id("quit", "退出").build(app)?;
let menu = MenuBuilder::new(app)
.item(&show_item)
.item(&quit_item)
.build()?;
TrayIconBuilder::with_id("main")
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
.on_menu_event(move |app, event| {
match event.id.as_ref() {
"show" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
"quit" => {
app.exit(0);
}
_ => {}
}
})
.build(app)?;
Ok(())
}
use tauri_plugin_notification::NotificationExt;
#[tauri::command]
fn send_notification(app_handle: tauri::AppHandle) {
app_handle.notification()
.builder()
.title("通知标题")
.body("通知内容")
.show()
.expect("failed to show notification");
}
use std::fs;
use std::path::PathBuf;
#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(&path).map_err(|e| e.to_string())
}
#[tauri::command]
fn write_file(path: String, content: String) -> Result<(), String> {
fs::write(&path, content).map_err(|e| e.to_string())
}
#[tauri::command]
fn get_config_path() -> Result<String, String> {
let config_dir = dirs::config_dir()
.ok_or("无法获取配置目录")?
.join("my-app");
std::fs::create_dir_all(&config_dir).map_err(|e| e.to_string())?;
Ok(config_dir.to_string_lossy().to_string())
}
use tauri_plugin_autostart::ManagerExt;
#[tauri::command]
async fn enable_autostart(app_handle: tauri::AppHandle) -> Result<bool, String> {
let autostart_manager = app_handle.autolaunch();
autostart_manager.enable().map_err(|e| e.to_string())?;
Ok(true)
}
#[tauri::command]
async fn disable_autostart(app_handle: tauri::AppHandle) -> Result<bool, String> {
let autostart_manager = app_handle.autolaunch();
autostart_manager.disable().map_err(|e| e.to_string())?;
Ok(false)
}
use tauri::GlobalShortcutExt;
fn setup_shortcuts(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let app_handle = app.handle().clone();
app.global_shortcut().on_shortcut("CommandOrControl+Shift+A", move |_app, _shortcut, _event| {
if let Some(window) = app_handle.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
})?;
Ok(())
}
use tauri::Manager;
#[tauri::command]
fn toggle_window(app: tauri::AppHandle) -> Result<(), String> {
if let Some(window) = app.get_webview_window("main") {
if window.is_visible().map_err(|e| e.to_string())? {
window.hide().map_err(|e| e.to_string())?;
} else {
window.show().map_err(|e| e.to_string())?;
window.set_focus().map_err(|e| e.to_string())?;
}
}
Ok(())
}
#[tauri::command]
fn set_window_fullscreen(app: tauri::AppHandle, fullscreen: bool) -> Result<(), String> {
if let Some(window) = app.get_webview_window("main") {
window.set_fullscreen(fullscreen).map_err(|e| e.to_string())?;
}
Ok(())
}
# 启动开发服务器(热更新)
pnpm tauri dev
# 构建应用
pnpm tauri build
# 构建产物位置
# macOS: src-tauri/target/release/bundle/macos/
# Windows: src-tauri/target/release/bundle/msi/
# Linux: src-tauri/target/release/bundle/deb/
macOS:
# Apple Silicon
pnpm tauri build --target aarch64-apple-darwin
# Intel
pnpm tauri build --target x86_64-apple-darwin
# 通用二进制(需要额外工具)
lipo -create -output output.app target/aarch64/release/bundle/macos/App.app/Contents/MacOS/app target/x86_64/release/bundle/macos/App.app/Contents/MacOS/app
Windows:
# 64 位
pnpm tauri build --target x86_64-pc-windows-msvc
Linux:
# Ubuntu/Debian
pnpm tauri build --target x86_64-unknown-linux-gnu
macOS:
# 代码签名
codesign --deep --force --verify --verbose --sign "Developer ID Application: Your Name" "Eye Sentry.app"
# 公证
xcrun notarytool submit "Eye Sentry.app" --apple-id "your@email.com" --password "app-specific-password" --team-id "team-id" --wait
Windows:
# 使用 signtool 签名
signtool sign /f certificate.pfx /p password /t http://timestamp.digicert.com Eye-Sentry-setup.exe
Homebrew 是 macOS 用户最常用的包管理器,发布到 Homebrew 可以让用户通过 brew install 命令一键安装应用。
Cask 文件使用 Ruby 编写,描述应用的安装信息。
# eye-sentry.rb
cask "eye-sentry" do
version "1.0.0" # 应用版本号
sha256 "abc123..." # DMG 文件的 SHA256 校验和
url "https://github.com/yourname/your-app/releases/download/v1.0.0/YourApp-1.0.0.dmg"
name "Your App"
desc "应用描述"
homepage "https://github.com/yourname/your-app"
app "YourApp.app"
zap trash: [
"~/Library/Application Support/your-app",
"~/Library/Logs/your-app",
"~/Library/Preferences/com.yourname.your-app.plist",
]
end
# 下载 DMG 文件
curl -L -o YourApp.dmg "https://github.com/yourname/your-app/releases/download/v1.0.0/YourApp-1.0.0.dmg"
# 计算 SHA256
shasum -a 256 YourApp.dmg
将输出的 SHA256 值填入 Cask 文件的 sha256 字段。
方式一:提交到 homebrew-cask 仓库
# Fork homebrew/homebrew-cask 仓库
git clone https://github.com/your-username/homebrew-cask.git
cd homebrew-cask
# 复制 Cask 文件
cp /path/to/eye-sentry.rb Casks/eye-sentry.rb
# 提交更改
git add Casks/eye-sentry.rb
git commit -m "eye-sentry 1.0.0 (new formula)"
git push origin main
# 在 GitHub 上创建 Pull Request 到 homebrew/homebrew-cask
方式二:使用 brew 命令测试
# 测试 Cask 文件是否正确
brew install --cask --debug /path/to/eye-sentry.rb
# 如果测试通过,提交到 homebrew-cask
brew tap homebrew/cask
cd "$(brew --repository)/Library/Taps/homebrew/homebrew-cask/Casks"
cp /path/to/eye-sentry.rb eye-sentry.rb
git add eye-sentry.rb
git commit -m "eye-sentry 1.0.0 (new formula)"
git push
如果 Cask 已存在,更新时只需修改版本号和 SHA256:
cd "$(brew --repository)/Library/Taps/homebrew/homebrew-cask/Casks"
# 编辑 Cask 文件
vim eye-sentry.rb
# 更新版本号和 SHA256
# version "1.0.0"
# sha256 "新的校验和"
# 提交更改
git add eye-sentry.rb
git commit -m "eye-sentry 1.0.0"
git push
发布后,用户可以通过以下命令安装:
# 安装应用
brew install --cask eye-sentry
# 启动应用
open -a "Eye Sentry"
# 升级应用
brew upgrade --cask eye-sentry
# 卸载应用
brew uninstall --cask eye-sentry
可以创建脚本自动更新 Cask 文件:
#!/bin/bash
# deploy-brew.sh
VERSION=$1
DMG_URL="https://github.com/yourname/your-app/releases/download/v${VERSION}/YourApp-${VERSION}.dmg"
# 下载 DMG
curl -L -o app.dmg "$DMG_URL"
# 计算 SHA256
SHA256=$(shasum -a 256 app.dmg | awk '{print $1}')
# 更新 Cask 文件
sed -i '' "s/version \".*\"/version \"${VERSION}\"/" Casks/eye-sentry.rb
sed -i '' "s/sha256 \".*\"/sha256 \"${SHA256}\"/" Casks/eye-sentry.rb
sed -i '' "s|url \".*\"|url \"${DMG_URL}\"|" Casks/eye-sentry.rb
# 提交
git add Casks/eye-sentry.rb
git commit -m "eye-sentry ${VERSION}"
git push
使用:
./deploy-brew.sh 1.0.0
Rust 后端:
src-tauri/src/
├── main.rs # 入口
├── lib.rs # 模块导出
├── commands.rs # Tauri 命令
├── app_state.rs # 全局状态
├── config.rs # 配置管理
├── utils.rs # 工具函数
└── modules/ # 功能模块
├── timer.rs
├── file.rs
└── system.rs
React 前端:
src/
├── main.jsx # 入口
├── App.jsx # 主组件
├── components/ # 组件
├── hooks/ # 自定义 Hooks
├── services/ # API 调用
└── utils/ # 工具函数
// 使用 Result 返回错误
#[tauri::command]
async fn do_something() -> Result<String, String> {
// 成功
Ok("Success".to_string())
// 失败
// Err("Something went wrong".to_string())
}
// 使用 ? 简化错误传播
#[tauri::command]
async fn read_config() -> Result<Config, String> {
let path = get_config_path()?;
let content = fs::read_to_string(path)?;
let config: Config = serde_json::from_str(&content)?;
Ok(config)
}
use tokio::time::{sleep, Duration};
#[tauri::command]
async fn async_task() -> String {
sleep(Duration::from_secs(1)).await;
"Task completed".to_string()
}
// 启动后台任务
fn start_background_task(app_handle: tauri::AppHandle) {
tokio::spawn(async move {
loop {
// 执行任务
sleep(Duration::from_secs(60)).await;
// 发送事件
app_handle.emit("background-task", ()).unwrap();
}
});
}
通过本文档,你已经了解了如何使用 Tauri + Rust + React 技术栈开发跨平台桌面应用的核心知识:
关键技术点:
开发流程:
pnpm tauri dev 进行热更新开发pnpm tauri build 生成多平台安装包核心能力:
最佳实践:
Tauri 结合了 Rust 的性能、React 的开发体验和 Web 技术的跨平台能力,是构建现代桌面应用的理想选择。无论是开发工具、生产力应用还是系统实用工具,Tauri 都能提供出色的性能和用户体验。
现在,你已经掌握了开发跨平台桌面应用所需的所有核心知识,可以开始构建你自己的应用了!🚀
macOS的小伙伴们有福啦!我最近开发了一款实用的护眼助手工具,经过多轮优化打磨,现在终于可以正式和大家见面啦~
👉 护眼助手官网:https://eye-sentry.vercel.app/ 🖥️ macOS专属安装命令:
brew install --cask rustx-labs/tap/eye-sentry
目前其他平台还在适配测试中(暂无对应设备调试,后续会逐步完善),大家在使用过程中有任何建议或反馈,欢迎随时告诉我呀~
P.S. 这次开发还顺带解锁了跨平台应用开发技能,如今前后端、客户端、合约端相关能力都已借助AI之力基本掌握~
后续有开发需求的老板,欢迎来谈合作呀~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!