mcpField
L
liveview-patterns

liveview-patterns

github.com/oliver-kriska开发57159 次安装1 周前更新
代码仓库

关于 liveview-patterns

这是一项面向 Phoenix LiveView 1.0 和 1.1 开发的参考技能,涵盖异步数据加载、streams、表单、导航和组件选择,并说明如何避免常见的生命周期与内存问题。

主要功能

  1. 01异步赋值与数据加载模式
  2. 02使用 streams 处理列表
  3. 03遵循生命周期的 PubSub 订阅
  4. 04表单验证与错误处理
  5. 05导航和组件选择指南

适用场景

  1. 01在 LiveView mount 中安全加载数据
  2. 02使用 streams 渲染大型列表
  3. 03避免重复订阅 PubSub
  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 项目:使用 ash-framework skill 处理 AshPhoenix.Form。生命周期:在 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 中执行无条件数据库查询 — Mount 会运行两次。默认使用 assign_async。SEO 路由:使用 connected? 守卫,并在断开连接的分支中使用缓存(爬虫会读取该 HTML)
  2. 列表始终使用 Streams — 普通 assigns = 每位用户 O(n) 内存。Streams = O(1)
  3. 订阅前检查 connected?/1 — 防止重复订阅
  4. 在 assign_async 闭包外提取变量 — 闭包会复制所有被引用的变量
  5. 在 mount/3 中加载主要数据,在 handle_params/3 中处理分页 — handle_params 会在每次 URL 变更时运行
  6. 绝不将 socket 传给业务逻辑 — 调用 contexts 前先提取数据
  7. 调试 UI 前先检查 changeset 错误 — 表单静默保存失败时,先检查 {:error, changeset},而不是视口/JS
  8. 所有必填的嵌入字段都要使用隐藏输入框 — 如果无法直接编辑,嵌入式 schema 中的每个必填字段都必须有一个 hidden_input
  9. 生命周期值绝不使用 assign_new — 如果键已存在,assign_new 会跳过函数。对于 locale、当前用户或任何每次 mount 都要刷新的值,请使用 assign/3
  10. 显式匹配 {:error, %Ecto.Changeset{}} — 裸写 {:error, _} 会合并 changeset 错误和非 changeset 错误,导致表单静默地无法重新渲染验证错误。请单独处理其他错误

内存影响

模式3K 项10K 用户 × 10K 项
普通 assigns~5.1 MB~10+ GB
Streams~1.1 MB极少(O(1))

决策:超过 100 项的列表 → 使用 streams,而不是 assigns

快速模式

Async Assigns(关键)
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
列表使用 Streams
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 ✅

官方建议:“优先使用函数组件,而不是 LiveComponent”

常见反模式

错误做法正确做法
未使用 assign_async 执行数据库查询Mount 查询使用 assign_async(SEO 路由:使用 connected? + 缓存的死渲染)
对列表使用 assign(socket, items: list)使用 stream(socket, :items, list)
未检查 connected? 就订阅 PubSubif connected?(socket), do: subscribe()
将 socket 传给 context 函数先提取 socket.assigns
在 handle_event 中编写业务逻辑委托给 context
在 hooks 中使用 assign_new 设置 locale/user使用 assign/3(必须在每次 mount 时运行)

参考资料

详细模式请参见:

  • ${CLAUDE_SKILL_DIR}/references/async-streams.md - assign_async、stream_async、streams
  • ${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"、hooks
  • ${CLAUDE_SKILL_DIR}/references/channels-presence.md - Phoenix Channels、Presence、令牌身份验证

评论

支持 Markdown