Skip to content

编写 Tao Component

farthinker edited this page Mar 29, 2017 · 15 revisions

假设我们已经有一个 todos#index 页面,这个页面的内容是一个 Todo 的列表:

<!-- app/views/todos/index.html.erb -->

<%= render @todos %>

现在我们希望将 Todo 抽象为独立的组件,这样在 _todo.html.erb 这个 partial 模版里面,只需要调用 Todo 组件对应的 view helper:

<!-- app/views/todos/_todo.html.erb -->

<%= tao_todo_item todo %>

服务器端

首先,我们需要创建服务器端对应的 Component 类:

# app/components/todos/item_componnent.rb

class Todos::ItemComponent < ApplicationComponent

  attr_reader :todo

  def initialize view, todo, options = {}
    super view, options
    
    @todo = todo
  end

end

这个 Component 类会帮助我们动态定义对应的 view helper 方法,方法的默认命名规则是 Component.tag_name.underscore,而 tag_name 的定义是:

# from TaoOnRails::Components::Base

def self.tag_name
  @tag_name ||= "#{self.tag_prefix}-#{self.component_name.to_s.dasherize}"
end

def self.component_name
  @component_name ||= self.name.underscore.split('/').map(&:singularize).join('_')
    .gsub(/(.+)_component$/, '\1')
    .gsub(/^#{Regexp.quote(self.tag_prefix.to_s.underscore)}_(.+)/, '\1')
end

def self.tag_prefix
  :tao
end

Todos::ItemComponent 对应的 view helper 名称就是 tao_todo_item,其中 tao 这个前缀是可以在 ApplicationComponent 或者 ItemComponent 里面重新定义的,比如:

class Todos::ItemComponent < ApplicationComponent

  ...
  
  def self.tag_prefix
    :awesome
  end

end

Component 类的另一个作用是决定组件的渲染逻辑,默认的渲染逻辑会尝试在 app/views/components/todos/ 文件夹里面查找名称为 _item.html.erb 的 partial 模版,如果找到了就渲染这个模版,如果没有找到就渲染一个默认的 Custom Element 容器,例如:

<tao-todo-item>
  <!-- 如果调用 view helper 的时候提供了block,那么 block 的内容会渲染在这里 -->
</tao-todo-item>

所以现在我们需要在 app/views/components/todos/ 文件夹里面创建 _todo.html.erb 模版,然后在这里面编写 Todo 组件的 HTML:

<!-- app/views/components/todos/_todo.html.erb -->

<tao-todo-item id="todo-item-<%= component.todo.id %>">
  <%= check_box_tag nil, '1', component.todo.completed, class: 'todo-checkbox' %>
  <span class="todo-content"><%= component.todo.content %></span>
<tao-todo-item>

Component 对象的实例会作为 local variable 传入这个 partial,所以模版里面可以使用 component 实例的任何 public attribute/method。假设我们现在有一个产品需求是,Todo 内容里面的 “#XXX#” 格式会定义任务的 Tag,Tag 需要渲染为链接,我们可以通过增加两个 public method 来实现这个需求:

# app/components/todos/item_componnent.rb

class Todos::ItemComponent < ApplicationComponent

  ...

  def tags
    /#(.+?)#/.match(todo.content).to_a
  end

  def content_without_tags
    todo.content.gsub(/#(.+?)#/, '')
  end

end

然后修改 _todo.html.erb

<!-- app/views/components/todos/_todo.html.erb -->

<tao-todo-item id="todo-item-<%= component.todo.id %>">
  <%= check_box_tag nil, '1', component.todo.completed, class: 'todo-checkbox' %>
  <div class="tags">
    <% component.tags.each do |tag| %>
      <%= link_to tag, '#', class: 'tag' %>
    <% end %>
  </div>
  <div class="content"><%= component.content_without_tags %></div>
<tao-todo-item>

客户端

Clone this wiki locally