Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

แรงบันดาลใจจาก React ViewComponents เป็นวัตถุ Ruby ที่ใช้ในการสร้างมาร์กอัปสำหรับการแสดงผลมุมมอง ViewComponent เป็นเฟรมเวิร์กสำหรับการสร้างส่วนประกอบมุมมองที่นำกลับมาใช้ใหม่ ทดสอบได้ และห่อหุ้มไว้ใน Rails โดยทั่วไป มุมมองที่นำกลับมาใช้ใหม่ได้ถูกสร้างขึ้นใน Rails โดยใช้ partials แล้วเรนเดอร์ในมุมมองที่แตกต่างกันตามต้องการ แต่ด้วยการแนะนำ ViewComponent gem บางส่วนสามารถเปลี่ยนเป็นส่วนประกอบมุมมองได้ เนื่องจากมีข้อดีมากกว่า มาดูกันว่าข้อดีเหล่านี้คืออะไร

เมื่อใดและเพราะเหตุใดฉันจึงควรใช้ ViewComponents

ตามที่ระบุไว้ก่อนหน้านี้ ส่วนประกอบมุมมองสามารถใช้ซ้ำได้และทดสอบได้ ดังนั้น จึงสามารถนำไปใช้ได้ทุกเมื่อที่ต้องการใช้ข้อมูลพร็อพเพอร์ตี้ซ้ำ หรือได้รับประโยชน์จากการทดสอบโดยตรง ประโยชน์บางประการขององค์ประกอบมุมมอง ตามที่ระบุไว้ในเอกสารประกอบ มีดังต่อไปนี้:

  • เร็วกว่าบางส่วน ~10 เท่า
  • เป็นวัตถุทับทิม ดังนั้นวิธีการเริ่มต้นของพวกเขาจึงกำหนดสิ่งที่จำเป็นในการแสดงมุมมองอย่างชัดเจน ซึ่งหมายความว่าง่ายต่อการทำความเข้าใจและนำกลับมาใช้ใหม่ในมุมมองอื่นๆ นอกจากนี้ ยังสามารถบังคับใช้มาตรฐานคุณภาพของโค้ด Ruby ได้ ซึ่งจะช่วยลดความเสี่ยงที่จะเกิดข้อผิดพลาด
  • สามารถทดสอบหน่วยได้เมื่อเทียบกับมุมมองแบบเดิมของ Rails ซึ่งต้องมีการทดสอบการผสานรวมที่ใช้เลเยอร์การกำหนดเส้นทางและตัวควบคุมเพิ่มเติมจากมุมมอง

การใช้งาน ViewComponent

ViewComponents เป็นคลาสย่อยของ ViewComponent::Base และอยู่ใน app/components . ชื่อลงท้ายด้วย - Component และควรตั้งชื่อตามสิ่งที่พวกเขาแสดง ไม่ใช่สิ่งที่พวกเขายอมรับ เราสามารถสร้างองค์ประกอบการดูได้ด้วยตนเองหรือผ่านตัวสร้างส่วนประกอบ แต่ไม่ใช่โดยไม่ต้องเพิ่ม gem ลงใน gemfile ของเราและเรียกใช้ bundle install ก่อน

gem "view_component", require: "view_component/engine"

ในการใช้ตัวสร้าง ให้รันคำสั่งต่อไปนี้:

rails generate component <Component name> <arguments needed for initialization>

ตัวอย่างเช่น หากเราต้องการสร้างองค์ประกอบที่รับผิดชอบในการแสดงรายการหลักสูตรในชั้นเรียนที่พร้อมใช้งานบนแพลตฟอร์มการเรียนรู้ เราสามารถใช้คำสั่งนี้:

rails g component Course course

เรากำลังผ่าน course อาร์กิวเมนต์ของคำสั่งเพราะเราจะเริ่มต้นวัตถุ Ruby นี้ด้วยหลักสูตรที่เราคาดหวังให้แสดงและเราตั้งชื่อว่า Course เพราะมันทำให้หลักสูตร ช่างเป็นเรื่องบังเอิญจริงๆ!

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ดังที่เราเห็นข้างต้น ส่วนประกอบและมุมมองที่เกี่ยวข้องจะถูกสร้างขึ้นใน app/components โฟลเดอร์พร้อมกับไฟล์ทดสอบ

