ページの作成:「== 概要 == Tauriの状態管理は、Rustバックエンドとフロントエンド (WebView) 間でアプリケーション全体の状態を共有・管理するための仕組みである。<br> <br> 従来のWebアプリケーションでは、フロントエンド側で状態を管理するのが一般的だが、TauriアプリケーションではRustバックエンド側で状態を保持し、必要に応じてフロントエンドから参照・更新で…」
 
編集の要約なし
 
12行目: 12行目:
<br>
<br>
フロントエンド (React + TypeScript) からは、invoke()関数を通じてRustコマンドを呼び出して、状態の取得・更新を行う。<br>
フロントエンド (React + TypeScript) からは、invoke()関数を通じてRustコマンドを呼び出して、状態の取得・更新を行う。<br>
<br>
<br><br>


== 状態管理の基本概念 ==
== 状態管理の基本概念 ==
332行目: 332行目:
<br>
<br>
  <syntaxhighlight lang="rust">
  <syntaxhighlight lang="rust">
  use tauri::{Builder, Manager, AppHandle};
  use tauri::{AppHandle, Manager};
  use std::sync::Mutex;
  use std::sync::Mutex;
use std::time::Duration;
   
   
  #[cfg_attr(mobile, tauri::mobile_entry_point)]
  fn start_background_task(app: AppHandle) {
pub fn run() {
    // 非同期タスクを開始
     Builder::default()
    // moveキーワードでappの所有権をタスクに移動
      .manage(Mutex::new(AppState::default()))
     tokio::spawn(async move {
      .setup(|app| {
      // 無限ループで定期的な処理を実行
          // セットアップ内で状態にアクセス
      loop {
          let state = app.state::<Mutex<AppState>>();
        // 60秒待機
          let mut state = state.lock().unwrap();
        tokio::time::sleep(Duration::from_secs(60)).await;
          state.initialized_at = Some(std::time::Instant::now());
          Ok(())
        // バックグラウンドから状態にアクセス
      })
        // AppHandleを通じて登録済みの状態を取得
      .on_window_event(|window, event| {
        let state = app.state::<Mutex<AppState>>();
          if let tauri::WindowEvent::CloseRequested { .. } = event {
            // ウインドウクローズ時に状態を保存
        // Mutexをロックして可変参照を取得
            let app = window.app_handle();
        let mut state = state.lock().unwrap();
            let state = app.state::<Mutex<AppState>>();
            let state = state.lock().unwrap();
        // 最終同期時刻を更新
            println!("Final counter value: {}", state.counter);
        state.last_sync = Some(chrono::Utc::now());
          }
      })
        // 定期的な同期処理等
      .run(tauri::generate_context!())
        println!("Background sync completed");
      .expect("error while running tauri application");
      }
    });
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
421行目: 423行目:
  <syntaxhighlight lang="typescript">
  <syntaxhighlight lang="typescript">
  // src/hooks/useCounter.ts
  // src/hooks/useCounter.ts
// カウンタ状態を管理するカスタムフック
  import { useState, useEffect, useCallback } from 'react';
  import { useState, useEffect, useCallback } from 'react';
  import { invoke } from '@tauri-apps/api/core';
  import { invoke } from '@tauri-apps/api/core';
   
   
  export function useCounter() {
  export function useCounter() {
   const [count, setCount] = useState<number>(0);
   const [count, setCount] = useState<number>(0);           // カウンタの現在値
   const [loading, setLoading] = useState<boolean>(false);
   const [loading, setLoading] = useState<boolean>(false); // 通信中かどうかのフラグ
   const [error, setError] = useState<string | null>(null);
   const [error, setError] = useState<string | null>(null); // エラーメッセージ (エラーがない場合はnull)
   
   
   // 初期値を取得
   // コンポーネントマウント時に初期値を取得
   useEffect(() => {
   useEffect(() => {
     fetchCounter();
     fetchCounter();
   }, []);
   }, []);
   
   
  // Rustバックエンドからカウンター値を取得する関数
   const fetchCounter = useCallback(async () => {
   const fetchCounter = useCallback(async () => {
     setLoading(true);
     setLoading(true);       // ロード開始
     setError(null);
     setError(null);         // エラーをクリア
     try {
     try {
      // Rustコマンド "get_counter" を呼び出し
       const value = await invoke<number>('get_counter');
       const value = await invoke<number>('get_counter');
       setCount(value);
       setCount(value);     // 取得した値をセット
     }
     }
     catch (err) {
     catch (err) {
       setError(String(err));
       setError(String(err)); // エラーをセット
     }
     }
     finally {
     finally {
       setLoading(false);
       setLoading(false);     // ロード終了
     }
     }
   }, []);
   }, []);
   
   
  // カウンタをインクリメントする関数
   const increment = useCallback(async () => {
   const increment = useCallback(async () => {
     setLoading(true);
     setLoading(true);
     setError(null);
     setError(null);
     try {
     try {
      // Rustコマンド "increment_counter" を呼び出し
      // 戻り値として新しいカウンター値を取得
       const newValue = await invoke<number>('increment_counter');
       const newValue = await invoke<number>('increment_counter');
       setCount(newValue);
       setCount(newValue);     // 新しい値をセット
       return newValue;
       return newValue;       // 呼び出し元にも値を返す
     }
     }
     catch (err) {
     catch (err) {
       setError(String(err));
       setError(String(err));
       throw err;
       throw err;             // エラーを呼び出し元に伝播
     }
     }
     finally {
     finally {
466行目: 474行目:
   }, []);
   }, []);
   
   
  // フックの使用者に公開する値と関数
   return {
   return {
     count,
     count,         // 現在のカウンタ値
     loading,
     loading,       // 通信中かどうか
     error,
     error,         // エラーメッセージ
     fetchCounter,
     fetchCounter, // 値を再取得する関数
     increment,
     increment,     // インクリメント関数
   };
   };
  }
  }
510行目: 519行目:


== React + TypeScriptでの実装例 ==
== React + TypeScriptでの実装例 ==
==== 完全な状態管理パターン ====
==== 状態管理のパターン ====
より複雑なアプリケーション向けに、Context APIと組み合わせた状態管理パターンを示す。<br>
より複雑なアプリケーション向けに、Context APIと組み合わせた状態管理パターンを示す。<br>
<br>
<br>
518行目: 527行目:
  import { invoke } from '@tauri-apps/api/core';
  import { invoke } from '@tauri-apps/api/core';
   
   
  // 型定義
  // ===== 型定義セクション =====
// アプリケーション全体の状態を表す型
  interface AppState {
  interface AppState {
   user: User | null;
   user: User | null;             // 現在のユーザ (未ログインならnull)
   settings: Settings;
   settings: Settings;             // アプリケーション設定
   notifications: Notification[];
   notifications: Notification[]; // 通知リスト
  }
  }
   
   
// ユーザ情報の型
  interface User {
  interface User {
   id: string;
   id: string;
531行目: 543行目:
  }
  }
   
   
// アプリケーション設定の型
  interface Settings {
  interface Settings {
   theme: 'light' | 'dark';
   theme: 'light' | 'dark';       // テーマ (light または dark)
   language: string;
   language: string;               // 言語設定
   notifications: boolean;
   notifications: boolean;         // 通知の有効/無効
  }
  }
   
   
// 通知メッセージの型
  interface Notification {
  interface Notification {
   id: string;
   id: string;                     // 通知の一意識別子
   message: string;
   message: string;               // 通知メッセージ本文
   timestamp: Date;
   timestamp: Date;               // タイムスタンプ
  }
  }
   
   
  // コンテキストの型
  // コンテキストが提供する値の型
  interface AppStateContextType {
  interface AppStateContextType {
   state: AppState;
   state: AppState;                                                 // 現在の状態
   loading: boolean;
   loading: boolean;                                               // ロード中かどうか
   error: string | null;
   error: string | null;                                           // エラーメッセージ
   updateUser: (user: User) => Promise<void>;
   updateUser: (user: User) => Promise<void>;                       // ユーザ更新関数
   updateSettings: (settings: Partial<Settings>) => Promise<void>;
   updateSettings: (settings: Partial<Settings>) => Promise<void>; // 設定更新関数
   addNotification: (message: string) => Promise<void>;
   addNotification: (message: string) => Promise<void>;             // 通知追加関数
   refreshState: () => Promise<void>;
   refreshState: () => Promise<void>;                               // 状態再取得関数
  }
  }
   
   
  // コンテキストの作成
  // Reactコンテキストを作成 (初期値はnull)
  const AppStateContext = createContext<AppStateContextType | null>(null);
  const AppStateContext = createContext<AppStateContextType | null>(null);
   
   
  // プロバイダーコンポーネント
  // ===== プロバイダーコンポーネント =====
// アプリケーション全体をラップして状態を提供する
  export function AppStateProvider({ children }: { children: ReactNode }) {
  export function AppStateProvider({ children }: { children: ReactNode }) {
  // アプリケーション状態を管理
   const [state, setState] = useState<AppState>({
   const [state, setState] = useState<AppState>({
     user: null,
     user: null,
564行目: 580行目:
     notifications: [],
     notifications: [],
   });
   });
 
  // ロード状態とエラー状態を管理
   const [loading, setLoading] = useState(false);
   const [loading, setLoading] = useState(false);
   const [error, setError] = useState<string | null>(null);
   const [error, setError] = useState<string | null>(null);
   
   
   // 状態を取得
   // Rustバックエンドから状態を取得する関数
   const refreshState = useCallback(async () => {
   const refreshState = useCallback(async () => {
     setLoading(true);
     setLoading(true);         // ロード開始
     setError(null);
     setError(null);           // エラーをクリア
     try {
     try {
      // Rustコマンドを呼び出して状態を取得
       const appState = await invoke<AppState>('get_full_state');
       const appState = await invoke<AppState>('get_full_state');
       setState(appState);
       setState(appState);     // 取得した状態をセット
     }
     }
     catch (err) {
     catch (err) {
       setError(String(err));
       setError(String(err));   // エラーをセット
     }
     }
     finally {
     finally {
       setLoading(false);
       setLoading(false);       // ロード終了
     }
     }
   }, []);
   }, []);
   
   
   // 初期化時に状態を取得
   // コンポーネントマウント時に初期状態を取得
   useEffect(() => {
   useEffect(() => {
     refreshState();
     refreshState();
   }, [refreshState]);
   }, [refreshState]);
   
   
   // ユーザを更新
   // ユーザ情報を更新する関数
   const updateUser = useCallback(async (user: User) => {
   const updateUser = useCallback(async (user: User) => {
     setLoading(true);
     setLoading(true);
     setError(null);
     setError(null);
     try {
     try {
      // Rustコマンドでユーザを更新
       await invoke('update_user', { user });
       await invoke('update_user', { user });
      // ローカル状態も更新
       setState(prev => ({ ...prev, user }));
       setState(prev => ({ ...prev, user }));
     }
     }
     catch (err) {
     catch (err) {
       setError(String(err));
       setError(String(err));
       throw err;
       throw err; // 呼び出し元にエラーを伝播
     }
     }
     finally {
     finally {
605行目: 626行目:
   }, []);
   }, []);
   
   
   // 設定を更新
   // 設定を部分的に更新する関数
   const updateSettings = useCallback(async (settings: Partial<Settings>) => {
   const updateSettings = useCallback(async (settings: Partial<Settings>) => {
     setLoading(true);
     setLoading(true);
     setError(null);
     setError(null);
     try {
     try {
      // Rustコマンドで設定を更新し、新しい設定を取得
       const newSettings = await invoke<Settings>('update_settings', { settings });
       const newSettings = await invoke<Settings>('update_settings', { settings });
      // ローカル状態を更新
       setState(prev => ({ ...prev, settings: newSettings }));
       setState(prev => ({ ...prev, settings: newSettings }));
     }
     }
622行目: 645行目:
   }, []);
   }, []);
   
   
   // 通知を追加
   // 新しい通知を追加する関数
   const addNotification = useCallback(async (message: string) => {
   const addNotification = useCallback(async (message: string) => {
     try {
     try {
      // Rustコマンドで通知を作成
       const notification = await invoke<Notification>('add_notification', { message });
       const notification = await invoke<Notification>('add_notification', { message });
      // 通知リストに追加
       setState(prev => ({
       setState(prev => ({
         ...prev,
         ...prev,
632行目: 657行目:
     }
     }
     catch (err) {
     catch (err) {
      // 通知追加の失敗はコンソールにログ出力のみ
       console.error('Failed to add notification:', err);
       console.error('Failed to add notification:', err);
     }
     }
   }, []);
   }, []);
   
   
  // コンテキストプロバイダを返す
   return (
   return (
     <AppStateContext.Provider
     <AppStateContext.Provider
653行目: 680行目:
  }
  }
   
   
  // カスタムフック
  // ===== カスタムフック =====
// 他のコンポーネントから状態にアクセスするためのフック
  export function useAppState() {
  export function useAppState() {
   const context = useContext(AppStateContext);
   const context = useContext(AppStateContext);
  // プロバイダ外で使用された場合はエラーをスロー
   if (!context) {
   if (!context) {
     throw new Error('useAppState must be used within AppStateProvider');
     throw new Error('useAppState must be used within AppStateProvider');
670行目: 699行目:
  use serde::{Deserialize, Serialize};
  use serde::{Deserialize, Serialize};
   
   
// ユーザ情報を表す構造体
  #[derive(Debug, Clone, Serialize, Deserialize)]
  #[derive(Debug, Clone, Serialize, Deserialize)]
  struct User {
  struct User {
677行目: 707行目:
  }
  }
   
   
// アプリケーション設定を表す構造体
  #[derive(Debug, Clone, Serialize, Deserialize)]
  #[derive(Debug, Clone, Serialize, Deserialize)]
  struct Settings {
  struct Settings {
     theme: String,
     theme: String,       // テーマ ("light" または "dark")
     language: String,
     language: String,     // 言語設定
     notifications: bool,
     notifications: bool, // 通知の有効/無効
  }
  }
   
   
// 通知メッセージを表す構造体
  #[derive(Debug, Clone, Serialize, Deserialize)]
  #[derive(Debug, Clone, Serialize, Deserialize)]
  struct Notification {
  struct Notification {
     id: String,
     id: String,           // 通知の一意識別子
     message: String,
     message: String,     // 通知メッセージ本文
     timestamp: String,
     timestamp: String,   // タイムスタンプ (ISO 8601形式)
  }
  }
   
   
// アプリケーション全体の状態を管理する構造体
  #[derive(Debug, Default)]
  #[derive(Debug, Default)]
  struct AppState {
  struct AppState {
     user: Option<User>,
     user: Option<User>,             // 現在のユーザー (未ログインならNone)
     settings: Settings,
     settings: Settings,             // アプリケーション設定
     notifications: Vec<Notification>,
     notifications: Vec<Notification>, // 通知リスト
  }
  }
   
   
// Settings構造体のデフォルト値を定義
  impl Default for Settings {
  impl Default for Settings {
     fn default() -> Self {
     fn default() -> Self {
708行目: 742行目:
  }
  }
   
   
// アプリケーション状態全体を取得するコマンド
  #[tauri::command]
  #[tauri::command]
  fn get_full_state(state: State<'_, Mutex<AppState>>) -> serde_json::Value {
  fn get_full_state(state: State<'_, Mutex<AppState>>) -> serde_json::Value {
     let state = state.lock().unwrap();
     let state = state.lock().unwrap(); // Mutexをロック
    // 状態をJSONとして返す
     serde_json::json!({
     serde_json::json!({
       "user": state.user,
       "user": state.user,
718行目: 754行目:
  }
  }
   
   
// ユーザ情報を更新するコマンド
  #[tauri::command]
  #[tauri::command]
  fn update_user(state: State<'_, Mutex<AppState>>, user: User) {
  fn update_user(state: State<'_, Mutex<AppState>>, user: User) {
     let mut state = state.lock().unwrap();
     let mut state = state.lock().unwrap(); // Mutexをロック (可変参照)
     state.user = Some(user);
     state.user = Some(user);               // ユーザ情報を設定
  }
  }
   
   
// 設定を部分的に更新するコマンド
  #[tauri::command]
  #[tauri::command]
  fn update_settings(
  fn update_settings(
     state: State<'_, Mutex<AppState>>,
     state: State<'_, Mutex<AppState>>,
     settings: PartialSettings,
     settings: PartialSettings, // 部分的な設定データ
  ) -> Settings {
  ) -> Settings {
     let mut state = state.lock().unwrap();
     let mut state = state.lock().unwrap();
    // 各フィールドがSomeの場合のみ更新
     if let Some(theme) = settings.theme {
     if let Some(theme) = settings.theme {
       state.settings.theme = theme;
       state.settings.theme = theme;
739行目: 778行目:
       state.settings.notifications = notifications;
       state.settings.notifications = notifications;
     }
     }
     state.settings.clone()
     state.settings.clone() // 更新後の設定を返す
  }
  }
   
   
// 部分的な設定データ (全フィールドがOptional)
  #[derive(Deserialize)]
  #[derive(Deserialize)]
  struct PartialSettings {
  struct PartialSettings {
749行目: 789行目:
  }
  }
   
   
// 新しい通知を追加するコマンド
  #[tauri::command]
  #[tauri::command]
  fn add_notification(
  fn add_notification(
     state: State<'_, Mutex<AppState>>,
     state: State<'_, Mutex<AppState>>,
     message: String,
     message: String, // 通知メッセージ
  ) -> Notification {
  ) -> Notification {
     let mut state = state.lock().unwrap();
     let mut state = state.lock().unwrap();
    // 新しい通知を作成
     let notification = Notification {
     let notification = Notification {
       id: uuid::Uuid::new_v4().to_string(),
       id: uuid::Uuid::new_v4().to_string(),         // UUIDを生成
       message,
       message,
       timestamp: chrono::Utc::now().to_rfc3339(),
       timestamp: chrono::Utc::now().to_rfc3339(),   // 現在時刻をISO形式で設定
     };
     };
     state.notifications.push(notification.clone());
     state.notifications.push(notification.clone()); // 通知リストに追加
     notification
     notification // 作成した通知を返す
  }
  }
   
   
// アプリケーションのエントリーポイント
  #[cfg_attr(mobile, tauri::mobile_entry_point)]
  #[cfg_attr(mobile, tauri::mobile_entry_point)]
  pub fn run() {
  pub fn run() {
     Builder::default()
     Builder::default()
       .manage(Mutex::new(AppState::default()))
       .manage(Mutex::new(AppState::default()))   // 状態を登録
       .invoke_handler(tauri::generate_handler![
       .invoke_handler(tauri::generate_handler![ // コマンドを登録
           get_full_state,
           get_full_state,
           update_user,
           update_user,