mcpField
L
liveview-patterns

liveview-patterns

github.com/oliver-kriska開発57159 回インストール1 週間前に更新
リポジトリ

liveview-patterns について

Phoenix LiveView 1.0 と 1.1 を使った開発向けのリファレンススキルです。非同期データ取得、ストリーム、フォーム、ナビゲーション、コンポーネントのパターンを示し、ライフサイクルやメモリに関する問題も扱います。

主な機能

  1. 01非同期アサインとデータ取得のパターン
  2. 02ストリームによるリスト処理
  3. 03ライフサイクルに配慮した PubSub 購読
  4. 04フォーム検証とエラー処理
  5. 05ナビゲーションとコンポーネント選択の指針

活用シーン

  1. 01LiveView の mount で安全にデータを取得
  2. 02ストリームを使った大規模リストの描画
  3. 03PubSub の重複購読の防止
  4. 04ナビゲーションとコンポーネント方式の選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/oliver-kriska/claude-elixir-phoenix --skill liveview-patterns

インストール後、Claude Code で/liveview-patternsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
async-streams.md
channels-presence.md
components.md
forms-uploads.md
js-interop.md
pubsub-navigation.md

詳細

LiveView パターンリファレンス

Ash プロジェクト:AshPhoenix.Form には ash-framework スキルを使用してください。ライフサイクル:phx-change で AshPhoenix.Form.validate/3、送信時に AshPhoenix.Form.submit/2、HEEx には to_form/1 を使用します。Ecto.Changeset.cast/3 は使用しないでください。

Phoenix LiveView 1.0/1.1 を使用した構築のためのリファレンスです。

絶対に守るべき原則

  1. MOUNT 内で無条件に DB クエリを実行しない — Mount は 2 回実行されます。既定では assign_async を使用してください。SEO 対応ルートでは connected? ガードとキャッシュを利用した未接続時の分岐を使用します(クローラーはその HTML を読み取ります)
  2. リストには必ずストリームを使用する — 通常の assign はユーザーごとに O(n) のメモリを使用します。ストリームは O(1) です
  3. サブスクリプションの前に必ず connected?/1 を確認する — 二重サブスクリプションを防ぎます
  4. assign_async のクロージャーの前に変数を取り出す — クロージャーは参照する変数全体をコピーします
  5. 主要データは mount/3 で読み込み、ページネーションは handle_params/3 で行う — handle_params は URL が変更されるたびに実行されます
  6. ビジネスロジックにソケットを渡さない — コンテキストを呼び出す前にデータを取り出してください
  7. UI のデバッグ前に changeset のエラーを確認する — フォームの保存が無言で失敗する場合は、ビューポートや JS ではなく、まず {:error, changeset} を確認してください
  8. 必須の埋め込みフィールドにはすべて hidden input を使用する — 埋め込みスキーマの必須フィールドを直接編集できない場合は、すべて hidden_input を用意する必要があります
  9. ライフサイクル値に assign_new を使用しない — キーが存在すると assign_new は関数を実行しません。ロケール、現在のユーザー、または mount ごとに更新する値には assign/3 を使用してください
  10. {:error, %Ecto.Changeset{}} を明示的に照合する — 単独の {:error, _} では changeset エラーと changeset 以外のエラーが混在し、フォームの再描画時に検証エラーが表示されません。その他のエラーは別途処理してください

メモリへの影響

パターン3K 項目10K ユーザー × 10K 項目
通常の assign~5.1 MB~10+ GB
ストリーム~1.1 MB最小(O(1))

判断基準:100 項目を超えるリストには assign ではなくストリームを使用してください

クイックパターン

Async Assign(重要)
def mount(%{"slug" => slug}, _session, socket) do
  # Extract needed values BEFORE the closure
  scope = socket.assigns.current_scope

  {:ok,
   socket
   |> assign_async(:org, fn -> {:ok, %{org: fetch_org(scope, slug)}} end)}
end
リストにストリームを使用する
def mount(_params, _session, socket) do
  {:ok, stream(socket, :items, Items.list_items())}
end

# Insert/update/delete
stream_insert(socket, :items, item, at: 0)
stream_delete(socket, :items, item)
SEO 向けデッドレンダー(キャッシュを利用した未接続時の分岐)

公開ルートや SEO 対応ルート(マーケティング、記事、商品一覧)では、 未接続時のレンダリングがクローラーに見える HTML になります。接続時は実データを取得し、 未接続時はキャッシュから取得します。

def mount(_params, _session, socket) do
  products =
    if connected?(socket),
      do: Catalog.list_products(),
      else: Cache.get_products() || []

  {:ok, assign(socket, products: products)}
end

リストが空の場合は、<noscript> に対応したスケルトンを使用してください。キャッシュには :persistent_term、ETS、 または Cachex を使用できます。これにより絶対に守るべき原則 #1 を満たし、Googlebot/GPTBot にも適切に対応できます。

connected? を使った PubSub の確認
def mount(_params, _session, socket) do
  if connected?(socket), do: Chat.subscribe(room_id)
  {:ok, socket}
end

ナビゲーションの判断ツリー

Same LiveView, different params? → patch / push_patch
Different LiveView, same live_session? → navigate / push_navigate
Different live_session or non-LiveView? → href / redirect

コンポーネントの判断ツリー

Does component need BOTH internal state AND event handling?
│
├── YES → Does it encapsulate APPLICATION logic (not just DOM)?
│   ├── YES → Use LiveComponent ✅
│   └── NO → Refactor to function component with parent handling
│
└── NO → Use Function Component ✅

公式ガイダンス:「ライブコンポーネントより関数コンポーネントを優先してください」

よくあるアンチパターン

誤り正しい方法
assign_async を使わない DB クエリmount でのクエリには assign_async を使用する(SEO 対応ルートでは connected? とキャッシュ済みのデッドレンダーを使用)
リストに assign(socket, items: list) を使うstream(socket, :items, list) を使用する
connected? を確認せずに PubSub を購読するif connected?(socket), do: subscribe() を使用する
コンテキスト関数にソケットを渡す先に socket.assigns から値を取り出す
handle_event にビジネスロジックを書くコンテキストに委譲する
フック内のロケールやユーザーに assign_new を使うassign/3 を使用する(mount ごとに実行する必要があります)

参考資料

詳しいパターンについては、以下を参照してください。

  • ${CLAUDE_SKILL_DIR}/references/async-streams.md - assign_async、stream_async、ストリーム
  • ${CLAUDE_SKILL_DIR}/references/forms-uploads.md - フォーム、検証、ファイルアップロード
  • ${CLAUDE_SKILL_DIR}/references/components.md - 関数コンポーネント、LiveComponents
  • ${CLAUDE_SKILL_DIR}/references/pubsub-navigation.md - PubSub、ナビゲーション、JS コマンド
  • ${CLAUDE_SKILL_DIR}/references/js-interop.md - サードパーティ製 JS ライブラリ、phx-update="ignore"、フック
  • ${CLAUDE_SKILL_DIR}/references/channels-presence.md - Phoenix Channels、Presence、トークン認証

コメント