ViewComponent มีตัวสร้างเทมเพลตสำหรับ erb , haml และ slim เอ็นจิ้นเทมเพลต แต่จะตั้งค่าเริ่มต้นเป็นเอ็นจิ้นเทมเพลตใดก็ตามที่ระบุไว้ใน config.generators.template_engine .อย่างไรก็ตาม คุณสามารถระบุเครื่องมือเทมเพลตที่คุณต้องการได้โดยใช้สิ่งต่อไปนี้:

rails generate component Course course --template-engine <your template engine>

มาดำเนินการสร้างโมเดลหลักสูตรของเราและบางหลักสูตรที่จะแสดง

rails g model Course title:string price:decimal location:string
rails db:migrate

ในคอนโซลของเรา เราสามารถสร้างหลักสูตรใหม่สองหลักสูตรได้อย่างรวดเร็ว:

Course.create(title: 'The Art of Learning', price: 125.00, location: 'Denmark')
Course.create(title: 'Organizing your Time', price: 55.00, location: 'London')

course_component.rb ไฟล์ถูกสร้างขึ้นด้วยวิธีการเริ่มต้นตามที่แสดงด้านล่าง

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

เราจำเป็นต้องสร้างผู้ควบคุมหลักสูตรที่นำเราไปยังรายการหลักสูตร

rails g controller Courses index

ใน routes.rb . ของเรา เราระบุเส้นทางรูทของเราโดยเพิ่มสิ่งต่อไปนี้:

root 'courses#index'

เมื่อพร้อมแล้ว ขั้นตอนต่อไปคือการสร้างมุมมอง สิ่งนี้ทำใน course_component.html.erb . ที่สร้างขึ้นแล้ว .

<div>
  <h2><%= @course.title %></h2>
  <h4><%=  number_to_currency(@course.price, :unit => "€") %></h4>
  <h4><%= @course.location %></h4>
</div>

ในมุมมองของเรา เราแสดงชื่อหลักสูตร ราคา และสถานที่โดยใช้ @course ซึ่งถูกกำหนดไว้แล้วในวิธีการเริ่มต้นของส่วนประกอบหลักสูตรของเรา ซึ่งคล้ายกับเมื่อคุณสร้างตัวแปรในวิธีการควบคุม และจากนั้นจะพร้อมใช้งานในมุมมอง

เมื่อรู้ว่าตัวควบคุมทำงานอย่างไร เราจะถูกส่งไปยัง index.html.erb . ที่เกี่ยวข้อง ของวิธีดัชนีของเรา ดังนั้นนี่คือที่ที่เราแสดงองค์ประกอบของเรา

#app/views/courses/index.html.erb
<%= render(CourseComponent.new(course: Course.find(1))) %>

ตามที่เห็นด้านบน เราสร้างอินสแตนซ์ใหม่ของส่วนประกอบหลักสูตรของเราโดยเริ่มต้นกับหลักสูตรที่เราตั้งใจให้แสดงผลในมุมมอง หลักสูตรนี้กลายเป็น @course ตัวแปรที่เปิดให้ course_component.html.erb ไฟล์.

นอกจากนี้ยังสามารถแสดงส่วนประกอบนี้โดยตรงจากตัวควบคุมของเรา ดังนั้นจึงข้ามมุมมองไฟล์ดัชนี:

class CoursesController < ApplicationController
  def index
    render(CourseComponent.new(course: Course.find(1)))
  end
end

ไม่ว่าคุณจะเลือกวิธีใด สิ่งนี้จะแสดงบนเซิร์ฟเวอร์:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

เนื้อหาเพิ่มเติมสามารถส่งต่อไปยังส่วนประกอบด้วยวิธีใดวิธีหนึ่งดังต่อไปนี้:

<%= render(CourseComponent.new(course: Course.find(1))) do %>
  container
<% end %>
<%= render(CourseComponent.new(course: Course.find(1)).with_content("container")) %>

ในไฟล์องค์ประกอบมุมมอง เราสามารถรวมเนื้อหาได้ทุกที่ที่เราต้องการ ในกรณีนี้ เราจะรวมเป็นคลาสโดยแก้ไข div ของเราให้เป็นดังนี้:

<div class=<%= content %>>

วิธีการทั้งหมดข้างต้นในการแสดงเนื้อหาเพิ่มเติมจะให้ภาพด้านล่าง:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

การแสดงผลคอลเล็กชัน

