Rails 稍加改进 render partial ,复用 html 模板更方便了

qichunren · 2022年03月25日 · 最后由 Rei 回复于 2022年03月25日 · 644 次阅读

我最近在整理项目中一些 TailwindCSS 的常用的 HTML 片段,作为 erb partial‘组件’方便复用。在带有多个参数的情况下,默认的 render 方法用起来不是很方便,暂时不想引入 view_component 这类复杂一点的东西,就稍加改进了一下,现在页面中 render 方法用起来更方便了,可以将一些常用的 TailwindCSS HTML 代码复用起来。

Github README: https://github.com/qichunren/view_context#viewcontext

一般情况下,如下面这个 Tab 的 html 模板

# _tab.html.erb
<div class="px-4 flex items-center justify-between border-b-2 border-gray-400">
  <ul class="flex px-4">
    <!-- slot for tab links -->
    <%= yield %>
  </ul>
  <!-- slot for right button -->
  <%= side %>
</div>

使用的时候这样用:

<%= render "shared/tab", side: "<a href='/some' class='btn'></a>".html_safe do  %>
  <li><a href="/some">Tab 1</a></li>
  <li><a href="/some">Tab 2</a></li>
  <li><a href="/some">Tab 3</a></li>
<% end %>

这样用起来一点也不方便。现在稍微改进一点,同时没有带来太多复杂性,也没有带来破坏性。

# _tab.html.erb
<% _view_context = ViewContext.new(self) %>
<div class="px-4 flex items-center justify-between border-b-2 border-gray-400">
    <ul class="tab px-4">
        <%= yield _view_context  %>
        <%= _view_context.main %>
    </ul>
    <%= _view_context.secondary %>
</div>

Then, render tab template partial below:

<%= render "shared/tab" do |context| %>

  <% context.set_main do %>
      <li><a href="/some">Tab 1</a></li>
      <li><a href="/some">Tab 2</a></li>
      <li><a href="/some">Tab 3</a></li>
  <% end %>

  <% context.set_secondary do %>
    <a href='/settings' class='btn'>Settings</a>
  <% end %>

<% end %>

view_context.rb 的代码如下:

class ViewContext
  attr_reader :main, :secondary

  def initialize(action_view)
    @view = action_view
  end

  def set_main(content = nil, &blk)
    @main = if blk
      @view.capture do
        blk.call
      end
    else
      content
    end
    nil
  end

  def set_secondary(content = nil, &blk)
    @secondary = if blk
      @view.capture do
        blk.call
      end
    else
      content
    end
    nil
  end

  def method_missing(name, *args, &blk)
    m = name.match(/^set_([a-z]\w*)/)
    if m
      iv = m[1]
      if blk
        instance_variable_set("@#{iv}", @view.capture { blk.call })
      else
        instance_variable_set("@#{iv}", args.first)
      end
      self.class.class_eval { attr_reader iv.to_sym }
    else
      super
    end
  end
end

只需要把这个 view_context.rb 文件放在 lib 下,并设置config.eager_load_paths << Rails.root.join("lib")就可以了。

看起来这个场景更适合用 viewcomponent,与 viewcomponent 里的slot功效一样。

已经看不懂了,相比起来 viewcomponent 还有文档。

@v2up @Rei viewcomponent 多少文件,多少代码呢?我只是想加强一下 erb partial 里的 yield 分块占位这个基本的固定需求,用不着再加一个轮子啊。我稍微简化了一下文中的示例代码。

其实我偏好的代码是这样:

<div class="tab">
  <a href="#" class="tab__item tab__item--active">
    Tab Item
  </a>
  <a href="#" class="tab__item">
    Tab Item
  </a>
  <a href="#" class="tab__item">
    Tab Item
  </a>
</div>

如果是 tailwindcss 应该用 @apply 整合组件样式。

如果不用 @apply,那就得用上某种组件库,例如后段的 viewcomponent,前端的 vue,react。这是解决一个问题又引入一个问题。我越来越觉得 tailwindcss 不是一个好模范。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号