Turbolinks - อาจเป็นคำที่ดูถูกเหยียดหยามที่สุดในจักรวาล Rails
บางทีคุณอาจลองใช้แล้ว คุณรวมเทอร์โบลิงก์ไว้ในโปรเจ็กต์ใหม่หรือแอปที่มีอยู่ และในไม่ช้าแอพก็เริ่มล้มเหลวในลักษณะที่แปลกประหลาดและมหัศจรรย์ ยังดีที่การแก้ไขนั้นง่ายเหมือนกัน - ปิดเทอร์โบลิงก์
...แต่บางบริษัทก็ทำได้ ที่ Honeybadger เราทำให้มันสำเร็จ - และเราไม่ใช่อัจฉริยะ
คำตอบนั้นง่ายมากจนฉันแทบจะลังเลที่จะพูดถึงมัน แต่หลังจากพูดเรื่องนี้ที่ Ruby Nation และ Madison+Ruby แล้ว ดูเหมือนว่าผู้คนจะพบว่าหัวข้อนี้มีประโยชน์ มาขุดกันเถอะ
วิธีการทำงานของ Turbolink และ PJAX
Turbolinks และ PJAX ทำงานในลักษณะเดียวกัน พวกมันคล้ายกันมาก ฉันจะพูดถึง PJAX ต่อจากนี้ไป :)
คุณเข้าใจ PJAX ในแง่ของคำขอสองหน้าได้ ครั้งแรกที่ผู้ใช้ขอหน้า หน้านั้นจะแสดงเหมือนกับหน้า Rails "ดั้งเดิม" อื่นๆ แต่เมื่อผู้ใช้คลิกลิงก์ที่เปิดใช้งาน PJAX จะมีสิ่งพิเศษเกิดขึ้น แทนที่จะโหลดหน้าใหม่ทั้งหมด จะมีการอัปเดตเพียงบางส่วนของหน้าเท่านั้น ทำผ่าน AJAX
สิ่งนี้ทำให้เราได้ประโยชน์มากมายจากแอปหน้าเดียว ในขณะที่หลีกเลี่ยงปัญหาบางอย่าง:
- แอป PJAX มักจะดูฉับไวพอๆ กับแอปหน้าเดียว เนื่องจากไม่จำเป็นต้องโหลดหน้าซ้ำทุกคำขออีกต่อไป
- คุณจะได้ใช้สแต็กเดียวกันสำหรับการพัฒนาส่วนหน้าและส่วนหลัง
- แอป PJAX จะลดระดับลงอย่างสวยงามตามค่าเริ่มต้นเมื่อผู้ใช้ปิดใช้งาน JS
- แอป PJAX เข้าถึงได้ง่ายขึ้นและเป็นมิตรกับ SEO
การติดตั้ง Turbolinks
มีห้องสมุดมากมายที่จะช่วยยกระดับ PJAX ให้กับคุณ Turbolinks น่าจะเป็นที่รู้จักมากที่สุด การตั้งค่าเป็นเพียงเรื่องของการรวม turbolinks gem ใน Gemfile ของคุณ:
gem 'turbolinks'
...และรวม JS ไว้ใน app/assets/javascripts/application.js
//= require turbolinks
เมื่อคุณรีโหลดแอปของคุณ ทุกลิงก์ภายในจะเป็นเทอร์โบลิงก์ เมื่อคุณคลิก หน้าใหม่จะถูกร้องขอผ่าน AJAX และแทรกลงในเอกสารปัจจุบัน
กำลังใช้งาน jquery-pjax
ที่ Honeybadger เราใช้ไลบรารี PJAX ที่พัฒนาโดย Github ต้องใช้การกำหนดค่ามากกว่า Turbolink เล็กน้อย แต่ก็มีความยืดหยุ่นมากกว่าเล็กน้อย
แทนที่จะสมมติว่าลิงก์ทั้งหมดเป็น PJAX จะให้คุณควบคุมสิ่งนั้นได้ นอกจากนี้ยังช่วยให้คุณควบคุมว่าจะแทรกเนื้อหา PJAX ไว้ที่ใดในหน้า
สิ่งแรกที่ฉันต้องทำคือเพิ่มคอนเทนเนอร์ลงใน HTML
<div class="container" id="pjax-container">
Go to <a href="/page/2">next page</a>.
</div>
ตอนนี้ฉันต้องตั้งค่าลิงก์ PJAX:
$(document).pjax('a', '#pjax-container')
สุดท้าย ฉันจะบอกรางไม่ให้แสดงเลย์เอาต์ตามคำขอ PJAX คุณต้องทำสิ่งนี้ มิฉะนั้นคุณจะจบลงด้วยส่วนหัวและส่วนท้ายที่ซ้ำกัน เช่น. เว็บไซต์ของคุณจะมีลักษณะเหมือน Inception
def index
if request.headers['X-PJAX']
render :layout => false
end
end
มันไม่ง่ายขนาดนั้น!
โอเค มันซับซ้อนกว่าที่ฉันบอกนิดหน่อย ส่วนใหญ่เป็นเพราะหลุมพรางขนาดยักษ์ที่ไม่ค่อยมีคนพูดถึง
เมื่อ DOM ของคุณไม่เคลียร์ทุกครั้งที่โหลดหน้าเว็บ หมายความว่า JS ที่อาจทำงานบนแอป Rails แบบเดิมของคุณตอนนี้ทำงานผิดปกติอย่างมาก
เหตุผลก็คือพวกเราหลายคนเรียนรู้ที่จะเขียน JS ในลักษณะที่สนับสนุนความขัดแย้งในการตั้งชื่อโดยไม่ได้ตั้งใจ หนึ่งในผู้ร้ายที่ร้ายกาจที่สุดคือตัวเลือก jquery ง่ายๆ
// I may look innocent, but I'm not!
$(".something")
การเขียน JS สำหรับหน้าที่ไม่โหลดซ้ำ
ความขัดแย้งเป็นปัญหาอันดับหนึ่งเมื่อคุณเขียน JS สำหรับหน้าที่ไม่เคยโหลดซ้ำ ปัญหาที่แปลกประหลาดและยากต่อการดีบักบางอย่างเกิดขึ้นเมื่อ JS จัดการกับ HTML ที่ไม่ได้ตั้งใจจะสัมผัส ตัวอย่างเช่น ลองดูตัวจัดการเหตุการณ์ jQuery ธรรมดา:
$(document).on("click", ".hide-form", function() {
$(".the-form").hide();
});
นี่เป็นเหตุผลที่สมเหตุสมผลอย่างยิ่งหากทำงานเพียงหน้าเดียว แต่ถ้า DOM ไม่เคยถูกโหลดซ้ำ อีกไม่นานก็จะมีใครบางคนเข้ามาและเพิ่มองค์ประกอบอื่นด้วยคลาสของ .hide-form ตอนนี้คุณมีความขัดแย้ง
ความขัดแย้งเช่นนี้จะเกิดขึ้นเมื่อคุณมีข้อมูลอ้างอิงทั่วโลกจำนวนมาก และคุณจะลดจำนวนการอ้างอิงทั่วโลกได้อย่างไร คุณใช้เนมสเปซ
ตัวเลือกเนมสเปซ
ในคลาส Ruby ด้านล่าง เราใช้ชื่อเดียว - ชื่อคลาส - เพื่อซ่อนชื่อเมธอดจำนวนมาก
# One global name hides two method names
class MyClass
def method1
end
def method2
end
end
แม้ว่าจะไม่มีการสนับสนุนในตัวสำหรับเนมสเปซองค์ประกอบ DOM (อย่างน้อยก็จนกว่า ES6 WebComponents จะมาถึง) แต่ก็เป็นไปได้ที่จะจำลองเนมสเปซด้วยรูปแบบการเข้ารหัส
ตัวอย่างเช่น สมมติว่าคุณต้องการติดตั้งวิดเจ็ตแก้ไขแท็ก หากไม่มีเนมสเปซ อาจมีลักษณะเช่นนี้ โปรดทราบว่ามีการอ้างอิงทั่วโลกสามรายการ
// <input class="tags" type="text" />
// <button class="tag-submit">Save</button>
// <span class="tag-status"></span>
$(".tag-submit").click(function(){
save($(".tags").val());
$(".tag-status").html("Tags were saved");
});
อย่างไรก็ตาม ด้วยการสร้าง "เนมสเปซ" และทำให้การค้นหาองค์ประกอบทั้งหมดสัมพันธ์กับมัน เราสามารถลดจำนวนการอ้างอิงทั่วโลกให้เหลือเพียงรายการเดียว เรายังได้กำจัดคลาสหลายคลาสออกไปด้วย
// <div class="tags-component">
// <input type="text" />
// <button>Save</button>
// <span></span>
// </div>
$container = $("#tags-component")
$container.on("click", "button" function(){
save($container.find("input").val());
$container.find("span").html("Tags were saved");
});
ฉันบอกคุณว่ามันง่าย
ในตัวอย่างข้างต้น ฉันเนมสเปซองค์ประกอบ DOM ของฉันโดยใส่ไว้ในคอนเทนเนอร์ที่มีคลาสของ "tags-component" ไม่มีอะไรพิเศษเกี่ยวกับชื่อนั้น แต่ถ้าฉันใช้หลักการตั้งชื่อที่คอนเทนเนอร์เนมสเปซทุกอันมีคลาสที่ลงท้ายด้วย "-component" สิ่งที่น่าสนใจบางอย่างก็เกิดขึ้น
คุณสามารถระบุตัวเลือกส่วนกลางที่ไม่ถูกต้องได้อย่างรวดเร็ว
หากตัวเลือกส่วนกลางเพียงตัวเดียวที่คุณอนุญาตคือส่วนประกอบ และส่วนประกอบทั้งหมดมีคลาสที่ลงท้ายด้วย "-component" คุณจะเห็นได้อย่างรวดเร็วว่ามีตัวเลือกส่วนกลางที่ไม่ดีหรือไม่
// Bad
$(".foo").blah()
// Ok
$(".foo-component".blah()
มันง่ายที่จะหา JS ที่ควบคุม HTML
มาทบทวนฟอร์มแท็กเชิงโต้ตอบของเรากันอีกครั้ง คุณได้เขียน HTML สำหรับแบบฟอร์ม ตอนนี้คุณต้องเพิ่ม JS และ CSS แต่คุณจะใส่ไฟล์เหล่านั้นไว้ที่ไหน? โชคดีที่เมื่อคุณมีรูปแบบการตั้งชื่อสำหรับเนมสเปซ มันแปลเป็นโครงร่างการตั้งชื่อสำหรับไฟล์ JS และ CSS ได้อย่างง่ายดาย แผนผังไดเร็กทอรีอาจมีลักษณะดังนี้
.
├── javascripts
| ├── application.coffee
│ └── components
│ └── tags.coffee
└── stylesheets
├── application.scss
└── components
└── tags.scss
การเริ่มต้นอัตโนมัติ
ในเว็บแอปแบบดั้งเดิม เป็นเรื่องปกติที่จะเริ่มต้น JS ทั้งหมดของคุณในการโหลดหน้าเว็บ แต่ด้วย PJAX และ Turbolinks คุณจะมีการเพิ่มและนำองค์ประกอบออกจาก DOM ตลอดเวลา ด้วยเหตุนี้ จึงเป็นประโยชน์อย่างยิ่งหากโค้ดของคุณสามารถตรวจจับได้โดยอัตโนมัติเมื่อส่วนประกอบใหม่เข้าสู่ dom และเริ่มต้นสิ่งที่จำเป็นสำหรับ JS ได้ทันที
รูปแบบการตั้งชื่อที่สอดคล้องกัน ทำให้ทำได้ง่ายมาก มีวิธีการมากมายที่คุณอาจใช้เพื่อกำหนดค่าเริ่มต้นอัตโนมัติ นี่คือหนึ่ง:
Initializers = {
tags: function(el){
$(el).on("click", "button", function(){
// setup component
});
}
// Other initializers can go here
}
// Handler called on every normal and pjax page load
$(document).on("load, pjax:load", function(){
for(var key in Initializers){
$("." + key + "-component").each(Initializers[key]);
}
}
มันทำให้ CSS ของคุณดีขึ้นด้วย!
JavaScript ไม่ได้เป็นเพียงแหล่งที่มาของความขัดแย้งในหน้าเว็บ CSS อาจยิ่งแย่ลงไปอีก! โชคดีที่ระบบเนมสเปซอันชาญฉลาดของเรายังช่วยให้เขียน CSS ได้ง่ายขึ้นโดยไม่มีข้อขัดแย้งอีกด้วย ดีกว่าใน SCSS:
.tags-component {
input { ... }
button { ... }
span { ... }
}