Bạn đã bao giờ phải vừa cập nhật dữ liệu, vừa cập nhật HTML, vừa cập nhật giá, rồi lại lo quên đâu đó một cái <span> chưa? Knockout.js sinh ra để giải phóng bạn khỏi cái vòng lặp khổ ải đó.
Mở đầu: Câu chuyện của một cái giỏ hàng
Tưởng tượng bạn đang xây dựng giao diện giỏ hàng cho một trang thương mại điện tử. Người dùng nhấn nút "Xóa" vào sản phẩm Beer - nghe đơn giản. Nhưng đằng sau cái nhấn đó, bạn phải:
1. Xóa sản phẩm khỏi mảng dữ liệu JavaScript
2. Tìm đúng phần tử <tr> trên HTML mà xóa đi
3. Tính lại tổng giá rồi cập nhật vào một cái <span> khác
4. Cầu trời đừng quên bước nào
Cứ thế nhân lên cho 10 tính năng, 20 màn hình - bạn sẽ có một mã nguồn đủ để ác mộng.
Knockout.js ra đời để giải quyết chính xác vấn đề này: thay vì bạn tự tay kéo dữ liệu lên giao diện, framework sẽ tự động đồng bộ hai bên cho bạn.
Knockout.js là gì?
Knockout.js là một thư viện JavaScript phía client, được viết hoàn toàn bằng JavaScript thuần. Không cần server-side đặc biệt - dùng với ASP.NET, PHP, Django, Rails hay bất cứ thứ gì bạn thích đều được.
Điểm đặc biệt của nó nằm ở triết lý thiết kế: tạo một kết nối trực tiếp giữa dữ liệu và giao diện. Khi dữ liệu thay đổi, giao diện tự cập nhật. Không cần document.getElementById, không cần .innerHTML, không cần lo lắng.
Lưu ý quan trọng: Knockout.js KHÔNG phải là jQuery hay MooTools. Nó không làm animation, không xử lý AJAX theo kiểu truyền thống. Nó chuyên biệt cho một việc duy nhất: kết nối dữ liệu với giao diện. Chính sự chuyên biệt này khiến nó có thể làm việc cùng với bất kỳ thư viện nào khác.
Kiến trúc MVVM: Không khó như bạn nghĩ
Knockout.js sử dụng mô hình MVVM (Model-View-ViewModel) - nghe có vẻ học thuật nhưng thực ra rất trực quan:
|
Thành phần
|
Vai trò
|
Ví dụ
|
|
Model
|
Dữ liệu gốc (database, API)
|
Thông tin giỏ hàng trong DB
|
|
ViewModel
|
Đối tượng JavaScript trung gian
|
Object ShoppingCart với các method
|
|
View
|
Giao diện HTML người dùng thấy
|
Trang web hiển thị danh sách sản phẩm
|
ViewModel là "trung gian" - nó nhận dữ liệu từ Model và đẩy lên View. Knockout.js tạo kết nối trực tiếp giữa ViewModel và View, nhờ đó mọi thay đổi trên ViewModel đều tự động phản ánh lên giao diện.
Ba khái niệm cốt lõi bạn cần nắm
1. Observable - "Biến biết mình đang bị theo dõi"
Trong JavaScript thông thường, khi bạn thay đổi một biến, không ai biết cả - kể cả trình duyệt. Observable của Knockout.js giống như một biến, nhưng nó "thông báo" cho framework mỗi khi giá trị thay đổi.
Biến thường - Knockout.js không hay biết gì:
var firstName = "John";
firstName = "Ryan"; // View vẫn hiện "John"
Observable - Knockout.js tự động cập nhật View:
var firstName = ko.observable("John");
firstName("Ryan"); // View tự đổi thành "Ryan" ngay lập tức
Lưu ý cú pháp: observable hoạt động như một hàm, không phải biến:
- Lấy giá trị: firstName()(không có tham số)
- Gán giá trị: firstName("Mary")(truyền vào như tham số)
Đây là điểm dễ nhầm nhất cho người mới - đừng dùng = để gán, bạn sẽ vô tình phá vỡ toàn bộ cơ chế theo dõi!
2. Computed Observable - "Biến phụ thuộc biến khác"
Đôi khi bạn cần một thuộc tính được tính toán từ những thuộc tính khác. Ví dụ: fullName = firstName + lastName. Computed Observable cho phép bạn làm điều đó - và Knockout.js sẽ tự cập nhật fullName bất cứ khi nào firstName hoặc lastName thay đổi:
function PersonViewModel() {
this.firstName = ko.observable("John");
this.lastName = ko.observable("Smith");
this.fullName = ko.computed(function() {
return this.firstName() + " " + this.lastName();
}, this);
}
Khi bạn gọi vm.firstName("Mary"), fullName tự động trở thành "Mary Smith". Không cần can thiệp thêm.
3. Observable Array - "Mảng biết mình bị sửa"
Khi làm việc với danh sách (giỏ hàng, bảng dữ liệu...), bạn cần Observable Array - một mảng mà Knockout.js theo dõi từng thao tác thêm/xóa:
this.shoppingCart = ko.observableArray([
new Product("Beer", 10.99),
new Product("Brats", 7.99),
new Product("Buns", 1.49)
]);
// Thêm item - View tự cập nhật ngay
this.shoppingCart.push(new Product("More Beer", 10.99));
// Xóa item - View tự cập nhật ngay
this.shoppingCart.remove(product);
Data Binding: "Dán nhãn" HTML để Knockout.js hiểu
Để kết nối ViewModel với View, Knockout.js dùng thuộc tính HTML đặc biệt: data-bind. Chỉ cần một dòng này, framework biết phần tử nào liên kết với dữ liệu nào:
<!-- Hiển thị giá trị -->
<p><span data-bind="text: firstName"></span>'s Shopping Cart</p>
<!-- Gắn sự kiện click -->
<button data-bind="click: checkout">Thanh toán</button>
<!-- Lặp qua mảng -->
<tbody data-bind="foreach: shoppingCart">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: price"></td>
</tr>
</tbody>
Knockout.js cung cấp nhiều loại binding cho các trường hợp khác nhau:
- Hiển thị dữ liệu: text, html, visible
- Giao diện động: css, style, attr
- Form tương tác: value, click, event, checked, options
- Điều khiển luồng: foreach, if, ifnot, with
Điểm mạnh của interactive binding là two-way: người dùng chỉnh sửa một <input>, ViewModel tự cập nhật. Bạn gán giá trị cho ViewModel, <input> tự hiển thị lại. Hai chiều, tự động, không cần viết thêm một dòng xử lý nào.
Ví dụ thực tế: Giỏ hàng tối giản
Đây là ví dụ đầy đủ từ ebook, cho thấy sức mạnh của Knockout.js trong khoảng 30 dòng:
JavaScript (ViewModel):
function Product(name, price) {
this.name = ko.observable(name);
this.price = ko.observable(price);
}
function ShoppingCart() {
var self = this;
this.items = ko.observableArray([
new Product("Beer", 10.99),
new Product("Brats", 7.99),
new Product("Buns", 1.49)
]);
this.addProduct = function() {
self.items.push(new Product("More Beer", 10.99));
};
this.removeProduct = function(product) {
self.items.remove(product);
};
}
ko.applyBindings(new ShoppingCart());
HTML (View):
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: price"></td>
<td>
<button data-bind="click: $root.removeProduct">Xoa</button>
</td>
</tr>
</tbody>
<button data-bind="click: addProduct">Them Beer</button>
Chỉ vậy thôi. Nhấn "Thêm Beer" - hàng xuất hiện. Nhấn "Xóa" - hàng biến mất. Không một dòng DOM manipulation nào.
Knockout.js so với các framework hiện đại
Knockout.js ra đời năm 2010, trước thời đại của React hay Vue. So sánh nhanh:
- jQuery: Giỏi thao tác DOM, animation, AJAX - nhưng không có data-binding tự động. Knockout.js thường được dùng kết hợp với jQuery.
- React / Vue: Hiện đại hơn, hệ sinh thái lớn hơn - nhưng cũng phức tạp hơn nhiều để setup ban đầu.
- Knockout.js: Nhẹ, không cần build tool, chỉ cần một file .js là chạy được. Phù hợp khi bạn cần thêm reactivity vào một dự án có sẵn mà không muốn đập đi xây lại toàn bộ.
Kết luận
Knockout.js có thể không còn là "ngôi sao sáng nhất" trong bầu trời JavaScript framework năm 2024, nhưng nó vẫn là một ví dụ xuất sắc về tư duy thiết kế data-driven UI. Học Knockout.js giúp bạn hiểu bản chất của data-binding - nền tảng mà React Hooks hay Vue Composition API đều kế thừa theo cách riêng của họ.
Quan trọng hơn, nó dạy bạn một nguyên tắc không bao giờ lỗi thời:
Hãy để framework lo việc đồng bộ giao diện, còn bạn tập trung vào dữ liệu. Khi bạn hiểu điều đó, mọi framework hiện đại đều trở nên dễ học hơn rất nhiều.
Tài liệu tham khảo
[1] Jamie Munro , Knockout.js: Building Dynamic Client-Side Web Applications, O'Reilly Media, 2014.
[2] Jorge Ferrando, KnockoutJS Essentials, Packt Publishing, 2015
[3] https://knockoutjs.com/
ThS. Trương Châu Long - Trưởng bộ môn CNTT - HTTT