mcpField
L
leptos-guide

leptos-guide

github.com/daiki48開発0110 回インストール2 日前に更新
リポジトリ

leptos-guide について

Leptos v0.8.x でリアクティブなアプリケーションを開発するためのガイドです。シグナル、非同期データ、コンポーネント、所有権の扱い方を解説します。

主な機能

  1. 01シグナルとリアクティブプリミティブのパターン
  2. 02コンポーネント、Effect、Memo
  3. 03非同期リソースとフォームアクション
  4. 04コンテキストとリスト描画
  5. 05所有権とクロージャーの指針

活用シーン

  1. 01Leptosでリアクティブな状態を実装
  2. 02ビューで非同期データを読み込み
  3. 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()

コメント