MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Tauriの基礎 - Commandsのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Tauriの基礎 - Commands
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == Tauriの <u>Commands</u> は、フロントエンド (JavaScript / TypeScript) からバックエンド (Rust) の関数を呼び出すための通信メカニズムである。<br> <br> この機能により、ReactやVue等のフロントエンドフレームワークから、Rustで実装された高速で安全な処理を直接実行できる。<br> <br> TauriのCommandsはIPC (Inter-Process Communication) を基盤としており、WebViewプロセスとRustプロセス間でのデータの送受信を行う。<br> <br> 通信はJSONベースのシリアライズを通じて行われ、型安全性を確保しながらデータのやり取りが可能である。<br> <br> Commandsの主な特徴は以下の通りである。<br> * 型安全な通信 *: Rustの型システムとTypeScriptの型定義により、コンパイル時に型チェックが行われる。 * JSONシリアライズ *: Serdeクレートを使用して、自動的にJSONへの変換が行われる。 * 非同期サポート *: 非同期関数を定義することで、ブロッキングを回避できる。 * エラーハンドリング *: Result型を使用して、構造化されたエラー処理が可能である。 <br><br> == 前提条件 == Commandsを使用するには、以下に示す前提条件を満たしている必要がある。<br> <br> ==== 必要な依存関係 ==== Cargo.tomlファイルに以下に示す依存関係を追加する。<br> <br> <syntaxhighlight lang="toml"> [dependencies] tauri = { version = "2", features = ["unstable"] } serde = { version = "1", features = ["derive"] } serde_json = "1" </syntaxhighlight> <br> ==== プロジェクト構成 ==== Tauriプロジェクトの基本的な構成を以下に示す。<br> <br> my-tauri-app/ ├── src/ # フロントエンド (React / TypeScript) │ ├── App.tsx │ └── main.tsx ├── src-tauri/ # バックエンド (Rust) │ ├── src/ │ │ ├── main.rs # エントリーポイント │ │ └── lib.rs # Commands定義 │ └── Cargo.toml └── package.json <br> ==== TypeScriptの型定義 ==== フロントエンドで型安全性を確保するために、以下に示すパッケージをインストールする。<br> <br> npm install @tauri-apps/api <br><br> == #[tauri::command]マクロ == <code>#[tauri::command]</code> マクロは、Rust関数をフロントエンドから呼び出し可能にするための属性マクロである。<br> <br> ==== 基本的な使い方 ==== マクロを関数に付与することにより、その関数がCommandとして登録される。<br> <br> <syntaxhighlight lang="rust"> use tauri::command; #[command] fn greet(name: &str) -> String { format!("Hello, {}!", name) } </syntaxhighlight> <br> ==== 関数シグネチャの要件 ==== Commandとして定義する関数は、以下に示す要件を満たす必要がある。<br> <br> * 引数の型 *: 引数は、<code>serde::Deserialize</code> を実装している必要がある。 *: 基本的な型 (String、i32、bool等) や カスタム構造体を使用できる。 * 戻り値の型 *: 戻り値は、<code>serde::Serialize</code> を実装している必要がある。 *: <code>Result<T, E></code> 型も使用可能である。(Eは、Serializeを実装している必要がある) * ライフタイム *: 引数に参照を使用する場合は、ライフタイムの考慮が必要である。 <br> ==== 複数の引数を受け取る関数 ==== 複数の引数を受け取る関数の例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> #[command] fn calculate(a: i32, b: i32, operation: String) -> Result<i32, String> { match operation.as_str() { "add" => Ok(a + b), "subtract" => Ok(a - b), "multiply" => Ok(a * b), "divide" => { if b == 0 { Err("Division by zero".to_string()) } else { Ok(a / b) } } _ => Err(format!("Unknown operation: {}", operation)), } } </syntaxhighlight> <br><br> == invoke関数による呼び出し == フロントエンドからCommandを呼び出すには、<code>invoke</code>関数を使用する。<br> <br> ==== JavaScript / TypeScript側の実装 ==== <u>@tauri-apps/api</u> パッケージから <code>invoke</code> 関数をインポートして使用する。<br> <br> <syntaxhighlight lang="typescript"> import { invoke } from '@tauri-apps/api/core' // 基本的な呼び出し const result = await invoke<string>('greet', { name: 'Tauri' }) console.log(result) // "Hello, Tauri!" // エラーハンドリング付き try { const result = await invoke<number>('calculate', { a: 10, b: 5, operation: 'add' }) console.log(result) // 15 } catch (error) { console.error('Error:', error) } </syntaxhighlight> <br> ==== Reactでの使用パターン ==== ReactコンポーネントでCommandを呼び出す例を以下に示す。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react' import { invoke } from '@tauri-apps/api/core' interface GreetingProps { initialName?: string } function Greeting({ initialName = '' }: GreetingProps) { const [name, setName] = useState(initialName) const [greeting, setGreeting] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState<string | null>(null) const handleGreet = async () => { setLoading(true) setError(null) try { const result = await invoke<string>('greet', { name }) setGreeting(result) } catch (err) { setError(err as string) } finally { setLoading(false) } } return ( <div> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="名前を入力" /> <button onClick={handleGreet} disabled={loading}> {loading ? '処理中...' : '挨拶'} </button> {greeting && <p>{greeting}</p>} {error && <p style={{ color: 'red' }}>{error}</p>} </div> ) } export default Greeting </syntaxhighlight> <br> ==== 型定義の自動生成 ==== Tauri v2では、<code>tauri-codegen</code> クレートを使用して、TypeScriptの型定義を自動生成できる。<br> <br> Cargo.tomlファイルに以下に示す設定を追加する。<br> <br> <syntaxhighlight lang="toml"> [build-dependencies] tauri-build = { version = "2", features = ["codegen"] } </syntaxhighlight> <br> build.rsファイルを作成する。<br> <br> <syntaxhighlight lang="rust"> fn main() { tauri_build::build() } </syntaxhighlight> <br> これにより、ビルド時にTypeScriptの型定義ファイルが生成される。<br> <br><br> == 引数の受け渡し (JSONシリアライズ) == Commandへの引数は、JSONシリアライズを通じて渡される。<br> <br> ==== 基本的な引数 ==== 基本的な型は自動的にシリアライズされる。<br> <br> <center> {| class="wikitable" |+ サポートされる基本的な型 |- ! Rust型 !! TypeScript型 !! 説明 |- | String || string || 文字列 |- | i32, i64 || number || 整数 |- | f64 || number || 浮動小数点数 |- | bool || boolean || 真偽値 |- | Vec<T> || T[] || 配列 |- | Option<T> || T \| null || NULL許容 |- | HashMap<K, V> || Record<K, V> || マップ |} </center> <br> ==== 複雑なオブジェクトの受け渡し ==== カスタム構造体を使用して、複雑なオブジェクトを渡すことができる。<br> <br> * Rust側の定義 *: <syntaxhighlight lang="rust"> use serde::Deserialize; #[derive(Deserialize)] struct User { id: u32, name: String, email: String, active: bool, } #[command] fn create_user(user: User) -> Result<String, String> { if user.name.is_empty() { return Err("Name is required".to_string()) } Ok(format!("Created user: {} (ID: {})", user.name, user.id)) } </syntaxhighlight> *: <br> * TypeScript側の呼び出し *: <syntaxhighlight lang="typescript"> interface User { id: number name: string email: string active: boolean } const createUser = async (user: User) => { try { const result = await invoke<string>('create_user', { user }) console.log(result) } catch (error) { console.error('Failed to create user:', error) } } // 使用例 createUser({ id: 1, name: 'John Doe', email: 'john@example.com', active: true }) </syntaxhighlight> <br> ==== Serdeによるシリアライズ ==== Serdeクレートを使用して、シリアライズの動作をカスタマイズできる。<br> <br> <syntaxhighlight lang="rust"> use serde::{Deserialize, Serialize}; // フィールド名のリネーム #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct UserProfile { user_id: u32, first_name: String, last_name: String, created_at: String, } // オプショナルフィールド #[derive(Deserialize)] struct UpdateRequest { #[serde(default)] name: Option<String>, #[serde(default)] email: Option<String>, } // カスタムデシリアライザ #[derive(Deserialize)] struct Config { #[serde(deserialize_with = "parse_version")] version: u32, } </syntaxhighlight> <br><br> == 戻り値の受け取り == Commandからの戻り値は、JSONシリアライズを通じてフロントエンドに返される。<br> <br> ==== 基本的な戻り値 ==== 基本的な型を返す例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> #[command] fn get_timestamp() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() } #[command] fn get_app_info() -> (String, String) { ("MyApp".to_string(), "1.0.0".to_string()) } </syntaxhighlight> <br> ==== 複雑なオブジェクトの返却 ==== 構造体を返すことにより、複雑なデータを渡すことができる。<br> <br> <syntaxhighlight lang="rust"> use serde::Serialize; #[derive(Serialize)] struct FileInfo { name: String, size: u64, is_directory: bool, modified: String, } #[command] fn get_file_info(path: String) -> Result<FileInfo, String> { let metadata = std::fs::metadata(&path) .map_err(|e| format!("Failed to read file: {}", e))?; Ok(FileInfo { name: path.split('/').last().unwrap_or(&path).to_string(), size: metadata.len(), is_directory: metadata.is_dir(), modified: metadata.modified() .map(|t| format!("{:?}", t)) .unwrap_or_else(|_| "Unknown".to_string()), }) } </syntaxhighlight> <br> <syntaxhighlight lang="typescript"> // TypeScript側での受信 interface FileInfo { name: string size: number is_directory: boolean modified: string } const getFileInfo = async (path: string): Promise<FileInfo | null> => { try { const info = await invoke<FileInfo>('get_file_info', { path }) return info } catch (error) { console.error('Failed to get file info:', error) return null } } </syntaxhighlight> <br> ==== 型安全な受信 ==== TypeScriptのジェネリクスを使用して、型安全に受信する。<br> <br> <syntaxhighlight lang="typescript"> import { invoke } from '@tauri-apps/api/core' // 型定義 interface ApiResponse<T> { success: boolean data: T | null error: string | null } interface UserData { id: number name: string email: string } // 型安全なinvokeラッパー async function typedInvoke<T>( command: string, args?: Record<string, unknown> ): Promise<T> { return invoke<T>(command, args) } // 使用例 const fetchUser = async (userId: number) => { try { const response = await typedInvoke<ApiResponse<UserData>>( 'fetch_user', { userId } ) if (response.success && response.data) { console.log('User:', response.data.name) } else { console.error('Error:', response.error) } } catch (error) { console.error('Command failed:', error) } } </syntaxhighlight> <br><br> == generate_handler!マクロによるコマンド登録 == 定義したCommandを使用するには、<code>generate_handler!</code> マクロで登録する必要がある。<br> <br> ==== 単一コマンドの登録 ==== 基本的な登録方法を以下に示す。<br> <br> <syntaxhighlight lang="rust"> // src-tauri/src/lib.rs use tauri::command; #[command] fn greet(name: &str) -> String { format!("Hello, {}!", name) } // エクスポート #[tauri::command] fn get_version() -> String { env!("CARGO_PKG_VERSION").to_string() } </syntaxhighlight> <br> <syntaxhighlight lang="rust"> // main.rsでの登録 // src-tauri/src/main.rs #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet, get_version]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== 複数コマンドの登録 ==== 複数のCommandを一括で登録する。<br> <br> <syntaxhighlight lang="rust"> // src-tauri/src/lib.rs mod commands; pub use commands::*; // src-tauri/src/commands/mod.rs pub mod user; pub mod file; pub mod system; pub use user::*; pub use file::*; pub use system::*; </syntaxhighlight> <br> <syntaxhighlight lang="rust"> // src-tauri/src/main.rs use my_app::*; fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ // User commands user::create_user, user::get_user, user::update_user, user::delete_user, // File commands file::read_file, file::write_file, file::delete_file, // System commands system::get_system_info, system::get_env_var, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== main.rsでの設定 ==== Tauri Builderの設定例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> use tauri::Manager; fn main() { tauri::Builder::default() // Commandsの登録 .invoke_handler(tauri::generate_handler![ greet, get_version, calculate, create_user, get_file_info, ]) // ウィンドウ設定 .setup(|app| { #[cfg(debug_assertions)] { let window = app.get_webview_window("main").unwrap(); window.open_devtools(); } Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br><br> == サンプルコード == ==== Rust側の実装 ==== <syntaxhighlight lang="rust"> // src-tauri/src/lib.rs use serde::{Deserialize, Serialize}; use tauri::command; // データ構造 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Todo { id: u32, title: String, completed: bool, } // グローバル状態 (実際のアプリではデータベースを使用) static mut TODOS: Vec<Todo> = Vec::new(); // Todo一覧取得 #[command] fn get_todos() -> Vec<Todo> { unsafe { TODOS.clone() } } // Todo追加 #[command] fn add_todo(title: String) -> Result<Todo, String> { if title.trim().is_empty() { return Err("Title cannot be empty".to_string()) } unsafe { let id = TODOS.len() as u32 + 1; let todo = Todo { id, title, completed: false, }; TODOS.push(todo.clone()); Ok(todo) } } // Todo更新 #[command] fn update_todo(id: u32, completed: bool) -> Result<Todo, String> { unsafe { if let Some(todo) = TODOS.iter_mut().find(|t| t.id == id) { todo.completed = completed; Ok(todo.clone()) } else { Err(format!("Todo with id {} not found", id)) } } } // Todo削除 #[command] fn delete_todo(id: u32) -> Result<(), String> { unsafe { let initial_len = TODOS.len(); TODOS.retain(|t| t.id != id); if TODOS.len() == initial_len { Err(format!("Todo with id {} not found", id)) } else { Ok(()) } } } </syntaxhighlight> <br> ==== TypeScript / React側の実装 ==== <syntaxhighlight lang="typescript"> // src/App.tsx import { useState, useEffect } from 'react' import { invoke } from '@tauri-apps/api/core' interface Todo { id: number title: string completed: boolean } function App() { const [todos, setTodos] = useState<Todo[]>([]) const [newTodo, setNewTodo] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState<string | null>(null) // Todo一覧取得 const fetchTodos = async () => { try { const result = await invoke<Todo[]>('get_todos') setTodos(result) } catch (err) { setError(err as string) } } useEffect(() => { fetchTodos() }, []) // Todo追加 const handleAddTodo = async () => { if (!newTodo.trim()) return setLoading(true) setError(null) try { await invoke<Todo>('add_todo', { title: newTodo }) setNewTodo('') await fetchTodos() } catch (err) { setError(err as string) } finally { setLoading(false) } } // Todo更新 const handleToggleTodo = async (id: number, completed: boolean) => { try { await invoke<Todo>('update_todo', { id, completed }) await fetchTodos() } catch (err) { setError(err as string) } } // Todo削除 const handleDeleteTodo = async (id: number) => { try { await invoke('delete_todo', { id }) await fetchTodos() } catch (err) { setError(err as string) } } return ( <div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}> <h1>Todo App</h1> {/* エラー表示 */} {error && ( <div style={{ color: 'red', marginBottom: '10px' }}> {error} <button onClick={() => setError(null)}>×</button> </div> )} {/* Todo追加フォーム */} <div style={{ marginBottom: '20px' }}> <input type="text" value={newTodo} onChange={(e) => setNewTodo(e.target.value)} placeholder="新しいTodoを入力" style={{ padding: '8px', marginRight: '10px', width: '300px' }} onKeyDown={(e) => e.key === 'Enter' && handleAddTodo()} /> <button onClick={handleAddTodo} disabled={loading} style={{ padding: '8px 16px' }} > {loading ? '追加中...' : '追加'} </button> </div> {/* Todo一覧 */} <ul style={{ listStyle: 'none', padding: 0 }}> {todos.map((todo) => ( <li key={todo.id} style={{ display: 'flex', alignItems: 'center', padding: '10px', borderBottom: '1px solid #ccc' }} > <input type="checkbox" checked={todo.completed} onChange={(e) => handleToggleTodo(todo.id, e.target.checked)} style={{ marginRight: '10px' }} /> <span style={{ flex: 1, textDecoration: todo.completed ? 'line-through' : 'none' }} > {todo.title} </span> <button onClick={() => handleDeleteTodo(todo.id)} style={{ color: 'red' }} > 削除 </button> </li> ))} </ul> {todos.length === 0 && ( <p style={{ textAlign: 'center', color: '#666' }}> Todoがありません </p> )} </div> ) } export default App </syntaxhighlight> <br><br> == 推奨される事柄 == ==== 引数の命名規則 ==== Rust側ではスネークケース (snake_case) を使用して、TypeScript側ではキャメルケース (camelCase) を使用する。<br> <br> <syntaxhighlight lang="rust"> // Rust (snake_case) #[command] fn get_user_by_id(user_id: u32) -> Result<User, String> { // ... } </syntaxhighlight> <br> <syntaxhighlight lang="typescript"> // TypeScript (camelCase) await invoke<User>('get_user_by_id', { userId: 1 }) </syntaxhighlight> <br> ==== 大きなデータの取り扱い ==== 大きなデータを渡す場合は、パフォーマンスに注意する。<br> <br> * チャンク化 *: 大きなデータは小さなチャンクに分割して処理する。 * ストリーミング *: Channel APIを使用して、ストリーミング処理を行う。 * 圧縮 *: 必要に応じてデータを圧縮する。<br> <br> ==== セキュリティ ==== CommandsはIPCを通じて通信するため、セキュリティに注意する。<br> <br> * 入力検証 *: 全ての入力を検証し、無効なデータを拒否する。 * 権限管理 *: ファイルシステムアクセス等の危険な操作は、必要最小限の権限で実行する。 * エラーメッセージ *: 内部実装の詳細を漏らさないエラーメッセージを返す。 <br> ==== デバッグのヒント ==== 開発時のデバッグに役立つテクニックを以下に示す。<br> <br> <syntaxhighlight lang="rust"> #[command] fn debug_command(input: String) -> String { #[cfg(debug_assertions)] println!("Debug: input = {}", input); format!("Processed: {}", input) } </syntaxhighlight> <br><br> == 関連情報 == * [[Tauriの基礎 - 非同期Commands]] *: 非同期処理、Channel API、プログレス表示について * [[Tauriの基礎 - Commandsのエラーハンドリング]] *: エラー処理、thiserror、anyhowについて * [https://tauri.app/v2/guide/features/command/ Tauri公式ドキュメント - Commands] *: 公式のCommandsリファレンス * [https://serde.rs/ Serde公式ドキュメント] *: シリアライズ / デシリアライズの詳細 <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Tauri,Rust,React,TypeScript,Commands,IPC,Desktop App,Serde,invoke,generate_handler |description={{PAGENAME}} - Tauri Commandsの基本機能に関するガイド | This page is {{PAGENAME}} in our wiki about Tauri Commands |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]]
Tauriの基礎 - Commands
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse