视图基础 #

一、视图概述 #

1.1 什么是视图 #

在Phoenix中,视图模块负责处理模板渲染和提供辅助函数。Phoenix 1.7+使用新的视图结构,HTML模块替代了传统的View模块。

text
Controller → HTML模块 → 模板(.heex) → HTML输出

1.2 视图结构 #

text
lib/hello_web/
├── controllers/
│   ├── user_controller.ex
│   ├── user_html.ex          # HTML视图模块
│   └── user_html/            # 模板目录
│       ├── index.html.heex
│       ├── show.html.heex
│       └── new.html.heex
└── components/
    ├── core_components.ex
    └── layouts.ex

二、HTML模块 #

2.1 创建HTML模块 #

elixir
defmodule HelloWeb.UserHTML do
  use HelloWeb, :html

  embed_templates "user_html/*"

  def format_date(date) do
    Calendar.strftime(date, "%Y-%m-%d")
  end

  def user_name(user) do
    "#{user.first_name} #{user.last_name}"
  end
end

2.2 embed_templates #

elixir
defmodule HelloWeb.UserHTML do
  use HelloWeb, :html

  embed_templates "user_html/*"
end

这会将模板目录中的所有.heex文件编译为函数:

elixir
HelloWeb.UserHTML.index(assigns)
HelloWeb.UserHTML.show(assigns)
HelloWeb.UserHTML.new(assigns)

2.3 定义内联模板 #

elixir
defmodule HelloWeb.PageHTML do
  use HelloWeb, :html

  def home(assigns) do
    ~H"""
    <div class="container">
      <h1>Welcome to Phoenix!</h1>
      <p>Start your journey here.</p>
    </div>
    """
  end
end

三、模板文件 #

3.1 模板命名 #

text
user_html/
├── index.html.heex    # 列表页
├── show.html.heex     # 详情页
├── new.html.heex      # 新建表单
├── edit.html.heex     # 编辑表单
└── _user.html.heex    # 部分模板(下划线开头)

3.2 基本模板 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

3.3 使用Assigns #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

四、辅助函数 #

4.1 定义辅助函数 #

elixir
defmodule HelloWeb.UserHTML do
  use HelloWeb, :html

  embed_templates "user_html/*"

  def format_date(nil), do: "N/A"
  def format_date(date), do: Calendar.strftime(date, "%Y-%m-%d %H:%M")

  def status_badge(:active), do: "bg-green-100 text-green-800"
  def status_badge(:inactive), do: "bg-gray-100 text-gray-800"
  def status_badge(:pending), do: "bg-yellow-100 text-yellow-800"

  def truncate(text, length \\ 50) do
    if String.length(text) > length do
      String.slice(text, 0, length) <> "..."
    else
      text
    end
  end
end

4.2 在模板中使用 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

五、部分模板 #

5.1 创建部分模板 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

5.2 渲染部分模板 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

5.3 集合渲染 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

六、布局 #

6.1 应用布局 #

elixir
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end
heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

6.2 设置布局 #

elixir
defmodule HelloWeb.UserController do
  use HelloWeb, :controller

  plug :put_layout, "admin.html"

  def index(conn, _params) do
    render(conn, :index)
  end
end

6.3 禁用布局 #

elixir
def show(conn, %{"id" => id}) do
  user = Hello.Accounts.get_user!(id)
  render(conn, :show, user: user, layout: false)
end

七、组件 #

7.1 函数组件 #

elixir
defmodule HelloWeb.CoreComponents do
  use Phoenix.Component

  attr :title, :string, required: true
  attr :class, :string, default: ""

  def card(assigns) do
    ~H"""
    <div class={"card #{@class}"}>
      <h2 class="card-title"><%= @title %></h2>
      <%= render_slot(@inner_block) %>
    </div>
    """
  end

  attr :type, :atom, values: [:info, :success, :warning, :error], default: :info
  attr :message, :string, required: true

  def alert(assigns) do
    ~H"""
    <div class={"alert alert-#{@type}"}>
      <%= @message %>
    </div>
    """
  end
end

7.2 使用组件 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

7.3 带插槽的组件 #

elixir
attr :title, :string, required: true
slot :actions
slot :footer

def panel(assigns) do
  ~H"""
  <div class="panel">
    <div class="panel-header">
      <h3><%= @title %></h3>
      <div class="panel-actions">
        <%= for action <- @actions do %>
          <%= render_slot(action) %>
        <% end %>
      </div>
    </div>
    <div class="panel-body">
      <%= render_slot(@inner_block) %>
    </div>
    <div class="panel-footer">
      <%= render_slot(@footer) %>
    </div>
  </div>
  """
end

使用:

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

八、路由辅助函数 #

8.1 路径辅助函数 #

elixir
defmodule HelloWeb.UserHTML do
  use HelloWeb, :html

  embed_templates "user_html/*"

  def user_link(conn, user) do
    path = ~p"/users/#{user.id}"
    link(user.name, to: path)
  end
end

8.2 在模板中使用 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

九、表单辅助函数 #

9.1 表单组件 #

elixir
defmodule HelloWeb.CoreComponents do
  use Phoenix.Component

  attr :for, :any, required: true
  attr :as, :atom, default: :form

  def simple_form(assigns) do
    ~H"""
    <.form :let={f} for={@for} as={@as}>
      <%= render_slot(@inner_block, f) %>
    </.form>
    """
  end

  attr :field, Phoenix.HTML.FormField, required: true
  attr :label, :string, default: nil
  attr :type, :atom, default: :text

  def input(assigns) do
    ~H"""
    <div class="form-group">
      <label><%= @label || Phoenix.HTML.Form.humanize(@field.field) %></label>
      <input type={@type} name={@field.name} value={@field.value} />
      <%= if @field.errors do %>
        <span class="error"><%= Enum.join(@field.errors, ", ") %></span>
      <% end %>
    </div>
    """
  end
end

9.2 使用表单组件 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

十、国际化 #

10.1 Gettext配置 #

elixir
defmodule HelloWeb.Gettext do
  use Gettext, otp_app: :hello
end

10.2 翻译文件 #

text
priv/gettext/
├── default.pot
├── en/
│   └── LC_MESSAGES/
│       └── default.po
└── zh/
    └── LC_MESSAGES/
        └── default.po

10.3 使用翻译 #

elixir
defmodule HelloWeb.UserHTML do
  use HelloWeb, :html
  import HelloWeb.Gettext

  embed_templates "user_html/*"

  def status_text(:active), do: gettext("Active")
  def status_text(:inactive), do: gettext("Inactive")
end
heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

十一、安全 #

11.1 HTML转义 #

Phoenix自动转义HTML:

elixir
user_input = "<script>alert('xss')</script>"
heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

输出:&lt;script&gt;alert('xss')&lt;/script&gt;

11.2 原始HTML #

elixir
def safe_html(html) do
  {:safe, html}
end
heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

11.3 CSRF保护 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

十二、总结 #

12.1 核心概念 #

概念 说明
HTML模块 视图模块,定义辅助函数
embed_templates 嵌入模板文件
assigns 模板变量
组件 可复用的UI单元
布局 页面框架模板

12.2 常用函数 #

函数 说明
render/2 渲染模板
render/3 渲染带参数的模板
link/2 生成链接
form/2 生成表单
~H HEEx模板宏

12.3 下一步 #

现在你已经了解了视图基础,接下来让我们学习 模板语法,深入了解HEEx模板!

最后更新:2026-03-28