จะเป็นอย่างไรถ้าเราต้องการแสดงรายการหลักสูตรทั้งหมดของเรา ViewComponent นำเสนอวิธีการตรงไปตรงมาโดยใช้ with_collection แท็ก แทนที่จะเริ่มต้นส่วนประกอบโดยใช้ .new มันถูกเริ่มต้นโดยใช้ .with_collection และคอลเล็กชันจะถูกส่งต่อเป็นตัวแปรดังที่แสดงด้านล่าง

CourseComponent.with_collection(Course.all)

ได้ผลลัพธ์ดังนี้:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

นอกจากนี้ยังมี with_collection_parameter แท็กใช้ได้ในกรณีที่เราต้องการระบุชื่อคอลเลกชันโดยใช้ชื่ออื่น

class CourseComponent < ViewComponent::Base
  with_collection_parameter :item

  def initialize(item:)
    @item = item
  end
end

ในกรณีข้างต้น พารามิเตอร์ของหลักสูตรถูกกำหนดเป็น item . ดังนั้น ในมุมมองที่เกี่ยวข้อง @course จะถูกแทนที่ด้วย @item ให้ได้ผลเช่นเดียวกัน

นอกจากนี้ยังเพิ่มพารามิเตอร์เพิ่มเติมในคอลเล็กชันได้อีกด้วย พารามิเตอร์เหล่านี้จะแสดงต่อรายการในคอลเลกชัน มาเพิ่ม Buy Me . กันเถอะ ส่งข้อความถึงแต่ละรายการด้วยวิธีนี้

#app/views/courses/index.html.erb
<%= render(CourseComponent.with_collection(Course.all, notice: "Buy Me")) %>
# app/components/course_component.rb
class CourseComponent < ViewComponent::Base
  with_collection_parameter :item
  def initialize(item:, notice:)
    @item = item
    @notice = notice
  end
end

เราเพิ่มย่อหน้าใหม่ใน app/components/course_component.html.erb ไฟล์เพื่อระบุข้อความสำหรับตัวแปรประกาศที่เพิ่งเพิ่มเข้ามา

<p><a href='#'> <%= @notice %> </a></p>

ได้ผลลัพธ์ดังนี้:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

สุดท้าย ภายใต้คอลเลกชัน เรามีตัวแปรตัวนับที่สามารถเปิดใช้งานเพื่อกำหนดหมายเลขรายการในมุมมอง เปิดใช้งานโดยการเพิ่ม _counter กับพารามิเตอร์คอลเลกชันและทำให้สามารถดูได้โดยใช้วิธีการเริ่มต้น

#app/components/course_component.rb
def initialize(item:, notice:, item_counter:)
  @item = item
  @notice = notice
  @counter = item_counter
end

ในมุมมองของเรา ข้างชื่อรายการ เราเพิ่มตัวนับของเรา:

<h2><%= @counter %>. <%= @item.title %></h2>

มาสร้างหลักสูตรที่สามจากคอนโซลเพื่อทำความเข้าใจปรากฏการณ์ตอบโต้กันดีกว่า

Course.create(title: 'Understanding Databases', price: '100', location: 'Amsterdam')

ได้ผลลัพธ์ดังนี้

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

การแสดงผลแบบมีเงื่อนไข

ViewComponent มี render? hook ซึ่งเมื่อใช้แล้ว จะกำหนดว่าควรแสดงผลมุมมองหรือไม่ ในการดำเนินการนี้ เราจะให้ส่วนลด 10% สำหรับหลักสูตรที่มีราคาเท่ากับหรือมากกว่า 100 ยูโร มาสร้างองค์ประกอบเพื่อการนี้กันเถอะ

rails generate component Discount item

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

คอมโพเนนต์นี้ได้รับการเริ่มต้นโดยอัตโนมัติแล้วด้วยรายการที่ควรแสดงส่วนลดดังที่แสดงด้านล่าง

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ดังนั้น ใน discount_component.html.erb เราเพิ่มข้อความที่เราตั้งใจจะแสดง

<p class="green"> A 10% discount is available on this course </p>

อย่าลังเลที่จะเพิ่มคลาส green ไปยังไฟล์ css ของคุณและกำหนดเฉดสีเขียวที่คุณต้องการ นอกจากนี้ ใน discount_component.rb . ของเรา เราเพิ่ม render? วิธีการกำหนดว่าเมื่อใดควรแสดงผลองค์ประกอบนี้

def render?
  @item.price >= 100
end

ตอนนี้ เราสามารถแสดงองค์ประกอบส่วนลดภายในมุมมองที่แสดงแต่ละหลักสูตรได้

# app/components/course_component.html.erb
<%= render(DiscountComponent.new(item: @item)) %>

ได้ผลลัพธ์ดังนี้:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

มันไม่เจ๋งเหรอ?

ตัวช่วย

ในมุมมอง Rails แบบเดิม เราสามารถเสียบตัวช่วยของเราได้ง่ายๆ โดยเรียกชื่อเมธอดในมุมมองของเรา แต่จะทำงานแตกต่างไปจากองค์ประกอบการดู ในคอมโพเนนต์มุมมอง ไม่สามารถเรียกเมธอดตัวช่วยได้โดยตรงในมุมมอง แต่สามารถรวมไว้ในคอมโพเนนต์ได้ เรามี courses_helper.rb . แล้ว ไฟล์ที่สร้างขึ้นโดยอัตโนมัติเมื่อ CoursesController ถูกสร้างขึ้น ดังนั้นเรามาใช้ประโยชน์จากมันกันเถอะ ขั้นแรก ให้สร้างวิธีการช่วยเหลือที่บอกเราว่ามีผู้ลงทะเบียนเรียนในหลักสูตรหนึ่งๆ กี่คนแล้ว มาทำให้ค่าหนึ่งในสี่ของราคากัน :)

module CoursesHelper
  def count_enrollees(course)
    count = (course.price / 4).round()
    tag.p "#{count} enrollees so far"
  end
end

ต่อไป เราจะสร้างส่วนประกอบที่เราจะเรียกตัวช่วย นี่คือองค์ประกอบที่จะแสดงในมุมมองของเรา ในนั้นเราจะเพิ่ม include คำสั่ง รวมทั้งตัวช่วย จากนั้นเราสามารถเรียกวิธีการใด ๆ ในตัวช่วยภายในองค์ประกอบนี้

# app/components/enrollee_component.rb
class EnrolleeComponent < ViewComponent::Base
include CoursesHelper

  def total_enrollees(course)
    count_enrollees(course)
  end
end

ขั้นตอนสุดท้ายคือการเพิ่ม EnrolleeComponent ในมุมมองที่แสดงหลักสูตรของเรา

# app/components/course_component.html.erb
<%= EnrolleeComponent.new.total_enrollees(@item) %>

โปรดทราบว่าเราไม่ได้ใช้คำแสดงผลสำหรับ EnrolleeComponent เนื่องจากไม่มีมุมมอง และผลลัพธ์จะเป็นของเมธอด Helper ที่เรียกว่า ได้ผลลัพธ์ดังนี้:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

สามารถใช้ Helpers สำหรับไอคอน Gravitar หรืออะไรก็ได้ที่คุณอาจเลือก ViewComponent จะไม่เปลี่ยนการใช้ตัวช่วย มันแค่เปลี่ยนวิธีที่เราเรียกพวกมันในส่วนประกอบของเรา

วิธี before_render

ViewComponent เสนอ before_render เมธอดที่สามารถเรียกได้ก่อนที่จะแสดงผลคอมโพเนนต์ มาเพิ่มดาวข้างประกาศส่วนลดของเรากันเถอะ เราเริ่มต้นด้วยการเพิ่มวิธีการช่วยเหลือที่ดึงดาว a star.png รูปภาพยังถูกดาวน์โหลดและวางไว้ใน app/assets/images โฟลเดอร์

#app/helpers/courses_helper.rb
def star_icon
  image_tag("/assets/star.png", width: "1%")
end

มาเพิ่ม before_render . กันเถอะ วิธีการไปยังองค์ประกอบส่วนลดของเราที่เรียกวิธีการช่วยเหลือนี้

# app/components/discount_component.rb
def before_render
  @star_icon = helpers.star_icon
end

ดังที่เราเห็นข้างต้น มีการแนะนำวิธีการเรียกผู้ช่วยอีกแบบหนึ่ง เรียก Helper ได้โดยใช้ helpers.method ในไฟล์ component.rb จากนั้น ในวิธีการเรนเดอร์ของเราสำหรับ DiscountComponent เราป้อนไอคอนรูปดาวของเรา ซึ่งขณะนี้มีให้บริการผ่าน @star_icon ตัวแปร

# app/components/discount_component.html.erb
<p class="green"> <%= @star_icon %> A 10% discount is available on this course </p>

ได้ผลลัพธ์ดังนี้:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

