liveview-code-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

LiveView Code Review

LiveView代码审查

Quick Reference

快速参考

Issue TypeReference
mount, handle_params, handle_event, handle_asyncreferences/lifecycle.md
When to use assigns vs streams, AsyncResultreferences/assigns-streams.md
Function vs LiveComponent, slots, attrsreferences/components.md
Authorization per event, phx-value trustreferences/security.md
问题类型参考文档
mount、handle_params、handle_event、handle_asyncreferences/lifecycle.md
assigns与streams的使用场景、AsyncResultreferences/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

上下文敏感规则

IssueFlag ONLY IF
Missing debounceInput is text/textarea AND triggers server event
Use streamsCollection has 100+ items OR is paginated
Missing auth checkEvent modifies data AND no auth in mount
问题仅在以下情况标记
缺少防抖输入框为text/textarea且触发服务器事件
使用streams集合包含100+条数据或支持分页
缺少权限校验事件修改数据且mount中未做权限验证

Critical Anti-Patterns

关键反模式

Socket Copying (MOST IMPORTANT)

Socket复制(最重要)

elixir
undefined
elixir
undefined

BAD - 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
undefined
def handle_event("load", _, socket) do user_id = socket.assigns.user.id Task.async(fn -> fetch_data(user_id) # 仅复制原始类型数据 end) {:noreply, socket} end
undefined

Missing Authorization

缺少权限验证

elixir
undefined
elixir
undefined

BAD - 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
undefined
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, "无权限操作")} end end
undefined

Before Submitting Findings

提交审查结果前

Use the issue format:
[FILE:LINE] ISSUE_TITLE
for each finding.
Load and follow review-verification-protocol before reporting any issue.
每个审查结果请使用以下格式:
[文件:行号] 问题标题
在报告任何问题前,请加载并遵循审查验证协议