Turbolinks - đó có lẽ là một trong những từ bị khinh thường nhất trong vũ trụ Rails.
Có thể bạn đã thử nó. Bạn đã bao gồm các vòng quay trong dự án mới hoặc một ứng dụng hiện có. Và chẳng bao lâu, ứng dụng bắt đầu thất bại theo những cách kỳ lạ và tuyệt vời. Thật tốt, việc sửa chữa rất dễ dàng - hãy tắt turbolinks.
... nhưng một số công ty làm cho nó hoạt động. Tại Honeybadger, chúng tôi đã làm cho nó hoạt động - và chúng tôi không phải là thiên tài.
Câu trả lời đơn giản đến mức tôi gần như do dự khi đưa ra. Nhưng sau khi nói chuyện về chủ đề này tại Ruby Nation và Madison + Ruby, có vẻ như mọi người thấy chủ đề này hữu ích. Vì vậy, hãy cùng tìm hiểu.
Cách hoạt động của Turbolinks và PJAX
Turbolinks và PJAX về cơ bản hoạt động theo cùng một cách. Chúng rất giống nhau, tôi sẽ chỉ nói PJAX từ bây giờ. :)
Bạn có thể hiểu PJAX về hai yêu cầu trang. Lần đầu tiên người dùng yêu cầu một trang, nó sẽ được phục vụ giống như bất kỳ trang Rails "truyền thống" nào khác. Nhưng khi người dùng nhấp vào liên kết hỗ trợ PJAX, một điều đặc biệt sẽ xảy ra. Thay vì tải lại hoàn toàn trang, chỉ một phần của trang được cập nhật. Nó được thực hiện thông qua AJAX.
Điều này mang lại cho chúng tôi rất nhiều lợi thế của một ứng dụng trang đơn lẻ, đồng thời vượt qua một số khó khăn:
- Các ứng dụng PJAX thường có vẻ linh hoạt như các ứng dụng trang đơn, vì trang không còn phải được tải lại hoàn toàn theo mọi yêu cầu.
- Bạn có thể sử dụng cùng một ngăn xếp để phát triển front-end và back end
- Theo mặc định, các ứng dụng PJAX sẽ giảm chất lượng một cách dễ dàng khi người dùng đã tắt JS
- Các ứng dụng PJAX dễ tiếp cận hơn và thân thiện với SEO
Triển khai Turbolinks
Có rất nhiều thư viện sẽ thực hiện công việc nặng nhọc của PJAX cho bạn. Turbolinks có lẽ là nổi tiếng nhất. Việc thiết lập nó chỉ là vấn đề bao gồm viên ngọc turbolinks trong Gemfile của bạn:
gem 'turbolinks'
... và bao gồm JS trong app / asset / javascripts / application.js
//= require turbolinks
Bây giờ khi bạn tải lại ứng dụng của mình, mọi liên kết nội bộ sẽ là một liên kết xoay. Khi bạn nhấp vào chúng, trang mới sẽ được yêu cầu thông qua AJAX và được chèn vào tài liệu hiện tại.
Triển khai jquery-pjax
Tại Honeybadger, chúng tôi sử dụng thư viện PJAX ban đầu được phát triển bởi Github. Nó yêu cầu cấu hình cao hơn một chút so với Turbolinks, nhưng nó cũng linh hoạt hơn một chút.
Thay vì giả định rằng tất cả các liên kết là PJAX, nó cho phép bạn kiểm soát điều đó. Nó cũng cho phép bạn kiểm soát vị trí nội dung PJAX sẽ được chèn trên trang.
Điều đầu tiên tôi cần làm là thêm vùng chứa vào HTML của mình
<div class="container" id="pjax-container">
Go to <a href="/page/2">next page</a>.
</div>
Bây giờ tôi cần thiết lập liên kết PJAX:
$(document).pjax('a', '#pjax-container')
Cuối cùng, tôi sẽ nói với rails không hiển thị bố cục theo yêu cầu PJAX. Bạn phải làm điều gì đó như thế này, hoặc bạn sẽ kết thúc với các đầu trang và chân trang trùng lặp. I E. trang web của bạn sẽ giống như Ban đầu.
def index
if request.headers['X-PJAX']
render :layout => false
end
end
Không dễ dàng như vậy!
Được rồi, nó phức tạp hơn một chút so với những gì tôi đã nói. Chủ yếu là do một cạm bẫy khổng lồ mà rất ít người nói đến.
Khi DOM của bạn không được xóa trong mỗi lần tải trang, điều đó có nghĩa là JS có thể đã hoạt động trên ứng dụng Rails truyền thống của bạn giờ đây bị hỏng theo những cách rất lạ.
Lý do cho điều này là nhiều người trong chúng ta đã học cách viết JS theo cách khuyến khích các xung đột đặt tên ngẫu nhiên. Một trong những thủ phạm xảo quyệt nhất là công cụ chọn jquery đơn giản.
// I may look innocent, but I'm not!
$(".something")
Viết JS cho các trang không tải lại
Xung đột là vấn đề số một khi bạn viết JS cho các trang không bao giờ tải lại. Một số vấn đề kỳ lạ, khó gỡ lỗi nhất xảy ra khi JS thao tác với HTML mà nó không bao giờ có ý định chạm vào. Ví dụ:hãy xem một trình xử lý sự kiện jQuery thông thường:
$(document).on("click", ".hide-form", function() {
$(".the-form").hide();
});
Điều này là hoàn toàn hợp lý, nếu nó chỉ chạy trên một trang. Nhưng nếu DOM không bao giờ được tải lại, chỉ là vấn đề thời gian trước khi ai đó đến và thêm một phần tử khác có lớp .hide-form. Bây giờ bạn có xung đột.
Xung đột như thế này xảy ra khi bạn có nhiều tài liệu tham khảo toàn cầu. Và làm thế nào để bạn giảm số lượng tham chiếu toàn cầu? Bạn sử dụng không gian tên.
Công cụ chọn vị trí đặt tên
Trong lớp Ruby bên dưới, chúng tôi đang sử dụng một tên chung - tên lớp - để ẩn rất nhiều tên phương thức.
# One global name hides two method names
class MyClass
def method1
end
def method2
end
end
Mặc dù không có hỗ trợ tích hợp cho các phần tử DOM của vùng chứa tên (ít nhất là không phải cho đến khi ES6 WebComponents đến), có thể mô phỏng không gian tên bằng các quy ước mã hóa.
Ví dụ:hãy tưởng tượng rằng bạn muốn triển khai tiện ích chỉnh sửa thẻ. Nếu không có không gian tên, nó có thể trông giống như thế này. Lưu ý rằng có ba tham chiếu toàn cầu.
// <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");
});
Tuy nhiên, bằng cách tạo một "không gian tên" và thực hiện tra cứu tất cả các phần tử liên quan đến nó, chúng ta có thể giảm số lượng tham chiếu toàn cục xuống còn một. Chúng tôi cũng đã loại bỏ hoàn toàn một số lớp.
// <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");
});
Tôi đã nói với bạn rằng nó rất đơn giản.
Trong ví dụ trên, tôi đặt tên cho các phần tử DOM của mình bằng cách đặt chúng bên trong vùng chứa có lớp "tags-component". Không có gì đặc biệt về cái tên cụ thể đó. Nhưng nếu tôi áp dụng quy ước đặt tên trong đó mọi vùng chứa không gian tên đều có lớp kết thúc bằng "-component", một số điều rất thú vị sẽ xảy ra.
Bạn có thể nhận ra các bộ chọn chung không chính xác trong nháy mắt.
Nếu bộ chọn chung duy nhất mà bạn cho phép là các thành phần và tất cả các thành phần đều có lớp kết thúc bằng "-component" thì bạn có thể xem nhanh xem mình có bất kỳ bộ chọn chung nào không hợp lệ hay không.
// Bad
$(".foo").blah()
// Ok
$(".foo-component".blah()
Thật dễ dàng tìm thấy JS điều khiển HTML
Hãy truy cập lại biểu mẫu thẻ tương tác của chúng tôi. Bạn đã viết HTML cho biểu mẫu. Bây giờ bạn cần thêm một số JS và CSS. Nhưng bạn đặt những tập tin đó ở đâu? May mắn thay, khi bạn có một sơ đồ đặt tên cho không gian tên, nó sẽ dễ dàng chuyển thành sơ đồ đặt tên cho các tệp JS và CSS. Đây là giao diện của cây thư mục.
.
├── javascripts
| ├── application.coffee
│ └── components
│ └── tags.coffee
└── stylesheets
├── application.scss
└── components
└── tags.scss
Khởi tạo tự động
Trong một ứng dụng web truyền thống, việc khởi tạo tất cả JS của bạn khi tải trang là điều bình thường. Nhưng với PJAX và Turbolinks, bạn luôn có các phần tử được thêm vào và xóa khỏi DOM. Vì điều này, nó thực sự có lợi nếu mã của bạn có thể tự động phát hiện khi nào các thành phần mới nhập dom và khởi tạo bất kỳ JS nào cần thiết cho chúng một cách nhanh chóng.
Một kế hoạch đặt tên nhất quán, làm cho điều này thực sự dễ dàng thực hiện. Có vô số cách tiếp cận bạn có thể thực hiện để tự động khởi tạo. Đây là một:
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]);
}
}
Nó cũng giúp CSS của bạn tốt hơn!
JavaScript không phải là nguồn xung đột duy nhất trên một trang web. CSS thậm chí có thể tồi tệ hơn! May mắn thay, hệ thống không gian tên tiện lợi của chúng tôi cũng giúp viết CSS dễ dàng hơn nhiều mà không bị xung đột. Nó thậm chí còn đẹp hơn trong SCSS:
.tags-component {
input { ... }
button { ... }
span { ... }
}