phoenix-views-templates

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Phoenix 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
embed_templates
macro to load HEEx templates from a directory:
elixir
defmodule HelloWeb.HelloHTML do
  use HelloWeb, :html

  embed_templates "hello_html/*"
end
This automatically creates functions for each
.html.heex
file in the
hello_html/
directory.
Phoenix 视图模块使用
embed_templates
宏从目录中加载 HEEx 模板:
elixir
defmodule HelloWeb.HelloHTML do
  use HelloWeb, :html

  embed_templates "hello_html/*"
end
这会自动为
hello_html/
目录中的每个
.html.heex
文件创建对应的函数。

HEEx 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>
@
符号用于访问从控制器传递过来的 Assigns。

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
undefined
Assigns 是从控制器传递到模板的键值对:
elixir
undefined

Controller

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
if/else
blocks:
heex
<%= 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/else
块进行条件渲染:
heex
<%= 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
for
:
heex
<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>
使用
for
生成动态列表:
heex
<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/2
:
heex
<%= for {item, index} <- Enum.with_index(@items) do %>
  <div class="item-<%= index %>">
    <%= item.name %>
  </div>
<% end %>
使用
Enum.with_index/2
获取迭代索引:
heex
<%= 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
attr
macro to declare attributes and the
~H
sigil for the template:
elixir
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
宏声明属性,并使用
~H
符号定义模板:
elixir
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

Using Function Components

使用函数组件

Invoke components with the
<.component_name />
syntax:
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>
  """
end

Multiple 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>
  """
end

Components with Computed Values

带计算值的组件

Use
assign/2
to compute values within components:
elixir
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/2
在组件内部计算值:
elixir
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

Slots

插槽

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>
  """
end
Use 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>
  """
end
Usage:
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>
  """
end

Rendering 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.Components

In template:

In template:

<Components.button text="Click me" />
undefined
<Components.button text="Click me" />
undefined

Layout 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
end

Root Layout

根布局

The root layout includes the
@inner_content
placeholder:
heex
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title>My App</title>
  </head>
  <body>
    <%= @inner_content %>
  </body>
</html>
根布局包含
@inner_content
占位符:
heex
<!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)
end

LiveView 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
end
LiveView 可以将渲染委托给已有的视图模块:
elixir
defmodule AppWeb.ThermostatLive do
  use Phoenix.LiveView

  def render(assigns) do
    Phoenix.View.render(AppWeb.PageView, "page.html", assigns)
  end
end

Embedding 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>
  """
end

Testing Views

测试视图

Testing View Rendering

测试视图渲染

Test views directly using
render_to_string/4
:
elixir
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/4
直接测试视图:
elixir
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

Testing 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!"
end

When to Use This Skill

何时使用此技能

Use this skill when you need to:
  1. Create dynamic HTML templates for Phoenix applications
  2. Build reusable function components for consistent UI elements
  3. Implement conditional rendering based on application state
  4. Render lists and tables with dynamic data
  5. Create complex layouts with nested components and slots
  6. Integrate LiveView components with static templates
  7. Test view rendering logic and component behavior
  8. Build accessible and semantic HTML structures
  9. Implement responsive designs with dynamic classes
  10. Create forms with validation feedback
  11. Display flash messages and user notifications
  12. Render navigation menus and breadcrumbs
  13. Build card-based layouts and dashboards
  14. Implement pagination controls
在以下场景中使用此技能:
  1. 为 Phoenix 应用创建动态 HTML 模板
  2. 构建可复用的函数组件以实现一致的 UI 元素
  3. 根据应用状态实现条件渲染
  4. 渲染包含动态数据的列表和表格
  5. 使用嵌套组件和插槽创建复杂布局
  6. 将 LiveView 组件与静态模板集成
  7. 测试视图渲染逻辑和组件行为
  8. 构建可访问且语义化的 HTML 结构
  9. 使用动态类实现响应式设计
  10. 创建带验证反馈的表单
  11. 显示闪存消息和用户通知
  12. 渲染导航菜单和面包屑
  13. 构建基于卡片的布局和仪表板
  14. 实现分页控件

Best Practices

