CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
视图与组件
这一课你将能做到
用 ERB 模板渲染数据,理解 <%= %> 与 <% %> 的区别
用布局 + yield + content_for 组织页面骨架,用 partial 消除重复片段
说清楚 Rails 服务端渲染与 Next.js 客户端渲染的取舍,知道 Hotwire 补位交互
为什么要学
视图是用户唯一能看见的部分。Rails 的视图层哲学是「HTML 优先」:服务端把数据填进模板直接吐出完整 HTML,浏览器零 JS 也能用;需要交互的地方交给 Hotwire(Turbo + Stimulus)做渐进增强。这和本站的 Next.js 路线(浏览器下载 JS bundle → 客户端取数据渲染)是两种范式,各自有明确的适用边界——内容型、后台型产品服务端渲染交付更快,重交互应用才需要前端框架。
先建立图像
视图的套娃结构:
layouts/application.html.erb ← 最外层:html/head/body 骨架
└── yield ← 内容插槽
└── articles/index.html.erb ← 动作对应视图
└── render "article" ← partial 局部视图(每个文章卡片)
└── _article.html.erb
渲染顺序由内而外:partial → 动作视图 → 布局 → 完整 HTML 响应。
ERB 两种标签:
<% @articles.each do |a| %> ← 只执行,不输出
<%= a.title %> ← 执行并输出(HTML 转义)
<% end %>
跟着做
步骤 1:第一个视图
app/views/articles/index.html.erb:
<h1>文章列表</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= link_to article.title, article_path(article) %>
<small><%= article.created_at.strftime("%Y-%m-%d") %></small>
</li>
<% end %>
</ul>
<%= link_to "写新文章", new_article_path %>
控制器里 @articles = Article.all,视图直接用实例变量。文件名规则:动作名.格式.模板引擎——index.html.erb 意为「HTML 格式、ERB 引擎」。
步骤 2:布局与 yield
app/views/layouts/application.html.erb:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title><%= content_for(:title) || "我的博客" %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<nav><%= link_to "首页", root_path %></nav>
<% flash.each do |type, msg| %>
<div class="flash <%= type %>"><%= msg %></div>
<% end %>
<main><%= yield %></main>
<footer>© 2026</footer>
</body>
</html>
每个动作视图的内容会被塞进 yield 位置。子页面想定制标题:
<%# app/views/articles/show.html.erb %>
<% content_for :title, @article.title %>
<h1><%= @article.title %></h1>
csrf_meta_tags 输出 CSRF 令牌给 JS 用,stylesheet_link_tag/javascript_importmap_tags 挂资产——都是布局的标配。
步骤 3:partial 消除重复 列表页和搜索页都要渲染文章卡片,抽成 partial。文件名以下划线开头:
<%# app/views/articles/_article.html.erb %>
<article class="card">
<h2><%= link_to article.title, article_path(article) %></h2>
<p><%= truncate(article.body, length: 120) %></p>
</article>
<%# 单条渲染 %>
<%= render "article", article: @article %>
<%# 集合渲染:自动对每条记录调用一次 %>
<%= render @articles %>
render @articles 是 Rails 的约定魔法:按模型类名找到 _article.html.erb,循环渲染并把局部变量 article 依次赋值——等价于手写 each + render,但短得多。
步骤 4:helper 放视图逻辑 视图中不该有复杂 Ruby 逻辑,抽到 app/helpers/articles_helper.rb:
module ArticlesHelper
def status_badge(article)
if article.published?
tag.span "已发布", class: "badge badge-green"
else
tag.span "草稿", class: "badge badge-gray"
end
end
end
视图里一行调用:<%= status_badge(article) %>。tag.span 是内置的 HTML 生成器,比手写字符串安全(自动转义属性)。
步骤 5:了解组件化方向——ViewComponent 当 partial + helper 的组合在大型项目里失控(隐式依赖实例变量、难以测试),社区方案 ViewComponent(GitHub 开源)把视图封装成 Ruby 类:
# app/components/article_card_component.rb
class ArticleCardComponent < ViewComponent::Base
def initialize(article:)
@article = article
end
end
<%# app/components/article_card_component.html.erb %>
<article class="card">
<h2><%= link_to @article.title, article_path(@article) %></h2>
</article>
调用:<%= render ArticleCardComponent.new(article: @article) %>。显式传参、可单测——思路和 React 组件一致,但渲染发生在服务端。初学者先把 partial 用熟,项目大了再上组件库。
讲清楚要点
<%= %> 输出并自动转义 :<%= "<b>x</b>" %> 会显示成文本 <b>x</b> 而不是加粗——这是 XSS 防线。确需输出可信 HTML 用 raw() 或 .html_safe,但永远不要对用户输入这么做。
实例变量是控制器到视图的桥 :@articles 在控制器赋值,视图直接可用。partial 里用局部变量(render 时显式传),别依赖实例变量——隐式依赖让 partial 无法复用。
布局可多套 :layout "admin" 切换布局;render layout: false 不要布局(如弹窗片段)。
helper 自动可用 :ArticlesHelper 的方法在 articles 相关视图直接调用。所有 helper 默认全局可见,命名要清晰避免冲突。
Hotwire 是视图层的交互答案 :Turbo 把链接/表单提交变成局部替换(无刷新体验),Stimulus 是轻量 JS 控制器处理富交互。80% 的「想要 SPA 感」需求,Hotwire 就够,不必上 React。
你可能会懵的地方
ERB 和其他模板引擎(Haml/Slim)选哪个?
ERB 是官方默认、文档最全、团队零学习成本。Haml/Slim 用缩进代替闭合标签,写起来快但可读性见仁见智。初学者用 ERB,别折腾。
render 和 render partial: 区别?
render "article" 在视图里默认就是渲染 partial(找下划线文件);render template: "articles/special" 才渲染完整模板。日常 99% 是 partial。
集合渲染怎么做分页?
分页是查询层的事:控制器里 @articles = Article.page(params[:page]).per(20)(kaminari 或 pagy gem),视图 <%= render @articles %> 照旧,加分页导航 <%= paginate @articles %>。
视图里能直接查数据库吗?
语法上可以(<% Article.count %>),但这是严重坏味道——视图查询绕过控制器,既难缓存又容易 N+1。所有数据在控制器备好。
什么时候该用 Next.js 而不是 Rails 视图?
交互密度是分水岭:拖拽编辑器、实时协作、复杂状态机——Next.js;CRUD、内容展示、表单流程、后台管理——Rails 视图 + Hotwire 更快更省。很多产品是混合架构:对外的营销页 Rails 渲染,核心交互应用 Next.js。
错法对照 错法 问题 正解 <%= raw(comment.body) %> 渲染用户输入XSS 漏洞 默认 <%= %> 自动转义,富文本用 sanitize partial 里直接用 @article 隐式依赖,别处无法复用 render "article", article: a 显式传局部变量视图里 <% Article.where... %> 绕过控制器,易 N+1 数据在控制器查询 五层嵌套 if/else 在 ERB 里 逻辑混乱难测 抽 helper 或 presenter 所有页面共用一套布局硬编码 后台/前台风格冲突 多布局 + layout "admin"
卡点急救
ActionView::MissingTemplate :文件名不符合 动作.格式.引擎 约定,或放错目录(应在 app/views/控制器名/ 下)。检查单复数。
partial 报 undefined local variable or method 'article' :渲染时没传局部变量,补 render "article", article: @article;集合渲染确认模型类名与 partial 文件名匹配。
修改视图不生效 :生产/预编译环境模板有缓存,开发环境正常应即时生效;若在开发环境不生效,重启服务器并检查是否改对了环境对应的文件。
面试常问
Rails 服务端渲染和 React/Next.js 客户端渲染怎么选?
答:看交互密度和团队结构。内容/CRUD/后台类产品,Rails 服务端渲染 + Hotwire 交付快、SEO 天然友好、一套代码无 API 层;重交互(编辑器、实时协作)、多端复用数据、前端团队独立时,前后端分离更合适。成本模型:前者省一层 API 和状态同步,后者换来交互上限和团队并行。
ERB 的 <%= %> 为什么默认转义?
答:防 XSS。用户输入若含 <script>,转义后显示为文本而非执行。输出可信 HTML 需显式 raw/sanitize——把「危险操作」做成显式动作,是 Rails 的安全设计哲学。
partial 和 ViewComponent 的取舍?
答:partial 轻量、约定式,适合页面片段复用;但依赖隐式约定、无法独立测试。ViewComponent 显式传参、有 Ruby 类可单测、支持插槽,适合设计系统和大型项目。小项目 partial 足够,组件超过几十个考虑迁移。
自测三问
<% %> 和 <%= %> 的区别?后者为什么天然防 XSS?
<%= render @articles %> 背后发生了什么约定推断?
布局里的 yield 和 content_for 分别解决什么问题?
小结一句话 Rails 视图 = 布局套模板、模板嵌 partial,默认转义保安全,数据永远来自控制器——HTML 优先,交互交给 Hotwire 增强。
延伸