跟我学 Tauri:构建跨平台桌面应用

  • King
  • 发布于 10小时前
  • 阅读 21

本文档以护眼卫士为例,介绍如何使用Tauri、Rust和React技术栈开发跨平台桌面应用。目录为什么选择Tauri技术架构概览开发环境准备创建新项目Tauri核心概念前后端通信常见功能实现构建与发布最佳实践为什么选择TauriTaurivsElec

本文档以护眼卫士为例,介绍如何使用 Tauri、Rust 和 React 技术栈开发跨平台桌面应用。

目录

为什么选择 Tauri

Tauri vs Electron

特性 Tauri Electron
打包体积 ~10MB ~100MB+
内存占用
性能 接近原生 较低
后端语言 Rust Node.js
安全性 中等

技术栈优势

  • Rust 后端:内存安全、高性能、跨平台支持
  • React 前端:生态丰富、开发体验好、组件化
  • 轻量级:打包体积小,启动速度快
  • 原生能力:可以调用系统 API,实现原生功能

技术架构概览

Tauri应用架构.png

应用结构

架构说明

  • 前端层:使用 React 构建用户界面,处理交互逻辑和状态管理
  • 后端层:使用 Rust 实现系统调用、文件操作和后台任务
  • IPC 通信:通过 Tauri 提供的 IPC 机制实现前后端数据交互
  • 操作系统:支持 macOS、Windows 和 Linux 三大平台

项目目录结构

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 构建配置

开发环境准备

1. 安装 Rust

# macOS/Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

# 验证安装
rustc --version
cargo --version

2. 安装 Node.js 和包管理器

# 安装 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

3. 安装系统依赖

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 核心概念

1. Tauri 命令(Commands)

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>
  );
}

2. 事件系统(Events)

事件系统用于后端向前端推送数据。

发送事件(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());
  };
}, []);

3. 全局状态管理

使用 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(())
}

常见功能实现

1. 系统托盘

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(())
}

2. 系统通知

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");
}

3. 文件操作

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())
}

4. 自动启动

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)
}

5. 全局快捷键

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(())
}

6. 窗口管理

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 Cask 发布

Homebrew 是 macOS 用户最常用的包管理器,发布到 Homebrew 可以让用户通过 brew install 命令一键安装应用。

1. 创建 Cask 文件

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

2. 计算 SHA256 校验和

# 下载 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 字段。

3. 提交到 Homebrew

方式一:提交到 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

4. 更新现有 Cask

如果 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

5. 用户安装体验

发布后,用户可以通过以下命令安装:

# 安装应用
brew install --cask eye-sentry

# 启动应用
open -a "Eye Sentry"

# 升级应用
brew upgrade --cask eye-sentry

# 卸载应用
brew uninstall --cask eye-sentry

6. 自动化部署脚本

可以创建脚本自动更新 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

最佳实践

1. 代码组织

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/            # 工具函数

2. 错误处理

// 使用 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)
}

3. 异步编程

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();
        }
    });
}

4. 性能优化

  • 减少 IPC 调用:批量处理数据,减少前后端通信次数
  • 使用缓存:缓存频繁访问的数据
  • 异步处理:耗时操作使用异步任务
  • 资源释放:及时释放不再使用的资源

5. 安全考虑

  • 验证输入:验证所有来自前端的输入
  • 路径安全:使用安全的路径操作,避免路径遍历漏洞
  • 权限控制:使用 Tauri 的权限系统限制功能访问
  • 敏感数据:不在日志中输出敏感信息

总结

通过本文档,你已经了解了如何使用 Tauri + Rust + React 技术栈开发跨平台桌面应用的核心知识:

关键技术点

  • Tauri 提供了轻量级的跨平台应用框架,相比 Electron 更小更快
  • Rust 后端提供了高性能、内存安全的原生能力
  • React 前端提供了现代化的 Web UI 开发体验
  • 通过 IPC 机制实现前后端通信,通过事件系统实现数据推送

开发流程

  1. 环境准备:安装 Rust、Node.js 和系统依赖
  2. 项目初始化:使用脚手架或手动创建项目
  3. 功能开发:定义 Tauri 命令、实现业务逻辑、构建 UI
  4. 本地调试:使用 pnpm tauri dev 进行热更新开发
  5. 构建发布:使用 pnpm tauri build 生成多平台安装包
  6. 分发部署:通过 GitHub Release 或 Homebrew Cask 发布

核心能力

  • ✅ 系统托盘集成
  • ✅ 系统通知
  • ✅ 文件操作
  • ✅ 自动启动
  • ✅ 全局快捷键
  • ✅ 窗口管理
  • ✅ 跨平台构建
  • ✅ Homebrew 一键安装

最佳实践

  • 合理的代码组织结构
  • 完善的错误处理机制
  • 高效的异步编程模式
  • 必要的性能优化措施
  • 严格的安全考虑

Tauri 结合了 Rust 的性能、React 的开发体验和 Web 技术的跨平台能力,是构建现代桌面应用的理想选择。无论是开发工具、生产力应用还是系统实用工具,Tauri 都能提供出色的性能和用户体验。

现在,你已经掌握了开发跨平台桌面应用所需的所有核心知识,可以开始构建你自己的应用了!🚀

广告时间

macOS的小伙伴们有福啦!我最近开发了一款实用的护眼助手工具,经过多轮优化打磨,现在终于可以正式和大家见面啦~

👉 护眼助手官网:https://eye-sentry.vercel.app/ 🖥️ macOS专属安装命令:

brew install --cask rustx-labs/tap/eye-sentry

目前其他平台还在适配测试中(暂无对应设备调试,后续会逐步完善),大家在使用过程中有任何建议或反馈,欢迎随时告诉我呀~

P.S. 这次开发还顺带解锁了跨平台应用开发技能,如今前后端、客户端、合约端相关能力都已借助AI之力基本掌握~

后续有开发需求的老板,欢迎来谈合作呀~

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发