iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0
Rust

用 Tauri 打造你的應用程式系列 第 8

[Day 07] 網頁前端與 Rust 後端之間的溝通 (二):Command

  • 分享至 

  • xImage
  •  

在了解了 IPC 的基礎概念後,我們來深入探討其中最常用的機制:Command。Command 是前端向後端發起請求,並獲取回應的標準途徑,讓我們的 Webview 介面能夠安全地驅動強大的 Rust 後端邏輯。

Command 的核心概念

Command 本質上是一個被特殊標記的 Rust 函式。Tauri 會自動將這個函式暴露給前端,使其可以透過 JavaScript 呼叫。這種「白名單」式的設計確保了極高的安全性,因為只有被 #[tauri::command] 巨集(Macro)明確標記的函式才能被前端存取,杜絕了任意程式碼執行的風險。

補充說明:巨集(Macro)是 Rust 語言的特性,能在編譯時期自動生成代碼。#[tauri::command] 會自動處理序列化、反序列化和 IPC 通訊的細節,讓開發者專注於業務邏輯。

透過 Command,我們可以輕鬆實現以往在瀏覽器中無法完成的功能,例如讀取本地設定檔、執行複雜的資料處理、或是與資料庫互動。

Command 範例

讓我們用當初在 Day04 自動建立的範例,來看看怎麼用 Command。

1. 後端:定義並註冊 Command

src-tauri/src/lib.rs 中,我們定義一個 Rust 函式,並用巨集標記它:

// 使用 `#[tauri::command]` 巨集將其標記為一個 Command
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        // 使用 `generate_handler!` 巨集註冊我們定義的 Command
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

2. 前端:呼叫 Command

在 Vue 元件中,我們使用 @tauri-apps/api 套件提供的 invoke 函式來呼叫後端 Command。

import { ref } from "vue";
import { invoke } from "@tauri-apps/api/core";

const greetMsg = ref("");
const name = ref("");

async function greet() {
  greetMsg.value = await invoke("greet", { name: name.value });
}

傳遞複雜物件

Tauri 支援直接傳遞複雜的 JavaScript 物件給 Rust。讓我們看一個更實際的例子:

後端定義

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct UserInfo {
    name: String,
    age: u32,
    email: String,
}

#[derive(Serialize, Deserialize)]
struct ApiResponse {
    success: bool,
    message: String,
    user_id: Option<u32>,
}

#[tauri::command]
fn create_user(user: UserInfo) -> ApiResponse {
    // 模擬建立使用者的邏輯
    println!("正在建立使用者: {} ({}歲)", user.name, user.age);
    
    ApiResponse {
        success: true,
        message: format!("使用者 {} 建立成功!", user.name),
        user_id: Some(12345),
    }
}

前端呼叫

import { invoke } from "@tauri-apps/api/core";

async function createNewUser() {
  try {
    const userData = {
      name: "張小明",
      age: 25,
      email: "ming@example.com"
    };
    
    const response = await invoke("create_user", { user: userData });
    
    console.log("建立結果:", response);
  } catch (error) {
    console.error("建立失敗:", error);
  }
}

記得在 lib.rs 中註冊新的 Command:

.invoke_handler(tauri::generate_handler![greet, create_user])

進階處理:非同步與錯誤

在真實世界中,許多操作並非立即完成,且有可能失敗。Tauri 的 Command 機制對此提供了優雅的支援。

  • 非同步 Command:只需將 Rust 函式簽名改為 async fn,Tauri 就會自動在背景執行緒中執行它,避免阻塞 UI。

  • 錯誤處理:讓 Rust 函式回傳 Result<T, E> 型別。當回傳 Ok(value) 時,前端 invoke 會成功解析出 value。當回傳 Err(error) 時,前端的 await invoke(...) 會拋出一個異常,可以被 try...catch 區塊捕獲。

#[tauri::command]
async fn check_file_exists(path: String) -> Result<bool, String> {
    // 這裡是一個模擬的耗時操作
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;

    if std::path::Path::new(&path).exists() {
        Ok(true)
    } else {
        // 當回傳 Err 時,前端的 invoke Promise 會被 reject
        Err("找不到指定的檔案".to_string())
    }
}

在前端呼叫時,如果 Rust 回傳 Ok(value)invoke 的 Promise 會 resolve 並回傳 value。如果 Rust 回傳 Err(error_message),Promise 則會 reject,並將 error_message 作為錯誤原因,可以被 catch 區塊捕獲。

小結

Command 是 Tauri 應用中實現「前端發起,後端執行」這種互動模式的基石。透過 #[tauri::command]invoke,我們可以輕鬆且安全地在 JavaScript 與 Rust 之間傳遞結構化資料,並優雅地處理同步與非同步任務。

然而,Command 是一對一的「請求-回應」模式。如果我們需要後端主動、持續地向前端推送訊息,例如通知進度條更新或發送即時聊天訊息,就需要使用明天要介紹的:Event。


上一篇
[Day 07] 網頁前端與 Rust 後端之間的溝通 (一):IPC
下一篇
[Day 09] 網頁前端與 Rust 後端之間的溝通 (三):Event
系列文
用 Tauri 打造你的應用程式10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言