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

ทิ้งแอพหน้าเดียวของคุณสำหรับ...เทอร์โบลิงค์?

Turbolinks - อาจเป็นคำที่ดูถูกเหยียดหยามที่สุดในจักรวาล Rails

บางทีคุณอาจลองใช้แล้ว คุณรวมเทอร์โบลิงก์ไว้ในโปรเจ็กต์ใหม่หรือแอปที่มีอยู่ และในไม่ช้าแอพก็เริ่มล้มเหลวในลักษณะที่แปลกประหลาดและมหัศจรรย์ ยังดีที่การแก้ไขนั้นง่ายเหมือนกัน - ปิดเทอร์โบลิงก์

...แต่บางบริษัทก็ทำได้ ที่ Honeybadger เราทำให้มันสำเร็จ - และเราไม่ใช่อัจฉริยะ

คำตอบนั้นง่ายมากจนฉันแทบจะลังเลที่จะพูดถึงมัน แต่หลังจากพูดเรื่องนี้ที่ Ruby Nation และ Madison+Ruby แล้ว ดูเหมือนว่าผู้คนจะพบว่าหัวข้อนี้มีประโยชน์ มาขุดกันเถอะ

ทิ้งแอพหน้าเดียวของคุณสำหรับ...เทอร์โบลิงค์?

Turbolinks และ PJAX ทำงานในลักษณะเดียวกัน พวกมันคล้ายกันมาก ฉันจะพูดถึง PJAX ต่อจากนี้ไป :)

คุณเข้าใจ PJAX ในแง่ของคำขอสองหน้าได้ ครั้งแรกที่ผู้ใช้ขอหน้า หน้านั้นจะแสดงเหมือนกับหน้า Rails "ดั้งเดิม" อื่นๆ แต่เมื่อผู้ใช้คลิกลิงก์ที่เปิดใช้งาน PJAX จะมีสิ่งพิเศษเกิดขึ้น แทนที่จะโหลดหน้าใหม่ทั้งหมด จะมีการอัปเดตเพียงบางส่วนของหน้าเท่านั้น ทำผ่าน AJAX

สิ่งนี้ทำให้เราได้ประโยชน์มากมายจากแอปหน้าเดียว ในขณะที่หลีกเลี่ยงปัญหาบางอย่าง:

  • แอป PJAX มักจะดูฉับไวพอๆ กับแอปหน้าเดียว เนื่องจากไม่จำเป็นต้องโหลดหน้าซ้ำทุกคำขออีกต่อไป
  • คุณจะได้ใช้สแต็กเดียวกันสำหรับการพัฒนาส่วนหน้าและส่วนหลัง
  • แอป PJAX จะลดระดับลงอย่างสวยงามตามค่าเริ่มต้นเมื่อผู้ใช้ปิดใช้งาน JS
  • แอป PJAX เข้าถึงได้ง่ายขึ้นและเป็นมิตรกับ SEO

มีห้องสมุดมากมายที่จะช่วยยกระดับ 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 { ... }
}