iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Rust

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

[Day 10] 狀態管理:在 Rust 後端共享資料 (State)

  • 分享至 

  • xImage
  •  

在前面的文章中,我們學會了如何建立 Command 來處理前端的請求。但隨著應用程式功能越來越豐富,我們常常會遇到一個問題:不同的 Command 之間需要共享資料。

想像一下,如果你的應用程式需要管理資料庫連線池、儲存使用者的登入狀態、或是快取一些常用的資料,你會怎麼做?如果每個 Command 都各自處理這些資料,不僅效率很差,還可能造成資料不一致的問題。

為什麼需要 State 機制?

也許你會想:「那我直接用 static mut 全域變數不就好了嗎?」這個想法很自然,但在多執行緒環境下,這樣做其實是很危險的!可能會造成競爭條件(Race Condition)和資料損壞的問題。

對此,Tauri 提供了一個既優雅又安全的解決方案:State 機制。它讓我們可以放心地在不同 Command 之間共享資料,完全不用擔心執行緒安全的問題。

State 機制最棒的地方就是:

  • 執行緒安全:不管有多少個 Command 同時在跑,都不會有資料競爭的問題
  • 自動管理:整個生命週期都由 Tauri 框架幫你處理,不用自己操心
  • 簡單易用:只需要幾行程式碼就能搞定

實作狀態管理

在此用一個簡單的計數器為例,讓不同的 Command 都能存取和修改它。

首先,我們需要定義一個結構體來存放狀態資料。為了確保執行緒安全,我們會用 Mutex 來包裝資料:

use std::sync::Mutex;
use tauri::{Builder, Manager, State};

// 定義應用程式狀態結構
#[derive(Default)]
struct AppState {
    counter: i32,
}

#[tauri::command]
fn increment_counter(state: State<Mutex<AppState>>) -> i32 {
    let mut app_state = state.lock().unwrap();
    app_state.counter += 1;
    app_state.counter
}

#[tauri::command]
fn get_counter(state: State<Mutex<AppState>>) -> i32 {
    let app_state = state.lock().unwrap();
    app_state.counter
}

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(Mutex::new(AppState::default()));
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            increment_counter,
            get_counter
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

程式碼解析:每個部分都在做什麼?

什麼是 Mutex?

Mutex(Mutual Exclusion,互斥鎖)就像是一個排隊機制。想像你家只有一個廁所,同一時間只能有一個人使用,其他人就要排隊等候。

在程式裡也是一樣的道理:當多個 Command 同時想要存取我們的 counter 資料時,Mutex 確保同一時間只有一個 Command 能進去「使用」,其他的就要乖乖排隊等候。這樣就不會有資料被搞亂的問題了!

狀態注入 (State Injection)

fn increment_counter(state: State<Mutex<AppState>>) -> i32

你有沒有注意到,我們的 Command 函數有一個 state 參數,但我們從來沒有手動傳入過?這就是 Tauri 的魔法!

這個機制叫做「依賴注入」:

  1. State<T> 告訴 Tauri:「嘿,我需要這個狀態資料」
  2. Tauri 就會自動把之前註冊的狀態傳給你
  3. 你的函數完全不用知道這個狀態是怎麼建立的,專心處理業務邏輯就好
  4. 整個狀態的生命週期都由 Tauri 框架管理

使用 Mutex:安全地存取共享資料

let mut app_state = state.lock().unwrap();

這行程式碼做了幾件事:

  1. state.lock() 就像是「敲廁所門」,看看現在能不能進去
  2. 如果有其他 Command 正在使用,我們就會在這裡等待
  3. 等到輪到我們時,就可以安全地存取 counter
  4. 當變數離開作用域時,鎖會自動釋放,下一個等候的 Command 就能接著使用

小提醒:這裡用 unwrap() 是為了讓範例簡潔,實際專案中建議適當處理可能的錯誤情況。

註冊狀態:告訴 Tauri 你有這個資料

app.manage(Mutex::new(AppState::default()));

這行程式碼的作用是:

  1. AppState::default() 建立一個初始的狀態實例
  2. Mutex::new() 用 Mutex 包裝起來,確保執行緒安全
  3. app.manage() 把這個狀態註冊到 Tauri 的依賴注入系統中
  4. 之後任何 Command 都可以透過 State<Mutex<AppState>> 參數來存取這個狀態

前端的狀態操作

現在讓我們看看前端要怎麼跟這些 Command 互動。其實跟之前學的差不多,就是呼叫不同的 Command 來操作共享狀態:

<template>
  <div>
    <h3>計數器: {{ counter }}</h3>
    <button @click="increment">增加</button>
    <button @click="refreshCounter">重新整理</button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { invoke } from '@tauri-apps/api/core'

const counter = ref(0)

async function increment() {
  counter.value = await invoke('increment_counter')
}

async function refreshCounter() {
  counter.value = await invoke('get_counter')
}

onMounted(async () => {
  await refreshCounter()
})
</script>

這個範例很簡單:

  • 點擊「增加」按鈕會呼叫 increment_counter Command
  • 點擊「重新整理」按鈕會呼叫 get_counter Command 來取得最新的計數值
  • 頁面載入時會自動取得當前的計數值

小結

透過 Tauri 的 State 機制,我們輕鬆建立了一個既安全又強大的狀態管理系統。重點回顧:

  1. State 機制解決了什麼問題:讓多個 Command 安全地共享資料
  2. Mutex 的作用:確保同一時間只有一個執行緒能存取共享資料
  3. 依賴注入的優勢:Tauri 自動管理狀態的生命週期和傳遞

有了這個基礎,你就能在 Tauri 應用程式中輕鬆處理各種複雜的狀態管理需求了!


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

尚未有邦友留言

立即登入留言