เราไม่จำเป็นต้องใช้ตัวช่วยสำหรับ before_render วิธีการทำงาน ฉันได้ใช้ตัวช่วยเพื่อแนะนำวิธีการเรียกวิธีการตัวช่วยอื่น

ตัวอย่าง

เช่นเดียวกับ Action Mailer ViewComponent ทำให้สามารถดูตัวอย่างส่วนประกอบได้ ต้องเปิดใช้งานสิ่งนี้ก่อนใน config/application.rb ไฟล์.

config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"

ส่วนประกอบการแสดงตัวอย่างอยู่ใน test/components/previews และสามารถใช้เพื่อดูตัวอย่างส่วนประกอบที่มีอินพุตที่แตกต่างกันหลายรายการ เราจะแสดงตัวอย่าง DiscountComponent ของเรา

# test/components/previews/discount_component_preview.rb
class DiscountComponentPreview < ViewComponent::Preview
  def with_first_course
    render(DiscountComponent.new(item: Course.find(1)))
  end

  def with_second_course
    render(DiscountComponent.new(item: Course.find(2)))
  end
end

เพิ่มวิธีการสองวิธีเพื่อแสดงตัวอย่าง DiscountComponent ในสถานการณ์ต่างๆ หากต้องการดูสิ่งนี้ ไปที่ https://localhost:3000/rails/view_components ที่เราพบส่วนประกอบการแสดงตัวอย่างทั้งหมดที่สร้างขึ้นและวิธีการ เราสามารถคลิกที่ใดก็ได้เพื่อดูว่ามีลักษณะอย่างไรดังที่แสดงด้านล่าง

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ดังที่คุณเห็นในวิดีโอด้านบน หลักสูตรแรกแสดงองค์ประกอบส่วนลด แต่จะไม่มีการเรนเดอร์สำหรับหลักสูตรที่สอง คุณรู้ไหมว่าทำไมสิ่งนี้ถึงเกิดขึ้น? render? การตรวจสอบวิธีการเกิดขึ้น สถานการณ์แรกคือเมื่อราคาต้นทุนมากกว่า 100 ยูโร (หลักสูตรที่ 1) แต่ค่าใช้จ่ายน้อยกว่า 100 ยูโรสำหรับหลักสูตรที่สอง ชื่อเมธอดไม่ได้สื่อความหมายมากนัก เพื่อให้คุณทราบสาเหตุก่อนที่จะเน้นที่นี่ :)

เปิดหรือปิดการแสดงตัวอย่างในสภาพแวดล้อมใดก็ได้โดยใช้ show_previews แต่ในสภาพแวดล้อมการพัฒนาและการทดสอบ จะถูกเปิดใช้งานโดยค่าเริ่มต้น

# config/environments/test.rb
config.view_component.show_previews = false

การรวม JS และ CSS

เป็นไปได้ที่จะรวม JavaScript และ CSS ควบคู่ไปกับส่วนประกอบ ซึ่งบางครั้งเรียกว่าเนื้อหาหรือไฟล์ "ไซด์คาร์" นี่ยังคงเป็นคุณลักษณะทดลอง ดังนั้น เราจะไม่เจาะลึกถึงการทำงานภายในในบทความนี้ แต่คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับคุณลักษณะ ViewComponent ได้ที่นี่

แม่แบบ

เทมเพลตขององค์ประกอบมุมมองสามารถกำหนดได้หลายวิธี ตัวเลือกที่ง่ายกว่าคือการแทรกมุมมองและองค์ประกอบในโฟลเดอร์เดียวกันดังที่แสดงด้านล่าง

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ดังที่เราเห็นที่นี่ เรามีทุกองค์ประกอบและดูใน app/components โฟลเดอร์

อีกทางเลือกหนึ่งคือการวางข้อมูลพร็อพเพอร์ตี้และเนื้อหาอื่นๆ ในไดเร็กทอรีย่อยที่มีชื่อเดียวกับคอมโพเนนต์ ดังนั้นใน app/components โฟลเดอร์ เรามี component.rb ไฟล์ซึ่งเก็บองค์ประกอบแล้วแยก course_component โฟลเดอร์ซึ่งมีมุมมอง course_component.html.erb และทรัพย์สินอื่นๆ ทั้งหมดที่เกี่ยวข้องกับ Course_component

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ในการสร้างไฟล์ส่วนประกอบด้วยวิธีนี้จากบรรทัดคำสั่ง --sidecar จำเป็นต้องมีแฟล็ก:

