Giới thiệu Mithril JS: một MVC framework cực nhẹ
Bài đăng này đã không được cập nhật trong 3 năm
Mở đầu
Về Javascript Framework hiện nay, thật sự mà nói thì nó nhiều kinh khủng luôn đấy. Điển hình như: React, Node, Ember, Angular, Knockout, Backbone, Jasmine, Babel, Flow,... còn nhiều nhiều nữa, kể thì ko biết bao giờ mới xong, mà không biết kiến thức mình đủ để list bằng hết ko nữa.
Một sự thật là Framework càng to học càng lâu, chưa kể kinh nghiệm trong khi dùng framework đó ( các trick mình chưa biết phải tìm hiểu ). Dù có biết hết một framework thì bạn cũng chưa chắc dùng hết được các tính năng của nó, điều đó tạo nên những tính năng dư thừa không cần thiết, có khi còn lãng phí tài nguyên nữa.
Nếu bạn đang cần một framework nhỏ, chia module rõ ràng, nhìn vào dễ hiểu dễ học, ít config lằng nhằng, có bộ DOM virtual nữa thì xin chúc mừng bạn đã tìm đúng top.
MithrilJS là gì ?
Mithril nó là một client-framework theo mô hình MVC, dùng để tổ chức code một cách dễ dàng, sáng sủa để đọc hiểu, cũng là dễ bảo trì hơn sau này. Nó phân chia ứng dụng ra thành ba phần:
-
Data layer (Model)
-
UI layer (View)
-
Glue layer (Controller)
Light-weight ( nhẹ )
-
Toàn bộ framework chỉ 7.8kb sau khi đã Gzip.
-
Code độc lập không có dependency.
Robust ( mạnh mẽ )
-
Nó mặc định là an toàn cho các templates.
-
Sử dụng mô hình MVC cho các components.
Fast ( nhanh chóng )
-
Có Virtual DOM và compilable templates ( template có thể dịch )
-
Hệ thống auto-redraw thông minh
Và đặt biệt Mithril cung cấp cho chúng ta API nhỏ đủ dùng, không quá phức tạp. Đó là lý do nó chỉ vỏn vẹn 7.8kb so với những framework khổng lồ khác.
Hiệu suất của Mithril
Thời gian ít hơn thì hiệu suất cao hơn. Như các bạn thấy Mithril so với các framework khác nhanh hơn hẳn.
Khái niệm
Về căn bản, một ứng dụng viết bằng Mithril sẽ được gắn vào element trong DOM tree bằng hàm m.mount(el, app). app ở đây có thể là một hoặc nhiều component lồng vào nhau. Với Mithril, component chỉ đơn giản là một plain-old JavaScript object (POJO) có hai thuộc tính controller và view.
var HelloWorld = {
controller: function () {
this.getText = function () {
return 'Hello World'
}
},
view: function (ctrl) {
return m('h1', ctrl.getText())
}
}
m.mount(document.body, m.component(HelloWorld))
Khi component được mount vào body, một biến var ctrl = new component.controller() sẽ được khởi tạo và pass vào component.view(). m() chính là hàm để khởi tạo virtual element để Mithril có thể render vào body, có signature như sau:
m(selector, attributes, children)
-
selector: string thể hiện đối tượng DOM, viết theo syntax của CSS, ví dụ như a.btn.btn-primary[href=#][title="Click me"]. Nếu bạn không khai báo tên tag thì mặc định là DIV.
-
attributes: (không bắt buộc) object khai báo các thuộc tính của HTML tag và giá trị tương ứng. Thường dùng để thiết lập các thuộc tính động và gắn event handlers.
-
children: (không bắt buộc) khai báo các children nodes của đối tượng DOM này. Giá trị có thể là:
-
string: 'Click me'
-
array: [m('i.fa.fa-close'), m('span', 'Click me')]
-
component: m.component(MySubcomponent)
-
Bạn có thể chạy thử đoạn code trên. Đơn giản vậy thôi, căn bản Mithril là vậy đó.
Code thôi
Để bắt đầu làm thì mình làm sơ qua giao diện.
var App = {}
App.view = function (ctrl) {
return m('.wrapper', [
m('table.pure-table', [
m('thead', [
m('tr', [
m('td', 'Item'),
m('td', 'Cost'),
m('td')
])
]),
m('tbody', [
m('tr', [
m('td', 'Potion'),
m('td', '50'),
m('td', [m('button.pure-button', 'Buy')])
]),
m('tr', [
m('td', 'Hi-Potion'),
m('td', '200'),
m('td', [m('button.pure-button', 'Buy')])
]),
m('tr', [
m('td', 'Phoenix Down'),
m('td', '150'),
m('td', [m('button.pure-button', 'Buy')])
])
])
]),
m('p', [m('a.pure-button[href=#]', 'View Cart')])
])
}
m.mount(document.body, m.component(App))
Hoàn toàn không có gì đặc biệt, chỉ là tạo HTML bằng virtual DOM của Mithril. Một lưu ý là bạn cũng có thể không cần khai báo controller trong component object, vì Mithril sẽ tạo một controller mặc định (do nothing) cho bạn. Nếu bạn đã có sẵn template HTML, có thể sử dụng template converter để chuyển thành Mithril virtual DOM. Hoặc nếu thích có thể dùng MSX để viết "nửa nạc nửa mỡ" cho giống React.
OK, bây giờ mình có một array các item, và muốn render nó lên view. Để làm điều này, mình sẽ pass một biến args kiểu object vào hàm m.component(). Biến này sẽ là đối số thứ 2 của hàm view().
var args = {
items: [
{name: 'Potion', price: 50},
{name: 'Hi-Potion', price: 200},
{name: 'Phoenix Down', price: 150},
{name: 'Echo Screen', price: 50},
{name: 'Soft', price: 100},
{name: 'Antidote', price: 50},
{name: 'Eye Drops', price: 50},
{name: 'Remedy', price: 300},
{name: 'Annoyntment', price: 150},
{name: 'Tent', price: 800}
]
}
m.component(App, args)
App.view = function (ctrl, args) {
return m('.wrapper', [
//...
// Dùng hàm map() để loop qua array các items
// và tạo ra một array mới chứa các virtual DOM
m('tbody', args.items.map(function (item) {
return m('tr', [
m('td', item.name),
m('td', item.price),
m('td', [m('button.pure-button', 'Buy')])
])
}))
])
//...
])
}
Sweet! Tiếp theo mình muốn khi click vào nút Buy sẽ có prompt hiện ra để nhập số lượng. Bây giờ là lúc đụng tới controller đây. Mình sẽ viết một hàm addToCart() trong controller để xử lý sự kiện click.
// args là biến được pass vào m.component()
App.controller = function (args) {
// Chứa các item trong cart
this.cart = []
/**
* Hàm xử lý khi click vào nút Buy
*
* @param {object} item
* @param {EventObject} e
*/
this.addToCart = function (item, e) {
e.preventDefault()
var quantity = parseInt(window.prompt('Enter quantity'), 10)
if (isNaN(quantity) || quantity <= 0) {
alert('Bigger than zero please')
return
}
this.cart.push({
quantity: quantity,
item: item
})
}
}
Để gắn event handler này vào view, mình sửa lại hàm map như sau:
m('tbody', args.items.map(function (item) {
return m('tr', [
m('td', item.name),
m('td', item.price),
m('td', [
m('button.pure-button', {
onclick: ctrl.addToCart.bind(ctrl, item)
}, 'Buy')
])
])
}))
Tiếp theo, hãy hiển thị các item trong cart. Lần này mình sẽ lấy data từ controller, chính xác là ctrl.cart để hiển thị lên view.
m('h3', 'Cart'),
m('table.pure-table', [
m('thead', [
m('tr', [
m('td', 'Item'),
m('td', 'Price * Quantity'),
m('td', 'Sum')
])
]),
// Loop qua các items trong cart
m('tbody', ctrl.cart.map(function (obj) {
return m('tr', [
m('td', obj.item.name),
// Dùng m.trurst() để hiển thị HTML entity
m('td', m.trust(obj.item.price + ' × ' + obj.quantity)),
m('td', obj.item.price * obj.quantity)
])
}))
Thử xem, bạn có thể click nút Buy và nhập số lượng. Nội dung trong cart sẽ thay đổi và tự động update lên view. Một chút lưu ý là về mặc định, m() sẽ tự động escape HTML entities, cũng như phòng ngừa các thể loại invalid markup hay tìm cách chèn mã độc vào chương trình. Do đó chúng ta sẽ dùng m.trust() nếu chắc chắn rằng nội dung hiển thị là an toàn (chết thì chịu).
Trong phần hiển thị cart, mình muốn có thêm một dòng Total để xem tổng giá trị của cart. Mình sẽ viết thêm một hàm nữa trong controller để làm chuyện này.
// controller
this.getCartTotal = function () {
if (this.cart.length === 0) {return 0}
return this.cart.reduce(function (acc, obj) {
return acc + obj.quantity * obj.item.price
}, 0)
}
// view
m('tfooter', m('tr', [
m('td[colspan=2]', m('strong', 'Total')),
m('td', ctrl.getCartTotal())
]))
Đến đây thì có thể coi như xong căn bản về Mithril rồi. Dĩ nhiên là cart này còn nhiều điểm cần cải tiến, ví dụ như xóa item ra khỏi cart, tự động update số lượng của item nếu đã có sẵn trong cart, etc. nhưng mình sẽ để cho phần sau.
Lời kết
Ở bài này chủ yếu giới thiệu Mithrils là chính. Hi vọng qua bài hướng dẫn này các bạn sẽ thấy một lựa chọn khác, nhỏ gọn, code dễ thương, khuyến khích FP và hiệu suất tốt. Các bạn có thể đọc thêm tài liệu về Mithril hoặc chờ các bài tiếp theo về Mithril của mình, mình sẽ hướng dẫn cụ thể hơn.
Tham khảo:
http://mithril.js.org/getting-started.html
http://laptrinh.pro/d/8-gioi-thieu-mithril-js-mot-mvc-framework-cuc-nhe
All rights reserved