phoenix-views-templates
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePhoenix Views and Templates
Phoenix 视图与模板
Phoenix uses HEEx (HTML+EEx) templates for rendering dynamic HTML content. HEEx provides compile-time validation, security through automatic escaping, and a component-based architecture. Views in Phoenix are modules that organize template rendering logic and house reusable function components.
Phoenix 使用 HEEx(HTML+EEx)模板来渲染动态 HTML 内容。HEEx 提供编译时验证、通过自动转义实现的安全性,以及基于组件的架构。Phoenix 中的视图是组织模板渲染逻辑并存放可复用函数组件的模块。
View Module Structure
视图模块结构
Phoenix view modules use the macro to load HEEx templates from a directory:
embed_templateselixir
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
endThis automatically creates functions for each file in the directory.
.html.heexhello_html/Phoenix 视图模块使用 宏从目录中加载 HEEx 模板:
embed_templateselixir
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
end这会自动为 目录中的每个 文件创建对应的函数。
hello_html/.html.heexHEEx Templates
HEEx 模板
Basic Template Structure
基础模板结构
HEEx templates combine HTML with embedded Elixir expressions:
heex
<section>
<h2>Hello World, from Phoenix!</h2>
</section>HEEx 模板将 HTML 与嵌入式 Elixir 表达式结合:
heex
<section>
<h2>Hello World, from Phoenix!</h2>
</section>Interpolating Dynamic Content
插入动态内容
Use to interpolate Elixir expressions into HTML:
<%= ... %>heex
<section>
<h2>Hello World, from <%= @messenger %>!</h2>
</section>The symbol accesses assigns passed from the controller.
@使用 将 Elixir 表达式插入到 HTML 中:
<%= ... %>heex
<section>
<h2>Hello World, from <%= @messenger %>!</h2>
</section>@Multi-line Expressions
多行表达式
For expressions without output, omit the :
=heex
<% # This is a comment %>
<% user_name = String.upcase(@user.name) %>
<p>Welcome, <%= user_name %>!</p>对于无输出的表达式,省略 :
=heex
<% # This is a comment %>
<% user_name = String.upcase(@user.name) %>
<p>Welcome, <%= user_name %>!</p>Working with Assigns
处理 Assigns
Assigns are key-value pairs passed from controllers to templates:
elixir
undefinedAssigns 是从控制器传递到模板的键值对:
elixir
undefinedController
Controller
def show(conn, %{"messenger" => messenger}) do
render(conn, :show, messenger: messenger, receiver: "Dweezil")
end
```heex
<!-- Template -->
<section>
<h2>Hello <%= @receiver %>, from <%= @messenger %>!</h2>
</section>All assigns are accessed with the prefix in templates.
@def show(conn, %{"messenger" => messenger}) do
render(conn, :show, messenger: messenger, receiver: "Dweezil")
end
```heex
<!-- Template -->
<section>
<h2>Hello <%= @receiver %>, from <%= @messenger %>!</h2>
</section>所有 Assigns 在模板中都通过 前缀访问。
@Conditional Rendering
条件渲染
Using if/else
使用 if/else
HEEx supports conditional rendering with blocks:
if/elseheex
<%= if some_condition? do %>
<p>Some condition is true for user: <%= @username %></p>
<% else %>
<p>Some condition is false for user: <%= @username %></p>
<% end %>HEEx 支持使用 块进行条件渲染:
if/elseheex
<%= if some_condition? do %>
<p>Some condition is true for user: <%= @username %></p>
<% else %>
<p>Some condition is false for user: <%= @username %></p>
<% end %>Using unless
使用 unless
For negative conditions:
heex
<%= unless @user.premium do %>
<div class="upgrade-banner">
Upgrade to premium for more features!
</div>
<% end %>针对否定条件:
heex
<%= unless @user.premium do %>
<div class="upgrade-banner">
Upgrade to premium for more features!
</div>
<% end %>Pattern Matching with case
使用 case 进行模式匹配
For multiple conditions:
heex
<%= case @status do %>
<% :pending -> %>
<span class="badge badge-warning">Pending</span>
<% :approved -> %>
<span class="badge badge-success">Approved</span>
<% :rejected -> %>
<span class="badge badge-danger">Rejected</span>
<% end %>针对多条件场景:
heex
<%= case @status do %>
<% :pending -> %>
<span class="badge badge-warning">Pending</span>
<% :approved -> %>
<span class="badge badge-success">Approved</span>
<% :rejected -> %>
<span class="badge badge-danger">Rejected</span>
<% end %>Looping and Iteration
循环与迭代
For Comprehensions
For 推导式
Generate dynamic lists using :
forheex
<table>
<tr>
<th>Number</th>
<th>Power</th>
</tr>
<%= for number <- 1..10 do %>
<tr>
<td><%= number %></td>
<td><%= number * number %></td>
</tr>
<% end %>
</table>使用 生成动态列表:
forheex
<table>
<tr>
<th>Number</th>
<th>Power</th>
</tr>
<%= for number <- 1..10 do %>
<tr>
<td><%= number %></td>
<td><%= number * number %></td>
</tr>
<% end %>
</table>Iterating Over Collections
遍历集合
Loop through lists or maps:
heex
<ul>
<%= for post <- @posts do %>
<li>
<h3><%= post.title %></h3>
<p><%= post.excerpt %></p>
</li>
<% end %>
</ul>循环遍历列表或映射:
heex
<ul>
<%= for post <- @posts do %>
<li>
<h3><%= post.title %></h3>
<p><%= post.excerpt %></p>
</li>
<% end %>
</ul>Shorthand :for Attribute
简写 :for 属性
HEEx provides cleaner syntax for simple iterations:
heex
<ul>
<li :for={item <- @items}><%= item.name %></li>
</ul>HEEx 为简单迭代提供了更简洁的语法:
heex
<ul>
<li :for={item <- @items}><%= item.name %></li>
</ul>Accessing Index
获取索引
Get the iteration index with :
Enum.with_index/2heex
<%= for {item, index} <- Enum.with_index(@items) do %>
<div class="item-<%= index %>">
<%= item.name %>
</div>
<% end %>使用 获取迭代索引:
Enum.with_index/2heex
<%= for {item, index} <- Enum.with_index(@items) do %>
<div class="item-<%= index %>">
<%= item.name %>
</div>
<% end %>Function Components
函数组件
Function components are reusable UI elements defined as Elixir functions that return HEEx templates.
函数组件是可复用的 UI 元素,定义为返回 HEEx 模板的 Elixir 函数。
Defining Function Components
定义函数组件
Use the macro to declare attributes and the sigil for the template:
attr~Helixir
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
attr :messenger, :string, required: true
def greet(assigns) do
~H"""
<h2>Hello World, from <%= @messenger %>!</h2>
"""
end
end使用 宏声明属性,并使用 符号定义模板:
attr~Helixir
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
attr :messenger, :string, required: true
def greet(assigns) do
~H"""
<h2>Hello World, from <%= @messenger %>!</h2>
"""
end
endUsing Function Components
使用函数组件
Invoke components with the syntax:
<.component_name />heex
<section>
<.greet messenger={@messenger} />
</section>使用 语法调用组件:
<.component_name />heex
<section>
<.greet messenger={@messenger} />
</section>Optional Attributes with Defaults
带默认值的可选属性
Define optional attributes with default values:
elixir
attr :messenger, :string, default: nil
attr :class, :string, default: "greeting"
def greet(assigns) do
~H"""
<h2 class={@class}>
Hello World<%= if @messenger, do: ", from #{@messenger}" %>!
</h2>
"""
end定义带有默认值的可选属性:
elixir
attr :messenger, :string, default: nil
attr :class, :string, default: "greeting"
def greet(assigns) do
~H"""
<h2 class={@class}>
Hello World<%= if @messenger, do: ", from #{@messenger}" %>!
</h2>
"""
endMultiple Attribute Types
多种属性类型
Components can accept various attribute types:
elixir
attr :title, :string, required: true
attr :count, :integer, default: 0
attr :active, :boolean, default: false
attr :user, :map, required: true
attr :items, :list, default: []
def card(assigns) do
~H"""
<div class={"card" <> if @active, do: " active", else: ""}>
<h3><%= @title %></h3>
<p>Count: <%= @count %></p>
<p>User: <%= @user.name %></p>
<ul>
<li :for={item <- @items}><%= item %></li>
</ul>
</div>
"""
end组件可以接受多种类型的属性:
elixir
attr :title, :string, required: true
attr :count, :integer, default: 0
attr :active, :boolean, default: false
attr :user, :map, required: true
attr :items, :list, default: []
def card(assigns) do
~H"""
<div class={"card" <> if @active, do: " active", else: ""}>
<h3><%= @title %></h3>
<p>Count: <%= @count %></p>
<p>User: <%= @user.name %></p>
<ul>
<li :for={item <- @items}><%= item %></li>
</ul>
</div>
"""
endComponents with Computed Values
带计算值的组件
Use to compute values within components:
assign/2elixir
attr :x, :integer, required: true
attr :y, :integer, required: true
attr :title, :string, required: true
def sum_component(assigns) do
assigns = assign(assigns, sum: assigns.x + assigns.y)
~H"""
<h1><%= @title %></h1>
<p>Sum: <%= @sum %></p>
"""
end使用 在组件内部计算值:
assign/2elixir
attr :x, :integer, required: true
attr :y, :integer, required: true
attr :title, :string, required: true
def sum_component(assigns) do
assigns = assign(assigns, sum: assigns.x + assigns.y)
~H"""
<h1><%= @title %></h1>
<p>Sum: <%= @sum %></p>
"""
endSlots
插槽
Slots allow components to accept blocks of content, enabling powerful composition patterns.
插槽允许组件接受内容块,实现强大的组合模式。
Defining and Using Slots
定义与使用插槽
Define a slot and render it:
elixir
slot :inner_block, required: true
def card(assigns) do
~H"""
<div class="card">
<%= render_slot(@inner_block) %>
</div>
"""
endUse the component with content:
heex
<.card>
<h2>Card Title</h2>
<p>Card content goes here</p>
</.card>定义插槽并渲染:
elixir
slot :inner_block, required: true
def card(assigns) do
~H"""
<div class="card">
<%= render_slot(@inner_block) %>
</div>
"""
end传入内容使用组件:
heex
<.card>
<h2>Card Title</h2>
<p>Card content goes here</p>
</.card>Named Slots
命名插槽
Components can have multiple named slots:
elixir
slot :header, required: true
slot :body, required: true
slot :footer
def panel(assigns) do
~H"""
<div class="panel">
<div class="panel-header">
<%= render_slot(@header) %>
</div>
<div class="panel-body">
<%= render_slot(@body) %>
</div>
<%= if @footer != [] do %>
<div class="panel-footer">
<%= render_slot(@footer) %>
</div>
<% end %>
</div>
"""
endUsage:
heex
<.panel>
<:header>
<h2>Panel Title</h2>
</:header>
<:body>
<p>Panel content</p>
</:body>
<:footer>
<button>Close</button>
</:footer>
</.panel>组件可以包含多个命名插槽:
elixir
slot :header, required: true
slot :body, required: true
slot :footer
def panel(assigns) do
~H"""
<div class="panel">
<div class="panel-header">
<%= render_slot(@header) %>
</div>
<div class="panel-body">
<%= render_slot(@body) %>
</div>
<%= if @footer != [] do %>
<div class="panel-footer">
<%= render_slot(@footer) %>
</div>
<% end %>
</div>
"""
end使用方式:
heex
<.panel>
<:header>
<h2>Panel Title</h2>
</:header>
<:body>
<p>Panel content</p>
</:body>
<:footer>
<button>Close</button>
</:footer>
</.panel>Slots with Attributes
带属性的插槽
Slots can accept attributes for more dynamic rendering:
elixir
slot :item, required: true do
attr :title, :string, required: true
attr :highlighted, :boolean, default: false
end
def list(assigns) do
~H"""
<ul>
<%= for item <- @item do %>
<li class={if item.highlighted, do: "highlight"}>
<%= item.title %>: <%= render_slot(item) %>
</li>
<% end %>
</ul>
"""
end插槽可以接受属性以实现更动态的渲染:
elixir
slot :item, required: true do
attr :title, :string, required: true
attr :highlighted, :boolean, default: false
end
def list(assigns) do
~H"""
<ul>
<%= for item <- @item do %>
<li class={if item.highlighted, do: "highlight"}>
<%= item.title %>: <%= render_slot(item) %>
</li>
<% end %>
</ul>
"""
endRendering Child Templates
渲染子模板
Rendering Other Templates
渲染其他模板
Include child templates within a parent:
heex
<%= render("child_template.html", assigns) %>在父模板中包含子模板:
heex
<%= render("child_template.html", assigns) %>Rendering Components from Other Modules
渲染其他模块的组件
Call components from different modules:
heex
<MyApp.Components.button text="Click me" />Or with aliasing:
elixir
alias MyApp.Components调用不同模块中的组件:
heex
<MyApp.Components.button text="Click me" />或者使用别名:
elixir
alias MyApp.ComponentsIn template:
In template:
<Components.button text="Click me" />
undefined<Components.button text="Click me" />
undefinedLayout Templates
布局模板
Using Layouts
使用布局
Layouts wrap rendered templates. Configure the layout in the controller:
elixir
def controller do
quote do
use Phoenix.Controller,
formats: [:html, :json],
layouts: [html: HelloWeb.Layouts]
...
end
end布局用于包裹渲染后的模板。在控制器中配置布局:
elixir
def controller do
quote do
use Phoenix.Controller,
formats: [:html, :json],
layouts: [html: HelloWeb.Layouts]
...
end
endRoot Layout
根布局
The root layout includes the placeholder:
@inner_contentheex
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>My App</title>
</head>
<body>
<%= @inner_content %>
</body>
</html>根布局包含 占位符:
@inner_contentheex
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>My App</title>
</head>
<body>
<%= @inner_content %>
</body>
</html>App Layout Component
应用布局组件
Nest layouts using components:
heex
<Layouts.app flash={@flash}>
<section>
<h2>Hello World, from <%= @messenger %>!</h2>
</section>
</Layouts.app>使用组件嵌套布局:
heex
<Layouts.app flash={@flash}>
<section>
<h2>Hello World, from <%= @messenger %>!</h2>
</section>
</Layouts.app>Disabling Layouts
禁用布局
Render without a layout:
elixir
def home(conn, _params) do
render(conn, :home, layout: false)
end不使用布局进行渲染:
elixir
def home(conn, _params) do
render(conn, :home, layout: false)
endLiveView Integration
LiveView 集成
Delegating to Phoenix Views
委托给 Phoenix 视图
LiveView can delegate rendering to existing view modules:
elixir
defmodule AppWeb.ThermostatLive do
use Phoenix.LiveView
def render(assigns) do
Phoenix.View.render(AppWeb.PageView, "page.html", assigns)
end
endLiveView 可以将渲染委托给已有的视图模块:
elixir
defmodule AppWeb.ThermostatLive do
use Phoenix.LiveView
def render(assigns) do
Phoenix.View.render(AppWeb.PageView, "page.html", assigns)
end
endEmbedding LiveView in Templates
在模板中嵌入 LiveView
Render LiveView components within static templates:
heex
<h1>Temperature Control</h1>
<%= live_render(@conn, AppWeb.ThermostatLive) %>在静态模板中渲染 LiveView 组件:
heex
<h1>Temperature Control</h1>
<%= live_render(@conn, AppWeb.ThermostatLive) %>Function Components in LiveView
LiveView 中的函数组件
Define and use function components in LiveView:
elixir
def weather_greeting(assigns) do
~H"""
<div title="My div" class={@class}>
<p>Hello <%= @name %></p>
<MyApp.Weather.city name="Kraków"/>
</div>
"""
end在 LiveView 中定义并使用函数组件:
elixir
def weather_greeting(assigns) do
~H"""
<div title="My div" class={@class}>
<p>Hello <%= @name %></p>
<MyApp.Weather.city name="Kraków"/>
</div>
"""
endTesting Views
测试视图
Testing View Rendering
测试视图渲染
Test views directly using :
render_to_string/4elixir
defmodule HelloWeb.ErrorHTMLTest do
use HelloWeb.ConnCase, async: true
import Phoenix.Template
test "renders 404.html" do
assert render_to_string(HelloWeb.ErrorHTML, "404", "html", []) == "Not Found"
end
test "renders 500.html" do
assert render_to_string(HelloWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
end
end使用 直接测试视图:
render_to_string/4elixir
defmodule HelloWeb.ErrorHTMLTest do
use HelloWeb.ConnCase, async: true
import Phoenix.Template
test "renders 404.html" do
assert render_to_string(HelloWeb.ErrorHTML, "404", "html", []) == "Not Found"
end
test "renders 500.html" do
assert render_to_string(HelloWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
end
endTesting Function Components
测试函数组件
Test components in isolation:
elixir
import Phoenix.LiveViewTest
test "renders greet component" do
assigns = %{messenger: "Phoenix"}
html = rendered_to_string(~H"""
<HelloWeb.HelloHTML.greet messenger={@messenger} />
""")
assert html =~ "Hello World, from Phoenix!"
end独立测试组件:
elixir
import Phoenix.LiveViewTest
test "renders greet component" do
assigns = %{messenger: "Phoenix"}
html = rendered_to_string(~H"""
<HelloWeb.HelloHTML.greet messenger={@messenger} />
""")
assert html =~ "Hello World, from Phoenix!"
endWhen to Use This Skill
何时使用此技能
Use this skill when you need to:
- Create dynamic HTML templates for Phoenix applications
- Build reusable function components for consistent UI elements
- Implement conditional rendering based on application state
- Render lists and tables with dynamic data
- Create complex layouts with nested components and slots
- Integrate LiveView components with static templates
- Test view rendering logic and component behavior
- Build accessible and semantic HTML structures
- Implement responsive designs with dynamic classes
- Create forms with validation feedback
- Display flash messages and user notifications
- Render navigation menus and breadcrumbs
- Build card-based layouts and dashboards
- Implement pagination controls
在以下场景中使用此技能:
- 为 Phoenix 应用创建动态 HTML 模板
- 构建可复用的函数组件以实现一致的 UI 元素
- 根据应用状态实现条件渲染
- 渲染包含动态数据的列表和表格
- 使用嵌套组件和插槽创建复杂布局
- 将 LiveView 组件与静态模板集成
- 测试视图渲染逻辑和组件行为
- 构建可访问且语义化的 HTML 结构
- 使用动态类实现响应式设计
- 创建带验证反馈的表单
- 显示闪存消息和用户通知
- 渲染导航菜单和面包屑
- 构建基于卡片的布局和仪表板
- 实现分页控件
Best Practices
最佳实践
- Use function components - Encapsulate reusable UI patterns in components
- Declare attributes explicitly - Use macro for all component attributes
attr - Provide default values - Make components flexible with sensible defaults
- Use semantic HTML - Choose appropriate HTML elements for accessibility
- Leverage slots - Use slots for flexible component composition
- Keep templates simple - Move complex logic to controller or context
- Use :for shorthand - Prefer attribute for simple iterations
:for - Avoid inline styles - Use CSS classes for styling
- Test components - Write tests for complex component logic
- Document components - Add docstrings to explain component usage
- Use verified routes - Always use sigil in templates
~p - Escape user content - Let HEEx handle escaping automatically
- Optimize renders - Minimize computation in template code
- Use descriptive names - Name components and attributes clearly
- Follow conventions - Stick to Phoenix naming patterns
- 使用函数组件 - 将可复用的 UI 模式封装到组件中
- 显式声明属性 - 对所有组件属性使用 宏
attr - 提供默认值 - 为组件设置合理的默认值以提升灵活性
- 使用语义化 HTML - 选择合适的 HTML 元素以提升可访问性
- 利用插槽 - 使用插槽实现灵活的组件组合
- 保持模板简洁 - 将复杂逻辑移至控制器或上下文
- 使用 :for 简写 - 针对简单迭代优先使用 属性
:for - 避免内联样式 - 使用 CSS 类进行样式控制
- 测试组件 - 为复杂组件逻辑编写测试
- 文档化组件 - 添加文档字符串说明组件用法
- 使用验证路由 - 在模板中始终使用 符号
~p - 转义用户内容 - 让 HEEx 自动处理转义
- 优化渲染 - 尽量减少模板代码中的计算量
- 使用描述性名称 - 为组件和属性设置清晰的名称
- 遵循约定 - 坚持使用 Phoenix 的命名模式
Common Pitfalls
常见陷阱
- Putting logic in templates - Complex business logic belongs in contexts, not views
- Not escaping HTML - Using without sanitizing user input
raw/1 - Deeply nested templates - Creating hard-to-maintain template hierarchies
- Missing attribute declarations - Not using macro for component attributes
attr - Overusing inline conditionals - Making templates hard to read
- Not using components - Repeating markup instead of extracting components
- Forgetting slot checks - Not checking if optional slots are provided
- Mixing concerns - Combining data fetching with presentation logic
- Large template files - Creating monolithic templates instead of components
- Inconsistent formatting - Not following HEEx formatting conventions
- Using EEx instead of HEEx - Missing compile-time validation benefits
- Ignoring accessibility - Not adding ARIA labels and semantic markup
- Hardcoding values - Not using assigns for configurable content
- Not testing edge cases - Missing nil checks and empty state handling
- Excessive nesting - Creating deeply nested component trees
- 在模板中编写逻辑 - 复杂业务逻辑应放在上下文而非视图中
- 未转义 HTML - 在未清理用户输入的情况下使用
raw/1 - 模板嵌套过深 - 创建难以维护的模板层级
- 缺少属性声明 - 不为组件属性使用 宏
attr - 过度使用内联条件 - 导致模板难以阅读
- 未使用组件 - 重复编写标记而非提取组件
- 忘记检查插槽 - 未检查可选插槽是否已提供
- 关注点混合 - 将数据获取与展示逻辑混合
- 模板文件过大 - 创建单体模板而非拆分组件
- 格式不一致 - 未遵循 HEEx 格式约定
- 使用 EEx 而非 HEEx - 错失编译时验证的优势
- 忽略可访问性 - 未添加 ARIA 标签和语义化标记
- 硬编码值 - 未使用 Assigns 处理可配置内容
- 未测试边缘情况 - 缺少空值检查和空状态处理
- 过度嵌套 - 创建深度嵌套的组件树