最佳实践

  1. Use function components - Encapsulate reusable UI patterns in components
  2. Declare attributes explicitly - Use
    attr
    macro for all component attributes
  3. Provide default values - Make components flexible with sensible defaults
  4. Use semantic HTML - Choose appropriate HTML elements for accessibility
  5. Leverage slots - Use slots for flexible component composition
  6. Keep templates simple - Move complex logic to controller or context
  7. Use :for shorthand - Prefer
    :for
    attribute for simple iterations
  8. Avoid inline styles - Use CSS classes for styling
  9. Test components - Write tests for complex component logic
  10. Document components - Add docstrings to explain component usage
  11. Use verified routes - Always use
    ~p
    sigil in templates
  12. Escape user content - Let HEEx handle escaping automatically
  13. Optimize renders - Minimize computation in template code
  14. Use descriptive names - Name components and attributes clearly
  15. Follow conventions - Stick to Phoenix naming patterns
  1. 使用函数组件 - 将可复用的 UI 模式封装到组件中
  2. 显式声明属性 - 对所有组件属性使用
    attr
  3. 提供默认值 - 为组件设置合理的默认值以提升灵活性
  4. 使用语义化 HTML - 选择合适的 HTML 元素以提升可访问性
  5. 利用插槽 - 使用插槽实现灵活的组件组合
  6. 保持模板简洁 - 将复杂逻辑移至控制器或上下文
  7. 使用 :for 简写 - 针对简单迭代优先使用
    :for
    属性
  8. 避免内联样式 - 使用 CSS 类进行样式控制
  9. 测试组件 - 为复杂组件逻辑编写测试
  10. 文档化组件 - 添加文档字符串说明组件用法
  11. 使用验证路由 - 在模板中始终使用
    ~p
    符号
  12. 转义用户内容 - 让 HEEx 自动处理转义
  13. 优化渲染 - 尽量减少模板代码中的计算量
  14. 使用描述性名称 - 为组件和属性设置清晰的名称
  15. 遵循约定 - 坚持使用 Phoenix 的命名模式

Common Pitfalls

常见陷阱

  1. Putting logic in templates - Complex business logic belongs in contexts, not views
  2. Not escaping HTML - Using
    raw/1
    without sanitizing user input
  3. Deeply nested templates - Creating hard-to-maintain template hierarchies
  4. Missing attribute declarations - Not using
    attr
    macro for component attributes
  5. Overusing inline conditionals - Making templates hard to read
  6. Not using components - Repeating markup instead of extracting components
  7. Forgetting slot checks - Not checking if optional slots are provided
  8. Mixing concerns - Combining data fetching with presentation logic
  9. Large template files - Creating monolithic templates instead of components
  10. Inconsistent formatting - Not following HEEx formatting conventions
  11. Using EEx instead of HEEx - Missing compile-time validation benefits
  12. Ignoring accessibility - Not adding ARIA labels and semantic markup
  13. Hardcoding values - Not using assigns for configurable content
  14. Not testing edge cases - Missing nil checks and empty state handling
  15. Excessive nesting - Creating deeply nested component trees
  1. 在模板中编写逻辑 - 复杂业务逻辑应放在上下文而非视图中
  2. 未转义 HTML - 在未清理用户输入的情况下使用
    raw/1
  3. 模板嵌套过深 - 创建难以维护的模板层级
  4. 缺少属性声明 - 不为组件属性使用
    attr
  5. 过度使用内联条件 - 导致模板难以阅读
  6. 未使用组件 - 重复编写标记而非提取组件
  7. 忘记检查插槽 - 未检查可选插槽是否已提供
  8. 关注点混合 - 将数据获取与展示逻辑混合
  9. 模板文件过大 - 创建单体模板而非拆分组件
  10. 格式不一致 - 未遵循 HEEx 格式约定
  11. 使用 EEx 而非 HEEx - 错失编译时验证的优势
  12. 忽略可访问性 - 未添加 ARIA 标签和语义化标记
  13. 硬编码值 - 未使用 Assigns 处理可配置内容
  14. 未测试边缘情况 - 缺少空值检查和空状态处理
  15. 过度嵌套 - 创建深度嵌套的组件树

Resources

资源