liveview-patterns について
Phoenix LiveView 1.0 と 1.1 を使った開発向けのリファレンススキルです。非同期データ取得、ストリーム、フォーム、ナビゲーション、コンポーネントのパターンを示し、ライフサイクルやメモリに関する問題も扱います。
主な機能
- 01非同期アサインとデータ取得のパターン
- 02ストリームによるリスト処理
- 03ライフサイクルに配慮した PubSub 購読
- 04フォーム検証とエラー処理
- 05ナビゲーションとコンポーネント選択の指針
活用シーン
- 01LiveView の mount で安全にデータを取得
- 02ストリームを使った大規模リストの描画
- 03PubSub の重複購読の防止
- 04ナビゲーションとコンポーネント方式の選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ oliver-kriska/ claude-elixir-phoenix --skill liveview-patternsインストール後、Claude Code で/liveview-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/async-streams.mdchannels-presence.mdcomponents.mdforms-uploads.mdjs-interop.mdpubsub-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 を使用した構築のためのリファレンスです。
絶対に守るべき原則
- MOUNT 内で無条件に DB クエリを実行しない — Mount は 2 回実行されます。既定では
assign_asyncを使用してください。SEO 対応ルートではconnected?ガードとキャッシュを利用した未接続時の分岐を使用します(クローラーはその HTML を読み取ります) - リストには必ずストリームを使用する — 通常の assign はユーザーごとに O(n) のメモリを使用します。ストリームは O(1) です
- サブスクリプションの前に必ず
connected?/1を確認する — 二重サブスクリプションを防ぎます assign_asyncのクロージャーの前に変数を取り出す — クロージャーは参照する変数全体をコピーします- 主要データは
mount/3で読み込み、ページネーションはhandle_params/3で行う —handle_paramsは URL が変更されるたびに実行されます - ビジネスロジックにソケットを渡さない — コンテキストを呼び出す前にデータを取り出してください
- UI のデバッグ前に changeset のエラーを確認する — フォームの保存が無言で失敗する場合は、ビューポートや JS ではなく、まず
{:error, changeset}を確認してください - 必須の埋め込みフィールドにはすべて hidden input を使用する — 埋め込みスキーマの必須フィールドを直接編集できない場合は、すべて
hidden_inputを用意する必要があります - ライフサイクル値に
assign_newを使用しない — キーが存在するとassign_newは関数を実行しません。ロケール、現在のユーザー、または mount ごとに更新する値にはassign/3を使用してください {: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、トークン認証