MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Tauriの基礎 - 状態管理のソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Tauriの基礎 - 状態管理
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == Tauriの状態管理は、Rustバックエンドとフロントエンド (WebView) 間でアプリケーション全体の状態を共有・管理するための仕組みである。<br> <br> 従来のWebアプリケーションでは、フロントエンド側で状態を管理するのが一般的だが、TauriアプリケーションではRustバックエンド側で状態を保持し、必要に応じてフロントエンドから参照・更新できる。<br> これにより、ファイルシステムへのアクセス、データベース操作、ネットワーク通信等、OSレベルのリソースを必要とする状態を安全に管理できる。<br> <br> Tauriの状態管理は、<code>manage()</code> メソッドによる状態の登録、<code>tauri::State<T></code> によるコマンド内でのアクセス、<code>Mutex<T></code> による可変状態の実現を組み合わせて実装する。<br> <br> また、非同期処理が必要な場合は、<code>tokio::sync::Mutex</code> を使用することにより、awaitポイントを跨いだロック保持が可能となる。<br> <br> AppHandleを使用することで、アプリケーションの任意の場所から状態にアクセスすることもでき、柔軟なアーキテクチャ設計をサポートする。<br> <br> フロントエンド (React + TypeScript) からは、invoke()関数を通じてRustコマンドを呼び出して、状態の取得・更新を行う。<br> <br> == 状態管理の基本概念 == ==== アーキテクチャ概要 ==== Tauriアプリケーションは、RustバックエンドとWebViewフロントエンドの2つの主要なコンポーネントで構成される。<br> <br> 状態管理の点から見ると、以下に示す特徴がある。<br> <br> * Rustバックエンド *: アプリケーションのコアロジック、OS APIへのアクセス、永続化処理を担当 *: 状態はここで保持され、スレッドセーフに管理される。 *: <br> * WebViewフロントエンド *: ユーザインターフェース (React等) を担当 *: Rustコマンドを通じて間接的に状態にアクセス *: <br> * IPCレイヤー *: RustとWebView間の通信を仲介 *: JSONベースのメッセージパッシング <br> ==== 状態のライフサイクル ==== Tauriアプリケーションにおける状態のライフサイクルは以下の通りである。<br> <br> # アプリケーション起動時に、<code>Builder::manage()</code> で状態を登録する。 # コマンド呼び出し時に<code>State<T></code>パラメータで自動注入する。 # 状態はアプリケーション終了まで保持される。 # 複数のコマンド間で同じ状態インスタンスを共有する。 <br> ==== なぜRust側で状態を管理するのか ==== * セキュリティ *: フロントエンドは潜在的に信頼できないコードを実行する可能性がある。 *: 機密データはRust側で保護された状態で管理 * パフォーマンス *: 重い計算処理はRust側で効率的に実行 *: ネイティブAPIへの直接アクセス * 永続化 *: ファイルシステムやデータベースへのアクセスはRust側で一元管理 <br><br> == manage()によるState登録 == ==== 基本的な登録方法 ==== <code>Builder::manage()</code> メソッドを使用して、アプリケーション起動時に状態を登録する。<br> <br> <syntaxhighlight lang="rust"> // src-tauri/src/lib.rs use std::sync::Mutex; use tauri::Builder; // アプリケーション状態を定義 #[derive(Default)] struct AppState { counter: u32, user_name: Option<String>, settings: AppSettings, } #[derive(Default)] struct AppSettings { theme: String, language: String, } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() // 状態を登録 (Mutexでラップして可変に) .manage(Mutex::new(AppState::default())) .invoke_handler(tauri::generate_handler![ get_counter, increment_counter, set_user_name, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== 複数の状態の登録 ==== 異なるタイプの状態を複数登録することも可能である。<br> <br> <syntaxhighlight lang="rust"> use std::sync::Mutex; use tauri::Builder; // データベース接続プール struct DatabasePool { connections: Vec<Connection>, } // キャッシュ struct Cache { data: HashMap<String, String>, } // アプリケーション設定 struct AppConfig { debug_mode: bool, api_endpoint: String, } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .manage(Mutex::new(DatabasePool::new())) .manage(Mutex::new(Cache::new())) .manage(AppConfig { debug_mode: false, api_endpoint: "https://api.example.com".to_string(), }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== 状態の初期化 ==== 状態は必ずしも <code>Default</code> トレイトを実装する必要はない。<br> カスタム初期化も可能である。<br> <br> <syntaxhighlight lang="rust"> use std::sync::Mutex; use tauri::Builder; struct AppState { counter: u32, initialized_at: std::time::Instant, } impl AppState { fn new() -> Self { Self { counter: 0, initialized_at: std::time::Instant::now(), } } } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() // カスタム初期化 .manage(Mutex::new(AppState::new())) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br><br> == tauri::State<T>によるコマンド内アクセス == ==== 読み取り専用アクセス ==== <code>State<T></code> パラメータをコマンド関数に追加することにより、状態への参照を取得できる。<br> <br> <syntaxhighlight lang="rust"> use tauri::State; use std::sync::Mutex; #[tauri::command] fn get_counter(state: State<'_, Mutex<AppState>>) -> u32 { // ロックを取得して値を読み取る let state = state.lock().unwrap(); state.counter } #[tauri::command] fn get_user_name(state: State<'_, Mutex<AppState>>) -> Option<String> { let state = state.lock().unwrap(); state.user_name.clone() } </syntaxhighlight> <br> ==== 自動注入の仕組み ==== Tauriは、コマンド関数のパラメータに <code>State<T></code> が含まれている場合、自動的に登録済みの状態を注入する。<br> <br> * 依存性注入 (Dependency Injection) パターン * 型推論により適切な状態が選択される。 * 複数の <code>State<T></code> パラメータも可能 <br> <syntaxhighlight lang="rust"> use tauri::State; use std::sync::Mutex; #[tauri::command] fn get_combined_info( app_state: State<'_, Mutex<AppState>>, config: State<'_, AppConfig>, ) -> String { let state = app_state.lock().unwrap(); format!( "User: {:?}, Counter: {}, Debug: {}", state.user_name, state.counter, config.debug_mode ) } </syntaxhighlight> <br> ==== Arcが不要な理由 ==== Tauriの <code>State<T></code> は、内部的に <code>Arc<T></code> でラップされているため、開発者が明示的に <code>Arc</code> を使用する必要はない。<br> <br> <syntaxhighlight lang="rust"> // 正しい例 .manage(Mutex::new(AppState::default())) // 不要な例 let state = Arc::new(Mutex::new(AppState::default())); .manage(state) </syntaxhighlight> <br><br> == Mutex<T>による可変State == ==== std::sync::Mutex (同期版) ==== 同期コマンド (非async関数) で使用する。<br> ロック取得は、<code>.lock().unwrap()</code> で行う。<br> <br> <syntaxhighlight lang="rust"> use std::sync::Mutex; use tauri::State; #[tauri::command] fn increment_counter(state: State<'_, Mutex<AppState>>) -> u32 { // ミュータブルなアクセスを取得 let mut state = state.lock().unwrap(); state.counter += 1; state.counter } #[tauri::command] fn set_user_name(state: State<'_, Mutex<AppState>>, name: String) { let mut state = state.lock().unwrap(); state.user_name = Some(name); } </syntaxhighlight> <br> ==== tokio::sync::Mutex (非同期版) ==== 非同期コマンド (async関数) で、awaitポイントを跨いでロックを保持する場合に使用する。<br> <br> <syntaxhighlight lang="rust"> use tokio::sync::Mutex; use tauri::State; #[tauri::command] async fn increment_counter_async(state: State<'_, Mutex<AppState>>) -> Result<u32, String> { // 非同期ロックを取得 let mut state = state.lock().await; state.counter += 1; Ok(state.counter) } #[tauri::command] async fn fetch_and_cache_data( state: State<'_, Mutex<AppState>>, url: String, ) -> Result<String, String> { let mut state = state.lock().await; // 非同期操作 (awaitポイント) let response = reqwest::get(&url).await .map_err(|e| e.to_string())?; let body = response.text().await .map_err(|e| e.to_string())?; // ロックを保持したまま状態を更新 state.cached_data = Some(body.clone()); Ok(body) } </syntaxhighlight> <br> ==== Mutexの種類の使い分け ==== <center> {| class="wikitable" |+ Mutexの選択基準 |- ! 種類 !! 使用場面 !! ロック取得方法 !! 注意点 |- | std::sync::Mutex || 同期コマンド || .lock().unwrap() || awaitポイントを跨げない |- | tokio::sync::Mutex || 非同期コマンド || .lock().await || 非同期コンテキストが必要 |- | std::sync::RwLock || 読み取りが多い場合 || .read()/.write() || 書き込みは排他的 |} </center><br> <br> ==== ロックエラーのハンドリング ==== <code>.lock().unwrap()</code> は、ロック取得に失敗した場合にパニックを起こす。<br> より安全なエラーハンドリングには、<code>.lock().expect()</code> や <code>match</code> を使用する。<br> <br> <syntaxhighlight lang="rust"> use std::sync::Mutex; use tauri::State; #[tauri::command] fn safe_increment(state: State<'_, Mutex<AppState>>) -> Result<u32, String> { // 安全なロック取得 let mut state = state.lock() .map_err(|e| format!("Failed to acquire lock: {}", e))?; state.counter += 1; Ok(state.counter) } </syntaxhighlight> <br><br> == AppHandleからのState取得 == ==== Managerトレイトの使用 ==== <code>AppHandle</code> は、<code>Manager</code>トレイトを実装しており、<code>.state::<T>()</code> メソッドで状態にアクセスできる。<br> <br> <syntaxhighlight lang="rust"> use tauri::{AppHandle, Manager}; use std::sync::Mutex; #[tauri::command] fn get_state_via_handle(app: AppHandle) -> u32 { // AppHandleから状態を取得 let state = app.state::<Mutex<AppState>>(); let state = state.lock().unwrap(); state.counter } </syntaxhighlight> <br> ==== イベントハンドラでの使用 ==== イベントハンドラ内では、<code>AppHandle</code> を使用して状態にアクセスする。<br> <br> <syntaxhighlight lang="rust"> use tauri::{Builder, Manager, AppHandle}; use std::sync::Mutex; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .manage(Mutex::new(AppState::default())) .setup(|app| { // セットアップ内で状態にアクセス let state = app.state::<Mutex<AppState>>(); let mut state = state.lock().unwrap(); state.initialized_at = Some(std::time::Instant::now()); Ok(()) }) .on_window_event(|window, event| { if let tauri::WindowEvent::CloseRequested { .. } = event { // ウインドウクローズ時に状態を保存 let app = window.app_handle(); let state = app.state::<Mutex<AppState>>(); let state = state.lock().unwrap(); println!("Final counter value: {}", state.counter); } }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== バックグラウンドタスクでの使用 ==== バックグラウンドスレッドやタイマ処理から状態にアクセスする場合も、<code>AppHandle</code> を使用する。<br> <br> <syntaxhighlight lang="rust"> use tauri::{AppHandle, Manager}; use std::sync::Mutex; use std::time::Duration; fn start_background_task(app: AppHandle) { tokio::spawn(async move { loop { tokio::time::sleep(Duration::from_secs(60)).await; // バックグラウンドから状態にアクセス let state = app.state::<Mutex<AppState>>(); let mut state = state.lock().unwrap(); state.last_sync = Some(chrono::Utc::now()); // 定期的な同期処理等 println!("Background sync completed"); } }); } </syntaxhighlight> <br><br> == フロントエンドからのCommand経由参照 == ==== invoke()の基本 ==== フロントエンド (React + TypeScript) からは、<code>@tauri-apps/api/core</code> の <code>invoke()</code> 関数を使用してRustコマンドを呼び出す。<br> <br> <syntaxhighlight lang="typescript"> // src/hooks/useAppState.ts import { invoke } from '@tauri-apps/api/core'; // 状態の型定義 interface AppState { counter: number; userName: string | null; } // カウンタを取得 export async function getCounter(): Promise<number> { return await invoke<number>('get_counter'); } // カウンタをインクリメント export async function incrementCounter(): Promise<number> { return await invoke<number>('increment_counter'); } // ユーザ名を設定 export async function setUserName(name: string): Promise<void> { await invoke('set_user_name', { name }); } </syntaxhighlight> <br> ==== Reactカスタムフック ==== 状態管理用のカスタムフックを作成することにより、コンポーネントでの使用を簡素化できる。<br> <br> <syntaxhighlight lang="typescript"> // src/hooks/useCounter.ts import { useState, useEffect, useCallback } from 'react'; import { invoke } from '@tauri-apps/api/core'; export function useCounter() { const [count, setCount] = useState<number>(0); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); // 初期値を取得 useEffect(() => { fetchCounter(); }, []); const fetchCounter = useCallback(async () => { setLoading(true); setError(null); try { const value = await invoke<number>('get_counter'); setCount(value); } catch (err) { setError(String(err)); } finally { setLoading(false); } }, []); const increment = useCallback(async () => { setLoading(true); setError(null); try { const newValue = await invoke<number>('increment_counter'); setCount(newValue); return newValue; } catch (err) { setError(String(err)); throw err; } finally { setLoading(false); } }, []); return { count, loading, error, fetchCounter, increment, }; } </syntaxhighlight> <br> ==== Reactコンポーネントでの使用 ==== <syntaxhighlight lang="typescript"> // src/components/Counter.tsx import React from 'react'; import { useCounter } from '../hooks/useCounter'; export function Counter() { const { count, loading, error, increment } = useCounter(); return ( <div className="counter-container"> <h2>カウンタ</h2> {error && ( <div className="error"> エラー: {error} </div> )} <p>現在の値: {count}</p> <button onClick={increment} disabled={loading} > {loading ? '処理中...' : 'インクリメント'} </button> </div> ); } </syntaxhighlight> <br><br> == React + TypeScriptでの実装例 == ==== 完全な状態管理パターン ==== より複雑なアプリケーション向けに、Context APIと組み合わせた状態管理パターンを示す。<br> <br> <syntaxhighlight lang="typescript"> // src/context/AppStateContext.tsx import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'; import { invoke } from '@tauri-apps/api/core'; // 型定義 interface AppState { user: User | null; settings: Settings; notifications: Notification[]; } interface User { id: string; name: string; email: string; } interface Settings { theme: 'light' | 'dark'; language: string; notifications: boolean; } interface Notification { id: string; message: string; timestamp: Date; } // コンテキストの型 interface AppStateContextType { state: AppState; loading: boolean; error: string | null; updateUser: (user: User) => Promise<void>; updateSettings: (settings: Partial<Settings>) => Promise<void>; addNotification: (message: string) => Promise<void>; refreshState: () => Promise<void>; } // コンテキストの作成 const AppStateContext = createContext<AppStateContextType | null>(null); // プロバイダーコンポーネント export function AppStateProvider({ children }: { children: ReactNode }) { const [state, setState] = useState<AppState>({ user: null, settings: { theme: 'light', language: 'ja', notifications: true }, notifications: [], }); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); // 状態を取得 const refreshState = useCallback(async () => { setLoading(true); setError(null); try { const appState = await invoke<AppState>('get_full_state'); setState(appState); } catch (err) { setError(String(err)); } finally { setLoading(false); } }, []); // 初期化時に状態を取得 useEffect(() => { refreshState(); }, [refreshState]); // ユーザを更新 const updateUser = useCallback(async (user: User) => { setLoading(true); setError(null); try { await invoke('update_user', { user }); setState(prev => ({ ...prev, user })); } catch (err) { setError(String(err)); throw err; } finally { setLoading(false); } }, []); // 設定を更新 const updateSettings = useCallback(async (settings: Partial<Settings>) => { setLoading(true); setError(null); try { const newSettings = await invoke<Settings>('update_settings', { settings }); setState(prev => ({ ...prev, settings: newSettings })); } catch (err) { setError(String(err)); throw err; } finally { setLoading(false); } }, []); // 通知を追加 const addNotification = useCallback(async (message: string) => { try { const notification = await invoke<Notification>('add_notification', { message }); setState(prev => ({ ...prev, notifications: [...prev.notifications, notification], })); } catch (err) { console.error('Failed to add notification:', err); } }, []); return ( <AppStateContext.Provider value={{ state, loading, error, updateUser, updateSettings, addNotification, refreshState, }} > {children} </AppStateContext.Provider> ); } // カスタムフック export function useAppState() { const context = useContext(AppStateContext); if (!context) { throw new Error('useAppState must be used within AppStateProvider'); } return context; } </syntaxhighlight> <br> ==== 対応するRust実装 ==== <syntaxhighlight lang="rust"> // src-tauri/src/lib.rs use std::sync::Mutex; use tauri::{Builder, State}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] struct User { id: String, name: String, email: String, } #[derive(Debug, Clone, Serialize, Deserialize)] struct Settings { theme: String, language: String, notifications: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] struct Notification { id: String, message: String, timestamp: String, } #[derive(Debug, Default)] struct AppState { user: Option<User>, settings: Settings, notifications: Vec<Notification>, } impl Default for Settings { fn default() -> Self { Self { theme: "light".to_string(), language: "ja".to_string(), notifications: true, } } } #[tauri::command] fn get_full_state(state: State<'_, Mutex<AppState>>) -> serde_json::Value { let state = state.lock().unwrap(); serde_json::json!({ "user": state.user, "settings": state.settings, "notifications": state.notifications, }) } #[tauri::command] fn update_user(state: State<'_, Mutex<AppState>>, user: User) { let mut state = state.lock().unwrap(); state.user = Some(user); } #[tauri::command] fn update_settings( state: State<'_, Mutex<AppState>>, settings: PartialSettings, ) -> Settings { let mut state = state.lock().unwrap(); if let Some(theme) = settings.theme { state.settings.theme = theme; } if let Some(language) = settings.language { state.settings.language = language; } if let Some(notifications) = settings.notifications { state.settings.notifications = notifications; } state.settings.clone() } #[derive(Deserialize)] struct PartialSettings { theme: Option<String>, language: Option<String>, notifications: Option<bool>, } #[tauri::command] fn add_notification( state: State<'_, Mutex<AppState>>, message: String, ) -> Notification { let mut state = state.lock().unwrap(); let notification = Notification { id: uuid::Uuid::new_v4().to_string(), message, timestamp: chrono::Utc::now().to_rfc3339(), }; state.notifications.push(notification.clone()); notification } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .manage(Mutex::new(AppState::default())) .invoke_handler(tauri::generate_handler![ get_full_state, update_user, update_settings, add_notification, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br><br> == 推奨される事柄 == ==== スレッドセーフティ ==== * Mutexを使用する。 *: 可変状態は、必ず <code>Mutex</code> または <code>RwLock</code> で保護する。 * ロック時間を最小化 *: ロック保持中は他のスレッドがブロックされるため、処理を最小限にする。 <br> <syntaxhighlight lang="rust"> // 良い例 : ロック時間を最小化 #[tauri::command] fn good_example(state: State<'_, Mutex<AppState>>) -> String { // ロック内で必要なデータのみをコピー let value = { let state = state.lock().unwrap(); state.counter.to_string() }; // ロック解放後の処理 format!("Counter: {}", value) } // 悪い例 : ロック時間が長い #[tauri::command] fn bad_example(state: State<'_, Mutex<AppState>>) -> String { let state = state.lock().unwrap(); // 重い処理をロック中に実行 let result = expensive_computation(&state.data); result } </syntaxhighlight> <br> ==== 状態の分割 ==== 巨大な状態オブジェクトを分割し、関心ごとに分けて管理する。<br> <br> <syntaxhighlight lang="rust"> // 良い例 : 状態を分割 struct UserState { current_user: Option<User>, } struct CacheState { data: HashMap<String, String>, } struct SettingsState { theme: String, language: String, } // それぞれ個別に登録 Builder::default() .manage(Mutex::new(UserState::default())) .manage(Mutex::new(CacheState::default())) .manage(SettingsState::default()) </syntaxhighlight> <br> ==== エラーハンドリング ==== 状態操作時のエラーを適切に処理し、ユーザにフィードバックを提供する。<br> <br> <syntaxhighlight lang="rust"> use thiserror::Error; #[derive(Error, Debug)] pub enum StateError { #[error("Lock acquisition failed: {0}")] LockError(String), #[error("Invalid state: {0}")] InvalidState(String), } #[tauri::command] fn safe_operation(state: State<'_, Mutex<AppState>>) -> Result<String, StateError> { let mut state = state.lock() .map_err(|e| StateError::LockError(e.to_string()))?; if state.counter < 0 { return Err(StateError::InvalidState("Counter cannot be negative".to_string())); } Ok(state.counter.to_string()) } </syntaxhighlight> <br><br> == トラブルシューティング == ==== エラー : state not found ==== * 原因 *: 状態が <code>manage()</code> で登録されていない。 * 解決方法 *: <syntaxhighlight lang="rust"> // 状態を登録する Builder::default() .manage(Mutex::new(AppState::default())) .invoke_handler(tauri::generate_handler![your_command]) </syntaxhighlight> <br> ==== エラー : PoisonError ==== * 原因 *: 別のスレッドがロック保持中にパニックを起こした。 * 解決方法 *: <syntaxhighlight lang="rust"> // ロック回復を試みる let state = state.lock().unwrap_or_else(|e| { eprintln!("Lock poisoned: {}", e); e.into_inner() // 毒状態のデータを回復 }); </syntaxhighlight> <br> ===== 非同期コマンドでのデッドロック ===== * 原因 *: <code>std::sync::Mutex</code> をawaitポイントを跨いで保持している。 * 解決方法 *: <syntaxhighlight lang="rust"> // 非同期コマンドでは tokio::sync::Mutex を使用 use tokio::sync::Mutex; #[tauri::command] async fn async_operation(state: State<'_, Mutex<AppState>>) -> Result<(), String> { let mut state = state.lock().await; // awaitポイントを跨いでも安全 let data = fetch_data().await?; state.data = data; Ok(()) } </syntaxhighlight> <br><br> == 関連情報 == * [https://v2.tauri.app/develop/state-management/ Tauri公式ドキュメント - State Management] * [https://docs.rs/tauri/latest/tauri/struct.State.html tauri::State API リファレンス] * [https://doc.rust-lang.org/std/sync/struct.Mutex.html std::sync::Mutex ドキュメント] * [[Tauriの基礎 - ウインドウ管理]] * [[Tauriの基礎 - メニューとシステムトレイ]] * [[Tauriの基礎 - セキュリティモデル]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板,Tauri,状態管理,State Management,Rust,React,TypeScript,デスクトップアプリ |description={{PAGENAME}} - Tauriアプリケーションにおける状態管理の基本概念から実践的な実装パターンまで詳しく解説 | This page is {{PAGENAME}} about Tauri state management |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]]
Tauriの基礎 - 状態管理
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse