Command 系统概述
Tauri 的 Commands 是前端与 Rust 后端通信的核心机制。工作流程如下:
前端(JavaScript/TypeScript)
│
│ invoke('command_name', { arg1, arg2 })
│
▼
Tauri IPC Bridge(消息序列化 JSON)
│
▼
Rust 后端
│ #[tauri::command]
│ fn command_name(arg1: String, arg2: u32) -> Result<Data, Error>
│
▼
返回值序列化为 JSON → 前端 Promise resolve
基础 Command 定义
// src-tauri/src/lib.rs
use tauri::AppHandle;
// 最简单的 Command:同步,接受参数,返回 String
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! From Rust.", name)
}
// 接受多个参数
#[tauri::command]
fn add_numbers(a: i64, b: i64) -> i64 {
a + b
}
// 返回结构体(自动序列化为 JSON)
#[derive(serde::Serialize)]
struct SystemInfo {
os: String,
arch: String,
cpu_count: usize,
}
#[tauri::command]
fn get_system_info() -> SystemInfo {
SystemInfo {
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
cpu_count: num_cpus::get(),
}
}
// 注册所有 Commands
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
greet,
add_numbers,
get_system_info,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
参数类型映射
| TypeScript 类型 | Rust 类型 | 说明 |
|---|---|---|
string | String / &str | 字符串 |
number | i32 / u64 / f64 等 | 整数/浮点数 |
boolean | bool | 布尔值 |
null / undefined | Option<T> | 可选值 |
T[] | Vec<T> | 数组 |
Record<string, T> | HashMap<String, T> | 键值对 |
object(带字段) | #[derive(Deserialize)] struct | 自定义结构 |
Uint8Array | Vec<u8> | 二进制数据 |
接受结构体参数
use serde::Deserialize;
// 接受 JSON 对象,字段名用 snake_case(Tauri 自动处理驼峰转换)
#[derive(Deserialize)]
struct CreateFileArgs {
path: String,
content: String,
overwrite: Option<bool>, // 可选字段
}
#[tauri::command]
fn create_file(args: CreateFileArgs) -> Result<(), String> {
let overwrite = args.overwrite.unwrap_or(false);
if std::path::Path::new(&args.path).exists() && !overwrite {
return Err("File already exists".to_string());
}
std::fs::write(&args.path, &args.content)
.map_err(|e| e.to_string())
}
// 前端调用:字段名可用驼峰(Tauri 自动转为 snake_case)
await invoke('create_file', {
path: '/tmp/test.txt',
content: 'Hello, Tauri!',
overwrite: true,
});
异步 Command
耗时操作(文件 IO、网络请求、计算密集型任务)应使用 async fn,避免阻塞主线程:
use tokio::time::{sleep, Duration};
// async Command 在 Tokio 运行时上执行,不阻塞 UI
#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url)
.await
.map_err(|e| e.to_string())?;
response.text().await.map_err(|e| e.to_string())
}
// 模拟耗时操作
#[tauri::command]
async fn process_large_file(path: String) -> Result<u64, String> {
// 在 spawn_blocking 中运行 CPU 密集型任务,不占用 async 线程
tokio::task::spawn_blocking(move || {
let content = std::fs::read(&path)
.map_err(|e| e.to_string())?;
// 复杂计算...
Ok(content.len() as u64)
}).await
.map_err(|e| e.to_string())?
}
// 前端:async Command 返回 Promise,可用 await 等待
try {
const data = await invoke<string>('fetch_data', {
url: 'https://api.example.com/data'
});
console.log(data);
} catch (error) {
// Rust 返回 Err(...) 时,Promise reject,error 是错误字符串
console.error('Failed:', error);
}
错误处理
use serde::Serialize;
use thiserror::Error;
// 自定义错误类型,实现 Serialize 才能传递到前端
#[derive(Debug, Error, Serialize)]
enum AppError {
#[error("File not found: {path}")]
FileNotFound { path: String },
#[error("Permission denied")]
PermissionDenied,
#[error("IO error: {0}")]
Io(
#[from]
#[serde(skip)]
std::io::Error
),
}
#[tauri::command]
fn read_config(path: String) -> Result<String, AppError> {
if !std::path::Path::new(&path).exists() {
return Err(AppError::FileNotFound { path });
}
let content = std::fs::read_to_string(&path)?; // ? 触发 From 转换
Ok(content)
}
// 前端接收结构化错误
try {
const config = await invoke<string>('read_config', { path: 'config.toml' });
} catch (error) {
// error 是 Rust 错误结构的 JSON 表示
console.error(error); // "File not found: config.toml"
}
共享状态:State 管理
use std::sync::Mutex;
use tauri::State;
// 定义全局状态(用 Mutex 包装以实现线程安全)
struct AppState {
counter: Mutex<i64>,
config: Mutex<Option<String>>,
}
// 在 Command 中通过 State 访问共享状态
#[tauri::command]
fn increment(state: State<AppState>) -> i64 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
#[tauri::command]
fn get_counter(state: State<AppState>) -> i64 {
*state.counter.lock().unwrap()
}
// 注册时通过 manage() 注入初始状态
pub fn run() {
tauri::Builder::default()
.manage(AppState {
counter: Mutex::new(0),
config: Mutex::new(None),
})
.invoke_handler(tauri::generate_handler![increment, get_counter])
.run(tauri::generate_context!())
.expect("error");
}
Command 命名规范
Rust 函数名使用 snake_case(如 get_system_info),前端 invoke 时也使用 snake_case 字符串。Tauri 不做自动的驼峰/蛇形转换(仅参数字段名做转换)。保持一致的命名风格可减少调试时的困惑。
Command 参数的高级用法
Tauri Commands 支持一些特殊的参数注入,不需要从前端传递:
use tauri::{AppHandle, Window, State, Env};
// AppHandle:访问 Tauri 应用句柄(emit 事件、获取窗口等)
// Window:当前调用此 command 的窗口引用
// State:之前用 manage() 注入的状态
// 这些参数由 Tauri 自动注入,不需要前端传递
#[tauri::command]
async fn complex_operation(
app: AppHandle, // 自动注入:应用句柄
window: Window, // 自动注入:调用方窗口
state: State<AppState>, // 自动注入:共享状态
user_arg: String, // 从前端传来的参数
count: u32, // 从前端传来的参数
) -> Result<String, String> {
// 获取当前窗口标题
let title = window.title().unwrap_or_default();
// 向前端发送进度事件
app.emit("progress", 50).unwrap();
// 访问共享状态
let counter = state.counter.lock().unwrap();
Ok(format!("Window: {}, Counter: {}, Arg: {}", title, *counter, user_arg))
}
Command 的多文件组织
随着项目增长,所有 Commands 放在一个文件会变得难以维护。推荐按功能模块拆分:
// src-tauri/src/commands/mod.rs
pub mod file_commands;
pub mod system_commands;
pub mod data_commands;
// src-tauri/src/commands/file_commands.rs
#[tauri::command]
pub fn read_file(path: String) -> Result<String, String> {
std::fs::read_to_string(&path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn write_file(path: String, content: String) -> Result<(), String> {
std::fs::write(&path, content).map_err(|e| e.to_string())
}
// src-tauri/src/commands/system_commands.rs
#[derive(serde::Serialize)]
pub struct SystemInfo {
pub os: String,
pub arch: String,
}
#[tauri::command]
pub fn get_system_info() -> SystemInfo {
SystemInfo {
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
}
}
// src-tauri/src/lib.rs(汇总注册)
mod commands;
use commands::{file_commands::*, system_commands::*};
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
read_file,
write_file,
get_system_info,
])
.run(tauri::generate_context!())
.unwrap();
}
完整实战:系统信息面板
综合运用同步/异步 Command、结构体序列化和错误处理,实现一个系统信息查询面板:
// src-tauri/src/lib.rs
use serde::Serialize;
#[derive(Serialize)]
pub struct DetailedSystemInfo {
os_name: String,
os_version: String,
architecture: String,
cpu_count: usize,
hostname: String,
}
#[tauri::command]
pub fn get_detailed_info() -> Result<DetailedSystemInfo, String> {
let hostname = hostname::get()
.map(|h| h.to_string_lossy().to_string())
.unwrap_or_else(|_| "unknown".to_string());
Ok(DetailedSystemInfo {
os_name: std::env::consts::OS.to_string(),
os_version: os_info::get().version().to_string(),
architecture: std::env::consts::ARCH.to_string(),
cpu_count: num_cpus::get(),
hostname,
})
}
import { invoke } from '@tauri-apps/api/core';
import { useEffect, useState } from 'react';
interface DetailedSystemInfo {
osName: string;
osVersion: string;
architecture: string;
cpuCount: number;
hostname: string;
}
function SystemInfoPanel() {
const [info, setInfo] = useState<DetailedSystemInfo | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
invoke<DetailedSystemInfo>('get_detailed_info')
.then(setInfo)
.catch(e => setError(String(e)));
}, []);
if (error) return <p className="error">Error: {error}</p>;
if (!info) return <p>Loading...</p>;
return (
<table>
<tr><td>操作系统</td><td>{info.osName} {info.osVersion}</td></tr>
<tr><td>架构</td><td>{info.architecture}</td></tr>
<tr><td>CPU 核心数</td><td>{info.cpuCount}</td></tr>
<tr><td>主机名</td><td>{info.hostname}</td></tr>
</table>
);
}
invoke 调用的常见错误
command not found
Command 未在
generate_handler! 中注册,或函数名拼写错误(大小写敏感,必须完全匹配)。invalid args
前端传递的参数类型与 Rust 参数类型不匹配,如传 number 但 Rust 期望 String,或缺少必填参数。
Permission denied
没有在 capabilities 中声明对应插件的权限,Tauri 的安全层拦截了调用。
不能在 SSR 中调用 invoke
使用 SvelteKit/Next.js 等 SSR 框架时,invoke 只能在浏览器(客户端)环境调用,不能在服务端渲染阶段调用。确保在
onMount/useEffect 等客户端生命周期中使用。本章小结
本章核心要点
- Commands 是 Tauri IPC 核心:用
#[tauri::command]标记 Rust 函数,用generate_handler![]注册,前端用invoke('function_name', args)调用;整个过程通过 JSON 序列化传递数据。 - 类型映射:TypeScript string → Rust String/&str,number → i32/u64/f64,boolean → bool,null/undefined → Option<T>,array → Vec<T>,object → #[derive(Deserialize)] struct。
- 错误处理:返回
Result<T, E>,Ok(v) → Promise.resolve(v),Err(e) → Promise.reject(e);错误类型需实现 Serialize 或转为 String。 - 自动注入参数:AppHandle、Window、State<T> 由 Tauri 自动注入,不需要前端传递;用于访问应用句柄、当前窗口和共享状态。
- async Command:耗时操作用 async fn 避免阻塞;CPU 密集型任务用 spawn_blocking 在线程池执行;网络请求使用 reqwest。
- 多模块组织:按功能拆分 commands/ 目录,每个模块 pub 导出 command 函数;在 lib.rs 中统一 use 和注册。