rails g component Example name  --sidecar

ซึ่งจะทำให้คุณสามารถเพิ่มไฟล์ css และ js ลงในโฟลเดอร์คอมโพเนนต์ได้ ViewComponents ยังสามารถแสดงผลโดยไม่มีไฟล์เทมเพลตได้ด้วยการกำหนด call กระบวนการ. ตัวอย่างนี้มีให้ในหัวข้อถัดไป ซึ่งเราจะพูดถึง สล็อต .

สล็อต

สามารถส่งผ่านเนื้อหาหลายบล็อกไปยัง ViewComponent เดียวได้โดยใช้ สล็อต . คล้ายกับ has_one และ has_many คุณลักษณะในโมเดล Rails สล็อตถูกกำหนดด้วย renders_one และ renders_many :

  • renders_one กำหนดช่องที่จะแสดงผลสูงสุดหนึ่งครั้งต่อองค์ประกอบ:renders_one :header.
  • renders_many กำหนดช่องที่สามารถแสดงผลได้หลายครั้งต่อองค์ประกอบ:renders_many :titles.

ลองนึกภาพว่าเราต้องการมีหน้าเว็บที่แสดงส่วนหัวและชื่อหลักสูตรทั้งหมดที่เรามี สามารถทำได้โดยใช้ สล็อต . มาสร้าง ListComponent . กันเถอะ ซึ่งจะมีส่วนหัวที่แสดงผลเพียงครั้งเดียว และ TitleComponent ซึ่งทำให้มีชื่อเรื่องมากมาย

#app/components/list_component.rb
class ListComponent < ViewComponent::Base
  renders_one :header, "HeaderComponent"
  # `HeaderComponent` is defined within this component, so we refer to it using a string.
  renders_many :titles, TitleComponent
  # `titleComponent` will be defined in another file, so we can refer to it by class name.
  class HeaderComponent < ViewComponent::Base
    def call
      content_tag :h1, content
    end
  end
end

ในองค์ประกอบด้านบน เราระบุว่าส่วนหัวมีการแสดงผลเพียงครั้งเดียว แต่มีการเรนเดอร์ชื่อหลายครั้ง เนื่องจากหน้านี้จะมีหลายชื่อ เราได้กำหนด HeaderComponent ภายใน ListComponent ด้วย ใช่ สามารถทำได้ด้วย ViewComponent; คลาสสามารถกำหนดได้ภายในคลาสอื่น นอกจากนี้ ให้สังเกตวิธีการโทรที่กล่าวถึงก่อนหน้านี้ภายใต้ส่วนเทมเพลตและวิธีการใช้ใน HeaderComponent เพื่อแสดงแท็ก h1 ซึ่งจะช่วยลดความจำเป็นในมุมมองที่เกี่ยวข้อง (ไฟล์ html.erb) ไฟล์ HTML ที่สอดคล้องกันสำหรับ ListComponent จะมีดังต่อไปนี้

#app/components/list_component.html.erb
<div>
  <%= header %> <!-- renders the header component -->
  <% titles.each do |title| %>
    <div>
      <%= title %> <!-- renders an individual course title -->
    </div>
  <% end %>
</div>

ในไฟล์ html เราได้รวมส่วนหัว ทำซ้ำผ่านชื่อทั้งหมดที่ส่งผ่านไปยังส่วนประกอบ และแสดงผล อย่างที่คุณเห็น เราไม่จำเป็นต้องระบุชื่อของส่วนประกอบที่จะแสดงผลในไฟล์มุมมองรายการ สล็อตของเราได้ดูแลเรื่องนั้นแล้ว ดังนั้นเราจึงระบุเป็น header และ title .

ขั้นตอนต่อไปคือการสร้าง TitleComponent และไฟล์ HTML ที่เกี่ยวข้อง เนื่องจากนี่คือสิ่งที่จะแสดงผลสำหรับทุกๆ ชื่อที่ส่งผ่าน

# app/components/title_component.rb
class TitleComponent < ViewComponent::Base
  def initialize(title:)
    @title = title
  end
end
# app/components/title_component.html.erb
<div>
  <h3> <%= @title %> </h3>
</div>

สุดท้ายใน index.html . ของเรา ให้ลบสิ่งที่เรามีชั่วคราวและแทนที่ด้วยการแสดงผล ListComponent

