从零开始构建 Tauri 2.0 桌面应用:React + Rust 完整实战教程

44 阅读3分钟

前言

Tauri 是一个使用 Web 技术构建桌面应用的框架,相比 Electron,它的优势在于:

  • 体积小:打包后仅几 MB,而 Electron 动辄上百 MB
  • 性能高:使用 Rust 作为后端,内存占用低
  • 安全性强:默认禁用 Node.js,减少攻击面

本文将带你从零构建一个完整的 Tauri 应用,包含前后端通信的实战案例。

一、环境准备

1.1 安装 Rust

Windows 用户下载并运行 rustup-init.exe,按提示完成安装。

安装完成后验证:

rustc --version
cargo --version

1.2 安装 Node.js

推荐使用 Node.js 18 或更高版本。

node --version
npm --version

1.3 安装系统依赖

Windows 需要安装:

  • Microsoft Visual Studio C++ Build Tools
  • WebView2(Windows 10/11 通常已预装)

二、创建项目

2.1 初始化 Tauri 项目

npm create tauri-app@latest

按提示选择:

✔ Project name · my-tauri-app
✔ Identifier · com.example.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript
✔ Choose your package manager · npm
✔ Choose your UI template · React
✔ Choose your UI flavor · TypeScript

2.2 进入项目并安装依赖

cd my-tauri-app
npm install

2.3 项目结构

my-tauri-app/
├── src/                    # React 前端代码
│   ├── App.tsx
│   ├── main.tsx
│   └── ...
├── src-tauri/              # Rust 后端代码
│   ├── src/
│   │   ├── lib.rs          # 主要逻辑
│   │   └── main.rs         # 入口文件
│   ├── Cargo.toml          # Rust 依赖配置
│   └── tauri.conf.json     # Tauri 配置文件
├── package.json
└── ...

2.4 启动开发服务器

npm run tauri dev

首次运行会编译 Rust 代码,需要等待几分钟。之后会弹出应用窗口。

三、前后端通信:核心概念

Tauri 的前后端通信主要通过 Commands(命令) 实现:

  • Rust 端:定义命令函数,处理业务逻辑
  • React 端:调用命令,获取返回值

这是 Tauri 最核心的功能,下面通过实战案例讲解。

四、实战案例:获取系统信息

我们来实现一个获取系统磁盘信息的功能。

4.1 Rust 端:定义命令

编辑 src-tauri/src/lib.rs

use serde::Serialize;

// 定义返回数据结构
#[derive(Debug, Serialize)]
pub struct DiskInfo {
    pub drive_letter: String,
    pub total_space: u64,
    pub free_space: u64,
    pub used_space: u64,
    pub usage_percent: f64,
}

// 定义 Tauri 命令
#[tauri::command]
fn get_disk_info() -> Result<DiskInfo, String> {
    // 这里简化处理,实际可以使用 sysinfo 库获取真实数据
    let total: u64 = 500 * 1024 * 1024 * 1024; // 500GB
    let free: u64 = 100 * 1024 * 1024 * 1024;  // 100GB
    let used = total - free;
    
    Ok(DiskInfo {
        drive_letter: "C".to_string(),
        total_space: total,
        free_space: free,
        used_space: used,
        usage_percent: (used as f64 / total as f64) * 100.0,
    })
}

// 注册命令
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![get_disk_info]) // 注册命令
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

关键点:

  1. #[tauri::command] 宏将函数标记为可被前端调用的命令
  2. 返回值需要实现 Serialize trait
  3. 使用 Result<T, String> 可以向前端返回错误信息
  4. 必须在 invoke_handler 中注册命令

4.2 添加 Rust 依赖

编辑 src-tauri/Cargo.toml,确保有 serde 依赖:

