liveview-code-review
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLiveView Code Review
LiveView代码审查
Quick Reference
快速参考
| Issue Type | Reference |
|---|---|
| mount, handle_params, handle_event, handle_async | references/lifecycle.md |
| When to use assigns vs streams, AsyncResult | references/assigns-streams.md |
| Function vs LiveComponent, slots, attrs | references/components.md |
| Authorization per event, phx-value trust | references/security.md |
| 问题类型 | 参考文档 |
|---|---|
| mount、handle_params、handle_event、handle_async | references/lifecycle.md |
| assigns与streams的使用场景、AsyncResult | references/assigns-streams.md |
| 函数组件与LiveComponent对比、插槽、属性 | references/components.md |
| 事件权限校验、phx-value信任机制 | references/security.md |
Review Checklist
审查检查清单
Critical Issues
关键问题
- No socket copying into async functions (extract values first)
- Every handle_event validates authorization
- No sensitive data in assigns (visible in DOM)
- phx-value data is validated (user-modifiable)
- 未将socket复制到异步函数中(需先提取值)
- 每个handle_event都验证了权限
- assigns中无敏感数据(避免在DOM中暴露)
- phx-value数据经过验证(用户可修改该数据)
Lifecycle
生命周期
- Subscriptions wrapped in
connected?(socket) - handle_params used for URL-based state
- handle_async handles :loading and :error states
- 订阅操作包裹在中
connected?(socket) - 使用handle_params处理基于URL的状态
- handle_async处理:loading和:error状态
Data Management
数据管理
- Streams used for large collections (100+ items)
- temporary_assigns for data not needed after render
- AsyncResult patterns for loading states
- 大型集合(100+条数据)使用streams
- 渲染后无需的数据使用temporary_assigns
- 加载状态使用AsyncResult模式
Components
组件
- Function components preferred over LiveComponents
- LiveComponents preserve :inner_block in update/2
- Slots use proper attr declarations
- phx-debounce on text inputs
- 优先使用函数组件而非LiveComponents
- LiveComponents在update/2中保留:inner_block
- 插槽使用正确的属性声明
- 文本输入框添加phx-debounce
Valid Patterns (Do NOT Flag)
有效模式(无需标记)
- Empty mount returning {:ok, socket} - Valid for simple LiveViews
- Using assigns for small lists - Streams only needed for 100+ items
- LiveComponent without update/2 - Default update/2 assigns all
- phx-click without phx-value - Event may not need data
- Inline function in heex - Valid for simple transforms
- 空mount返回{:ok, socket} - 适用于简单LiveViews
- 小型列表使用assigns - 仅100+条数据时才需要streams
- LiveComponent无update/2 - 默认update/2会分配所有属性
- phx-click无phx-value - 事件可能不需要数据
- heex中使用内联函数 - 适用于简单转换场景
Context-Sensitive Rules
上下文敏感规则
| Issue | Flag ONLY IF |
|---|---|
| Missing debounce | Input is text/textarea AND triggers server event |
| Use streams | Collection has 100+ items OR is paginated |
| Missing auth check | Event modifies data AND no auth in mount |
| 问题 | 仅在以下情况标记 |
|---|---|
| 缺少防抖 | 输入框为text/textarea且触发服务器事件 |
| 使用streams | 集合包含100+条数据或支持分页 |
| 缺少权限校验 | 事件修改数据且mount中未做权限验证 |
Critical Anti-Patterns
关键反模式
Socket Copying (MOST IMPORTANT)
Socket复制(最重要)
elixir
undefinedelixir
undefinedBAD - socket copied into async function
错误示例 - 将socket复制到异步函数中
def handle_event("load", _, socket) do
Task.async(fn ->
user = socket.assigns.user # Socket copied!
fetch_data(user.id)
end)
{:noreply, socket}
end
def handle_event("load", _, socket) do
Task.async(fn ->
user = socket.assigns.user # Socket被复制!
fetch_data(user.id)
end)
{:noreply, socket}
end
GOOD - extract values first
正确示例 - 先提取值
def handle_event("load", _, socket) do
user_id = socket.assigns.user.id
Task.async(fn ->
fetch_data(user_id) # Only primitive copied
end)
{:noreply, socket}
end
undefineddef handle_event("load", _, socket) do
user_id = socket.assigns.user.id
Task.async(fn ->
fetch_data(user_id) # 仅复制原始类型数据
end)
{:noreply, socket}
end
undefinedMissing Authorization
缺少权限验证
elixir
undefinedelixir
undefinedBAD - trusts phx-value without auth
错误示例 - 未验证权限即信任phx-value
def handle_event("delete", %{"id" => id}, socket) do
Posts.delete_post!(id) # Anyone can delete any post!
{:noreply, socket}
end
def handle_event("delete", %{"id" => id}, socket) do
Posts.delete_post!(id) # 任何人都可以删除任意帖子!
{:noreply, socket}
end
GOOD - verify authorization
正确示例 - 验证权限
def handle_event("delete", %{"id" => id}, socket) do
post = Posts.get_post!(id)
if post.user_id == socket.assigns.current_user.id do
Posts.delete_post!(post)
{:noreply, stream_delete(socket, :posts, post)}
else
{:noreply, put_flash(socket, :error, "Unauthorized")}
end
end
undefineddef handle_event("delete", %{"id" => id}, socket) do
post = Posts.get_post!(id)
if post.user_id == socket.assigns.current_user.id do
Posts.delete_post!(post)
{:noreply, stream_delete(socket, :posts, post)}
else
{:noreply, put_flash(socket, :error, "无权限操作")}
end
end
undefinedBefore Submitting Findings
提交审查结果前
Use the issue format: for each finding.
[FILE:LINE] ISSUE_TITLELoad and follow review-verification-protocol before reporting any issue.
每个审查结果请使用以下格式:
[文件:行号] 问题标题在报告任何问题前,请加载并遵循审查验证协议