leptos-guide について
Leptos v0.8.x でリアクティブなアプリケーションを開発するためのガイドです。シグナル、非同期データ、コンポーネント、所有権の扱い方を解説します。
主な機能
- 01シグナルとリアクティブプリミティブのパターン
- 02コンポーネント、Effect、Memo
- 03非同期リソースとフォームアクション
- 04コンテキストとリスト描画
- 05所有権とクロージャーの指針
活用シーン
- 01Leptosでリアクティブな状態を実装
- 02ビューで非同期データを読み込み
- 03フォーム送信やイベントを処理
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ daiki48/ dotfiles --skill leptos-guideインストール後、Claude Code で/leptos-guideを入力して読み込まれたか確認します。
詳細
Leptos v0.8.x 開発ガイド
シグナル(リアクティブプリミティブ)
let (count, set_count) = signal(0); // Tuple form
let count = RwSignal::new(0); // RwSignal form
// Read
count.get() // Clone value
count.read() // Get reference (&T)
// Write
set_count.set(1)
set_count.update(|n| *n += 1)
count.write() // Get &mut T
コンポーネント
#[component]
pub fn MyComponent(
title: String,
#[prop(optional)] class: Option<String>,
#[prop(default = 10)] limit: usize,
children: Children,
) -> impl IntoView {
view! {
<div class=class.unwrap_or_default()>
<h1>{title}</h1>
{children()}
</div>
}
}
エフェクトとメモ
// Side effects
Effect::new(move |_| { log::info!("count: {}", count.get()); });
// Derived signal (cached)
let doubled = Memo::new(move |_| count.get() * 2);
リソース(非同期データ)
let user = Resource::new(
move || user_id.get(),
|id| async move { fetch_user(id).await }
);
view! {
<Suspense fallback=|| view! { <p>"Loading..."</p> }>
{move || user.get().map(|u| view! { <p>{u.name}</p> })}
</Suspense>
}
アクション(フォーム送信)
let submit = Action::new(|data: &FormData| {
let data = data.clone();
async move { submit_form(data).await }
});
view! {
<form on:submit=move |ev| {
ev.prevent_default();
submit.dispatch(form_data);
}> /* ... */ </form>
}
パターン
条件付きレンダリング
<Show when=move || count.get() > 0 fallback=|| view! { <p>"Empty"</p> }>
<p>"Has items"</p>
</Show>
{move || match status.get() {
Status::Loading => view! { <p>"Loading"</p> }.into_any(),
Status::Ok(data) => view! { <Data data/> }.into_any(),
}}
リストのレンダリング
<For
each=move || items.get()
key=|item| item.id
children=|item| view! { <li>{item.name}</li> }
/>
コンテキスト
// Provider
provide_context(AppState::new());
// Consumer
let state = expect_context::<AppState>();
所有権パターン(重要)
なぜ必要か
Leptos のシグナルは Copy を実装していません。クロージャや非同期ブロックに移動すると、所有権が移転します。
StoredValue(非同期処理に推奨)
let api = StoredValue::new(client.clone());
Effect::new(move |_| {
let api = api.get_value(); // Get inside Effect
spawn_local(async move { api.fetch().await; });
});
Callback(イベントハンドラー)
let on_delete = Callback::new(move |id: i32| {
set_items.update(|items| items.retain(|i| i.id != id));
});
// Child: on_delete.run(id)
クローンパターン
let count = RwSignal::new(0);
let count_clone = count.clone();
let closure1 = move || count.get();
let closure2 = move || count_clone.get();
よくある間違い
// ❌ Wrong: get_value outside Effect
let api_value = api.get_value();
Effect::new(move |_| { spawn_local(async move { api_value.fetch().await; }); });
// ✅ Correct: get_value inside Effect
Effect::new(move |_| {
let api = api.get_value();
spawn_local(async move { api.fetch().await; });
});
// ❌ Wrong: Infinite loop
Effect::new(move |_| { signal.set(signal.get() + 1); });
// ✅ Correct: Use get_untracked()
Effect::new(move |_| {
let val = signal.get_untracked();
if should_update { signal.set(val + 1); }
});
クイックリファレンス
| ユースケース | パターン |
|---|---|
| Effect 内の API クライアント | StoredValue::new(client.clone()) |
| 複数のクロージャ内で使うシグナル | let sig_clone = sig.clone() |
| 子から親へのイベント | Callback<T> |
| 前の値を追跡する | StoredValue::new(initial) |
| 条件付き読み取り | signal.get_untracked() |
コメント
Markdown 対応