#app/views/courses/index.html.erb
<%= render ListComponent.new do |c| %>
  <% c.header do %>
  <%= link_to "List of Available Courses", root_path %>
  <% end %>
  <%= c.title(title: "First title") %>
  <%= c.title(title: "Second title!") %>
<% end %>

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ตอนนี้ มาส่งหลักสูตรของเราในมุมมองนี้เป็นคอลเลกชัน ในการส่งคอลเล็กชันไปยังสล็อต เราต้องส่งคอลเล็กชันเป็นอาร์เรย์ของอ็อบเจ็กต์ที่มีตัวแปรที่จำเป็นสำหรับการเริ่มต้น อย่างที่เราเห็น ทุกหลักสูตรที่ผ่านไปควรเริ่มต้นด้วยอาร์กิวเมนต์ชื่อ เราสามารถแยกชื่อหลักสูตรทั้งหมดในฐานข้อมูลของเราออกเป็นอาร์เรย์ของแฮชและแสดงผลได้

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ตอนนี้เราสามารถแทนที่รายชื่อหนังสือหลายเล่มด้วย c.titles . เดียว สำหรับคอลเล็กชันและส่งต่อไปยังแฮชของชื่อ ซึ่งเรากำหนดโดยใช้ตัวแปร course_titles .

# app/views/courses/index.html.erb
<% course_titles = Course.all.pluck(:title).map { |title| {title: title}} %>

<% c.titles(course_titles) %>

ได้ผลลัพธ์ดังนี้:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

นี่เป็นวิธีการทำงานของสล็อตอย่างแท้จริง:การแสดงองค์ประกอบหลายอย่างภายใน ViewComponent เดียว สล็อตสามารถแสดงผลได้หลายวิธี และคุณสามารถหาข้อมูลเพิ่มเติมได้ที่นี่

กำลังทดสอบ

คอมโพเนนต์ของมุมมองการทดสอบทำได้โดยใช้ "view_component/test_case" ในไฟล์ทดสอบและใช้ render_inline ตัวช่วยทดสอบเพื่อให้มีการยืนยันกับเอาต์พุตที่แสดงผล เริ่มต้นด้วยการทดสอบ DiscountComponent

require "test_helper"
require "view_component/test_case"

class DiscountComponentTest < ViewComponent::TestCase
  def test_render_component
    render_inline(DiscountComponent.new(item: "my item"))
  end
end

เมื่อเรารันการทดสอบนี้โดยใช้คำสั่ง rails test test/components/discount_component_test.rb เราได้รับข้อผิดพลาดดังต่อไปนี้:

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

สิ่งนี้พิสูจน์ให้เราเห็นว่าการทดสอบกำลังเข้าสู่องค์ประกอบที่ถูกต้อง แต่ไม่มีอุปกรณ์ประกอบฉากที่เหมาะสม เนื่องจากรายการดังกล่าวจะต้องเป็นหลักสูตรที่มีคุณสมบัติด้านราคาและไม่ใช่สตริงในขณะที่เราผ่าน นอกจากนี้ยังบอกเราว่ามี render? เมธอดถูกตรวจสอบก่อนที่คอมโพเนนต์นี้จะแสดงผล มาส่งตัวแปรที่ถูกต้องกันตอนนี้เลย

def test_render_component
  course = Course.create(title: 'Organizing your Time', price: 55.00, location: 'London')
  render_inline(DiscountComponent.new(item: course))
end

สิ่งนี้ทำงานสำเร็จ มาดำเนินการเพิ่มการยืนยันในการทดสอบนี้กัน

def test_render_component
  course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London')
  render_inline(DiscountComponent.new(item: course))
  assert_selector 'p[class="green"]'
  assert_text "10% discount"
end

การทดสอบนี้ผ่านเช่นกัน จำได้ว่ามีเงื่อนไขการเรนเดอร์สำหรับส่วนประกอบนี้ ไม่ต้องกังวลเพราะ ViewComponent ยังมีวิธีทดสอบว่าคอมโพเนนต์ไม่ได้แสดงผลโดยใช้ refute_component_rendered . เราสามารถทดสอบได้โดยใช้หลักสูตรที่มีราคาต่ำกว่า 100 ยูโร

def test_component_not_rendered
  course = Course.create(title: 'Organizing your Time', price: 55.00, location: 'London')
  render_inline(DiscountComponent.new(item: course))
  refute_component_rendered
end

