はじめに
今回はTauriを使ってレシピ取得アプリを作成しました。
著者はRustもReact(TypeScript)も初学者のため学習のために作成しました。
完成形のGitHubリポジトリ
https://github.com/taiyou1116/tauri-recipe-search
作るモチベ
RustとTypeScriptを一度に学べる!!
Rustの非同期処理について学べる!!
使う技術
- Tauri
- Rust
- React (TypeScript)
対象者
この記事は下記のような人を対象にしています。
- Tauriでデスクトップアプリを作成してみたい方
- Rust, TypeScript初学者
- 楽天APIを使ってみたい方
完成形
追記(2023/10/11)
下記コードでevalメソッドを使用していますが、直接jsコードを実行してしまうので、代わりにemitメソッドを使用しましょう。emitは引数の関数をイベントに登録できます。
以前のコード
// メイン関数で受信と処理
while let Some(recipes) = rx.recv().await {
// 重複を見てあげる
let mut new_recipes = Vec::new();
for recipe in recipes {
if seen.insert(recipe.recipe_id.clone()) {
new_recipes.push(recipe);
}
}
// ここで受信したレシピを処理
let js_command = format!(
"window.receiveRecipes({})",
serde_json::to_string(&new_recipes).unwrap()
);
window.eval(&js_command).unwrap();
}
次のように変更
// メイン関数で受信と処理
while let Some(recipes) = rx.recv().await {
// 重複を見てあげる
let mut new_recipes = Vec::new();
for recipe in recipes {
if seen.insert(recipe.recipe_id.clone()) {
new_recipes.push(recipe);
}
}
window.emit("receive_recipes", &new_recipes).unwrap();
}
その他詳細は私のGitHubや、下のコメントしてくれた方を参考にしてください。↓
コード解説
問題点
get_category_dataでレシピデータを取得しますが、楽天APIは人気のレシピデータを4つずつしか取得できません。また、APIへのリクエストは1秒ほど間隔を空けないとエラーが発生してしまいます。(負荷を抑えるため)
tokio::spawnで解決
tokio::spawnを使用して新しくタスクを作成します。その中でAPIへのリクエストをしレシピを取得します。
取得するたびにwhile let Some(recipes) = rx.recv().awaitの部分で受け取ります。
そして、window.eval(&js_command).unwrap();でフロントのwindow.receiveRecipesを実行します。
#[tauri::command(async)]
pub async fn get_category_data(category_name: String, window: Window) -> Result<(), String> {
let (tx, mut rx) = mpsc::channel(32); // 32はバッファサイズ
tokio::spawn(async move {
let config = config::Config::new();
let response: ApiResponseOfCategory = reqwest::get(&config.category_url)
.await
.unwrap()
.json()
.await
.unwrap();
let categories = extract_categories_from_response(&response, &category_name);
for category in categories {
let recipe_url = format!("{}{}", config.recipe_url, category.category_id);
let response: ApiResponseOfRecipe = reqwest::get(&recipe_url)
.await
.unwrap()
.json()
.await
.unwrap();
// チャネルを通じてレシピを送信
if let Err(_) = tx.send(response.result).await {
return;
}
// 1秒待機
sleep(Duration::from_secs(1)).await;
}
});
let mut seen = HashSet::new();
// メイン関数で受信と処理
while let Some(recipes) = rx.recv().await {
// 重複を見てあげる
let mut new_recipes = Vec::new();
for recipe in recipes {
if seen.insert(recipe.recipe_id.clone()) {
new_recipes.push(recipe);
}
}
// ここで受信したレシピを処理
let js_command = format!(
"window.receiveRecipes({})",
serde_json::to_string(&new_recipes).unwrap()
);
window.eval(&js_command).unwrap();
}
Ok(())
}
window.receiveRecipes
ここではrecipeListにRust側で取得したレシピを格納しています。
useEffect(() => {
window.receiveRecipes = function(recipes) {
useStore.setState((state) => ({
recipeList: [...state.recipeList, ...recipes],
}));
onGetDataMatchingMaterial(materialName);
};
// クリーンアップ関数:コンポーネントのアンマウント時に実行
return () => {
window.receiveRecipes = null;
};
}, [materialName]);
window.recipeRecipes
window.recipeRecipesを使うためには、WindowというグローバルインターフェースにreceiveRecipesを追加する必要があります。
import { Recipe } from "./Recipe";
declare global {
interface Window {
receiveRecipes: ((recipes: Recipe[]) => void) | null;
}
}
おわりに
Tauriで楽天レシピを取得するアプリについて、レシピ取得部分のみ解説してみました。
コード全体についてはGitHubリポジトリをご覧ください。