แรงบันดาลใจจาก 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
เพราะมันทำให้หลักสูตร ช่างเป็นเรื่องบังเอิญจริงๆ!
ดังที่เราเห็นข้างต้น ส่วนประกอบและมุมมองที่เกี่ยวข้องจะถูกสร้างขึ้นใน 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
ไฟล์ถูกสร้างขึ้นด้วยวิธีการเริ่มต้นตามที่แสดงด้านล่าง
เราจำเป็นต้องสร้างผู้ควบคุมหลักสูตรที่นำเราไปยังรายการหลักสูตร
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
ไม่ว่าคุณจะเลือกวิธีใด สิ่งนี้จะแสดงบนเซิร์ฟเวอร์:
เนื้อหาเพิ่มเติมสามารถส่งต่อไปยังส่วนประกอบด้วยวิธีใดวิธีหนึ่งดังต่อไปนี้:
<%= 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 นำเสนอวิธีการตรงไปตรงมาโดยใช้ with_collection
แท็ก แทนที่จะเริ่มต้นส่วนประกอบโดยใช้ .new
มันถูกเริ่มต้นโดยใช้ .with_collection
และคอลเล็กชันจะถูกส่งต่อเป็นตัวแปรดังที่แสดงด้านล่าง
CourseComponent.with_collection(Course.all)
ได้ผลลัพธ์ดังนี้:
นอกจากนี้ยังมี 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>
ได้ผลลัพธ์ดังนี้:
สุดท้าย ภายใต้คอลเลกชัน เรามีตัวแปรตัวนับที่สามารถเปิดใช้งานเพื่อกำหนดหมายเลขรายการในมุมมอง เปิดใช้งานโดยการเพิ่ม _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 มี render?
hook ซึ่งเมื่อใช้แล้ว จะกำหนดว่าควรแสดงผลมุมมองหรือไม่ ในการดำเนินการนี้ เราจะให้ส่วนลด 10% สำหรับหลักสูตรที่มีราคาเท่ากับหรือมากกว่า 100 ยูโร มาสร้างองค์ประกอบเพื่อการนี้กันเถอะ
rails generate component Discount item
คอมโพเนนต์นี้ได้รับการเริ่มต้นโดยอัตโนมัติแล้วด้วยรายการที่ควรแสดงส่วนลดดังที่แสดงด้านล่าง
ดังนั้น ใน 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)) %>
ได้ผลลัพธ์ดังนี้:
มันไม่เจ๋งเหรอ?
ตัวช่วย
ในมุมมอง 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 ที่เรียกว่า ได้ผลลัพธ์ดังนี้:
สามารถใช้ 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>
ได้ผลลัพธ์ดังนี้:
เราไม่จำเป็นต้องใช้ตัวช่วยสำหรับ 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
ที่เราพบส่วนประกอบการแสดงตัวอย่างทั้งหมดที่สร้างขึ้นและวิธีการ เราสามารถคลิกที่ใดก็ได้เพื่อดูว่ามีลักษณะอย่างไรดังที่แสดงด้านล่าง
ดังที่คุณเห็นในวิดีโอด้านบน หลักสูตรแรกแสดงองค์ประกอบส่วนลด แต่จะไม่มีการเรนเดอร์สำหรับหลักสูตรที่สอง คุณรู้ไหมว่าทำไมสิ่งนี้ถึงเกิดขึ้น? render?
การตรวจสอบวิธีการเกิดขึ้น สถานการณ์แรกคือเมื่อราคาต้นทุนมากกว่า 100 ยูโร (หลักสูตรที่ 1) แต่ค่าใช้จ่ายน้อยกว่า 100 ยูโรสำหรับหลักสูตรที่สอง ชื่อเมธอดไม่ได้สื่อความหมายมากนัก เพื่อให้คุณทราบสาเหตุก่อนที่จะเน้นที่นี่ :)
เปิดหรือปิดการแสดงตัวอย่างในสภาพแวดล้อมใดก็ได้โดยใช้ show_previews
แต่ในสภาพแวดล้อมการพัฒนาและการทดสอบ จะถูกเปิดใช้งานโดยค่าเริ่มต้น
# config/environments/test.rb
config.view_component.show_previews = false
การรวม JS และ CSS
เป็นไปได้ที่จะรวม JavaScript และ CSS ควบคู่ไปกับส่วนประกอบ ซึ่งบางครั้งเรียกว่าเนื้อหาหรือไฟล์ "ไซด์คาร์" นี่ยังคงเป็นคุณลักษณะทดลอง ดังนั้น เราจะไม่เจาะลึกถึงการทำงานภายในในบทความนี้ แต่คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับคุณลักษณะ ViewComponent ได้ที่นี่
แม่แบบ
เทมเพลตขององค์ประกอบมุมมองสามารถกำหนดได้หลายวิธี ตัวเลือกที่ง่ายกว่าคือการแทรกมุมมองและองค์ประกอบในโฟลเดอร์เดียวกันดังที่แสดงด้านล่าง
ดังที่เราเห็นที่นี่ เรามีทุกองค์ประกอบและดูใน app/components
โฟลเดอร์
อีกทางเลือกหนึ่งคือการวางข้อมูลพร็อพเพอร์ตี้และเนื้อหาอื่นๆ ในไดเร็กทอรีย่อยที่มีชื่อเดียวกับคอมโพเนนต์ ดังนั้นใน app/components
โฟลเดอร์ เรามี component.rb
ไฟล์ซึ่งเก็บองค์ประกอบแล้วแยก course_component
โฟลเดอร์ซึ่งมีมุมมอง course_component.html.erb
และทรัพย์สินอื่นๆ ทั้งหมดที่เกี่ยวข้องกับ Course_component
ในการสร้างไฟล์ส่วนประกอบด้วยวิธีนี้จากบรรทัดคำสั่ง --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 %>
ตอนนี้ มาส่งหลักสูตรของเราในมุมมองนี้เป็นคอลเลกชัน ในการส่งคอลเล็กชันไปยังสล็อต เราต้องส่งคอลเล็กชันเป็นอาร์เรย์ของอ็อบเจ็กต์ที่มีตัวแปรที่จำเป็นสำหรับการเริ่มต้น อย่างที่เราเห็น ทุกหลักสูตรที่ผ่านไปควรเริ่มต้นด้วยอาร์กิวเมนต์ชื่อ เราสามารถแยกชื่อหลักสูตรทั้งหมดในฐานข้อมูลของเราออกเป็นอาร์เรย์ของแฮชและแสดงผลได้
ตอนนี้เราสามารถแทนที่รายชื่อหนังสือหลายเล่มด้วย 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 เดียว สล็อตสามารถแสดงผลได้หลายวิธี และคุณสามารถหาข้อมูลเพิ่มเติมได้ที่นี่
กำลังทดสอบ
คอมโพเนนต์ของมุมมองการทดสอบทำได้โดยใช้ "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
เราได้รับข้อผิดพลาดดังต่อไปนี้:
สิ่งนี้พิสูจน์ให้เราเห็นว่าการทดสอบกำลังเข้าสู่องค์ประกอบที่ถูกต้อง แต่ไม่มีอุปกรณ์ประกอบฉากที่เหมาะสม เนื่องจากรายการดังกล่าวจะต้องเป็นหลักสูตรที่มีคุณสมบัติด้านราคาและไม่ใช่สตริงในขณะที่เราผ่าน นอกจากนี้ยังบอกเราว่ามี 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
การทดสอบนี้ยังผ่าน เป็นการทดสอบว่าองค์ประกอบผู้ลงทะเบียนและส่วนลดนั้นแสดงผลอย่างถูกต้องเช่นกัน
จำได้ว่าเรามีองค์ประกอบของสล็อต และดังที่แสดงในภาพด้านล่าง มันแสดงส่วนหัวหนึ่งรายการและหลายชื่อ
ในการทดสอบนี้ เราส่งบล็อกของโค้ดที่มีส่วนหัวและชื่อที่ควรจะแสดงผล จากนั้นเราสามารถยืนยันกับองค์ประกอบที่แสดงผลได้
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