Rails:使用命名yield渲染Partial时内容无法显示
问题描述
尝试通过命名yield在partial中渲染自定义内容,但外部模板中用content_for(:modal_content)定义的内容无法被partial内的yield(:modal_content)捕获,改用无名yield则能正常渲染。
调用partial的模板代码:
<%= render 'shared/remote_modal', modal_title: "bru" do %> <%= content_for(:modal_content) do %> <%= render @registrations, show_learner: true, show_product: false %> <% end %> <% end %>
对应的shared/remote_modal partial代码:
<div class="relative px-4 w-full max-w-6xl md:h-auto"> <!-- Modal content --> <div class="relative bg-white rounded-lg shadow dark:bg-gray-700"> <!-- Modal header --> <div class="w-full"> <div class="flex justify-between items-start p-5 rounded-t"> <div class="w-full-0"> <h3 class="text-lg font-semibold text-gray-900 dark:text-white"> <%= modal_title %> </h3> </div> <i class="close-modal fas fa-times text-lg cursor-pointer"></i> </div> </div> <!-- Modal body --> <div class="px-6"> <div class="w-full"> <%= yield(:modal_content) %> </div> </div> </div> </div>
核心原因
Rails的partial不支持直接通过命名yield捕获外部的content_for块。content_for的内容会被存储在全局渲染上下文(对应layout层面的上下文)中,而partial内的yield(:modal_content)只会查找当前partial自身的命名yield定义,无法访问外部模板用content_for设置的内容。无名yield能正常工作是因为它直接接收render块传递的内容,和命名yield的机制完全不同。
解决办法
方法1:局部变量传递内容块
把需要渲染的内容作为局部变量传递给partial,替代命名yield的逻辑:
修改调用模板:
<%= render 'shared/remote_modal', modal_title: "bru", modal_content: render(@registrations, show_learner: true, show_product: false) %>
修改partial代码,直接输出局部变量:
<!-- Modal body --> <div class="px-6"> <div class="w-full"> <%= modal_content %> </div> </div>
如果内容包含复杂ERB逻辑,用capture捕获后传递:
<% modal_content = capture do %> <%= render @registrations, show_learner: true, show_product: false %> <!-- 其他自定义逻辑 --> <% end %> <%= render 'shared/remote_modal', modal_title: "bru", modal_content: modal_content %>
方法2:用无名yield扩展多区域内容
如果partial需要多个自定义区域,可以在render块中传递子块,通过对象方法接收后渲染:
修改调用模板:
<%= render 'shared/remote_modal', modal_title: "bru" do |modal| %> <% modal.content do %> <%= render @registrations, show_learner: true, show_product: false %> <% end %> <% end %>
修改partial代码,定义接收块的对象:
<% content = nil %> <% yield OpenStruct.new( content: ->(&block) { content = capture(&block) } ) %> <!-- Modal body --> <div class="px-6"> <div class="w-full"> <%= content %> </div> </div>
这种方式可扩展支持多个区域(比如modal footer),结构更清晰。
方法3:Rails 7+ 用View Components替代partial(推荐)
Rails 7及以上版本的View Components天然支持多slot(类似命名yield的功能),代码更易维护:
创建组件类app/components/remote_modal_component.rb:
class RemoteModalComponent < ViewComponent::Base def initialize(modal_title:) @modal_title = modal_title end end
创建组件模板app/components/remote_modal_component.html.erb:
<div class="relative px-4 w-full max-w-6xl md:h-auto"> <div class="relative bg-white rounded-lg shadow dark:bg-gray-700"> <div class="w-full"> <div class="flex justify-between items-start p-5 rounded-t"> <div class="w-full-0"> <h3 class="text-lg font-semibold text-gray-900 dark:text-white"> <%= @modal_title %> </h3> </div> <i class="close-modal fas fa-times text-lg cursor-pointer"></i> </div> </div> <div class="px-6"> <div class="w-full"> <%= content %> </div> </div> </div> </div>
调用组件:
<%= render RemoteModalComponent.new(modal_title: "bru") do %> <%= render @registrations, show_learner: true, show_product: false %> <% end %>
如需多slot(比如添加footer),修改组件类:
class RemoteModalComponent < ViewComponent::Base renders_one :content renders_one :footer end
模板中添加slot渲染:
<!-- ... 其他代码 ... --> <div class="px-6"> <div class="w-full"> <%= content %> </div> </div> <% if footer? %> <div class="p-6 border-t"> <%= footer %> </div> <% end %>
调用时传递多slot:
<%= render RemoteModalComponent.new(modal_title: "bru") do |c| %> <% c.content do %> <%= render @registrations, show_learner: true, show_product: false %> <% end %> <% c.footer do %> <button class="btn">确认</button> <% end %> <% end %>
内容的提问来源于stack exchange,提问作者David Geismar