[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

4.3 React 端:调用命令

编辑 src/App.tsx

import { useState } from 'react';
import { invoke } from '@tauri-apps/api/core';

// 定义类型(与 Rust 端对应)
interface DiskInfo {
  drive_letter: string;
  total_space: number;
  free_space: number;
  used_space: number;
  usage_percent: number;
}

function App() {
  const [diskInfo, setDiskInfo] = useState<DiskInfo | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // 调用 Rust 命令
  const fetchDiskInfo = async () => {
    setLoading(true);
    setError(null);
    
    try {
      // invoke 函数调用 Rust 命令,命令名使用蛇形命名法
      const info = await invoke<DiskInfo>('get_disk_info');
      setDiskInfo(info);
    } catch (err) {
      setError(String(err));
    } finally {
      setLoading(false);
    }
  };

  // 格式化文件大小
  const formatSize = (bytes: number): string => {
    const gb = bytes / (1024 * 1024 * 1024);
    return `${gb.toFixed(2)} GB`;
  };

  return (
    <div style={{ padding: '20px' }}>
      <h1>Tauri 磁盘信息示例</h1>
      
      <button onClick={fetchDiskInfo} disabled={loading}>
        {loading ? '加载中...' : '获取磁盘信息'}
      </button>

      {error && (
        <p style={{ color: 'red' }}>错误: {error}</p>
      )}

      {diskInfo && (
        <div style={{ marginTop: '20px' }}>
          <p><strong>盘符:</strong> {diskInfo.drive_letter}:</p>
          <p><strong>总容量:</strong> {formatSize(diskInfo.total_space)}</p>
          <p><strong>已用:</strong> {formatSize(diskInfo.used_space)}</p>
          <p><strong>可用:</strong> {formatSize(diskInfo.free_space)}</p>
          <p><strong>使用率:</strong> {diskInfo.usage_percent.toFixed(1)}%</p>
        </div>
      )}
    </div>
  );
}

export default App;

关键点:

  1. 使用 @tauri-apps/api/core 中的 invoke 函数调用命令
  2. 命令名需要使用蛇形命名法(snake_case)
  3. 泛型参数指定返回值类型
  4. 使用 try-catch 处理可能的错误

4.4 运行测试

npm run tauri dev

点击按钮,即可看到从 Rust 端返回的磁盘信息。

五、进阶:带参数的命令

5.1 Rust 端

#[tauri::command]
fn greet(name: String) -> String {
    format!("Hello, {}! Welcome to Tauri.", name)
}

// 记得注册
.invoke_handler(tauri::generate_handler![get_disk_info, greet])

5.2 React 端

const greeting = await invoke<string>('greet', { name: 'Tauri' });
console.log(greeting); // "Hello, Tauri! Welcome to Tauri."

注意: 参数以对象形式传递,键名必须与 Rust 函数参数名一致。

六、进阶:异步命令

对于耗时操作,使用异步命令避免阻塞:

#[tauri::command]
async fn scan_files(path: String) -> Result<Vec<String>, String> {
    // 模拟耗时操作
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    
    Ok(vec![
        format!("{}/file1.txt", path),
        format!("{}/file2.txt", path),
    ])
}

需要在 Cargo.toml 添加 tokio 依赖:

[dependencies]
tokio = { version = "1", features = ["time"] }

七、配置应用信息

编辑 src-tauri/tauri.conf.json

{
  "productName": "我的应用",
  "version": "1.0.0",
  "identifier": "com.example.myapp",
  "build": {
    "frontendDist": "../dist"
  },
  "app": {
    "windows": [
      {
        "title": "我的 Tauri 应用",
        "width": 900,
        "height": 600,
        "resizable": true,
        "center": true
      }
    ]
  }
}

八、打包发布

8.1 执行打包命令

npm run tauri build

8.2 打包产物

打包完成后,安装包位于:

src-tauri/target/release/bundle/
├── msi/          # MSI 安装包
│   └── 应用名_版本_x64_en-US.msi
└── nsis/         # NSIS 安装包
    └── 应用名_版本_x64-setup.exe

8.3 常见问题:网络超时

如果遇到 timeout: global 错误,是因为下载 NSIS 工具超时。

解决方案:手动下载 NSIS 并放到缓存目录。

# 创建目录
New-Item -ItemType Directory -Path "$env:LOCALAPPDATA\tauri\NSIS" -Force

# 手动下载 nsis-3.zip 并解压到上述目录

九、总结

本文介绍了 Tauri 2.0 的核心开发流程:

  1. 环境搭建:安装 Rust 和 Node.js
  2. 项目创建:使用脚手架快速初始化
  3. 前后端通信:通过 Commands 实现 React 与 Rust 的数据交互
  4. 打包发布:生成 Windows 安装包

Tauri 的优势在于小体积、高性能、强安全性,非常适合开发轻量级桌面工具。希望这篇教程能帮助你快速上手 Tauri 开发!