การทดสอบนี้ก็ผ่านเช่นกัน

มาเขียนการทดสอบอีกครั้งสำหรับ CourseComponent ของเราเพื่อทดสอบว่าจะแสดงส่วนประกอบทั้งหมดซ้อนอยู่ภายในหรือไม่

require "test_helper"
require "view_component/test_case"

class CourseComponentTest < ViewComponent::TestCase
  def test_component_renders_all_children
    course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London')
    render_inline(CourseComponent.new(item: course, notice: 'A new test', item_counter: 1))
    assert_selector("h2", text: "Organizing your Time")
    assert_selector("h4", text: "€155.00")
    assert_selector("h4", text: "London")
    assert_text("enrollees")
    assert_text("discount")
  end
end

การทดสอบนี้ยังผ่าน เป็นการทดสอบว่าองค์ประกอบผู้ลงทะเบียนและส่วนลดนั้นแสดงผลอย่างถูกต้องเช่นกัน

จำได้ว่าเรามีองค์ประกอบของสล็อต และดังที่แสดงในภาพด้านล่าง มันแสดงส่วนหัวหนึ่งรายการและหลายชื่อ

ข้อมูลเบื้องต้นเกี่ยวกับ ViewComponent Gem

ในการทดสอบนี้ เราส่งบล็อกของโค้ดที่มีส่วนหัวและชื่อที่ควรจะแสดงผล จากนั้นเราสามารถยืนยันกับองค์ประกอบที่แสดงผลได้

require "test_helper"
require "view_component/test_case"

class ListComponentTest < ViewComponent::TestCase
  def test_renders_slots_with_content
  render_inline(ListComponent.new) do |component|
  component.header { "A Test List" }
  component.titles [{title: 'Test title 1'}, {title: 'Test title 2'}]
  end

  assert_selector("h1", text: "A Test List")

  assert_text("Test title 1")
  assert_text("Test title 2")
  end
end

การทดสอบนี้ก็ผ่านเช่นกัน :).

การทดสอบ Rspec

นอกเหนือจากทั้งหมดที่กล่าวมาข้างต้นเกี่ยวกับการทดสอบ หากเฟรมเวิร์กการทดสอบที่ต้องการคือ RSpec จะต้องดำเนินการกำหนดค่าเพิ่มเติมบางอย่างเพื่อเปิดใช้งาน RSpec สำหรับ ViewComponents

# spec/rails_helper.rb
require "view_component/test_helpers"
require "capybara/rspec"

RSpec.configure do |config|
  config.include ViewComponent::TestHelpers, type: :component
  config.include Capybara::RSpecMatchers, type: :component
end

การทดสอบ DiscountComponent ของเราสามารถเขียนใหม่และทดสอบซ้ำได้โดยใช้ Rspec ดังที่แสดงด้านล่าง:

require "rails_helper"

RSpec.describe DiscountComponent, type: :component do

  it "renders the component correctly" do
    course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London')
    render_inline(DiscountComponent.new(item: course))
    expect(rendered_component).to have_css "p[class='green']", text: "10% discount"
    expect(rendered_component).to have_css "img[src*='/assets/star.png']"
  end
end

การทดสอบนี้ผ่านไปอย่างสง่างาม ใช่แล้ว เราเห็นไอคอนรูปดาวของเรา

บทสรุป

การเขียนองค์ประกอบมุมมองหลายส่วนสำหรับแอป Rails ของคุณไม่เพียงแต่ทำให้โค้ดของคุณอ่านง่ายขึ้นและมีโอกาสเกิดข้อผิดพลาดน้อยลงจากความยุ่งยากที่ไม่ต้องการ แต่ยังทำให้สามารถทดสอบมุมมองของคุณแบบแยกส่วน และไม่เพียงแต่ระหว่างคำขอ HTTP ส่วนประกอบการดูนั้นง่ายต่อการรวมเข้ากับแอพ Rails ที่มีอยู่ และเป็นการดีที่สุดที่จะเริ่มต้นด้วยมุมมองที่ส่วนใหญ่นำกลับมาใช้ใหม่ ด้วยทุกสิ่งที่เรียนรู้มาจนถึงตอนนี้ สิ่งนี้ควรเป็นงานที่ง่าย อย่างไรก็ตาม หากคุณต้องการข้อมูลเพิ่มเติมเกี่ยวกับ ViewComponent โปรดอ่านเอกสารประกอบหรือข้อมูล RubyDoc