Bạn đã quá quen thuộc với các Javascript framework hàng khủng như Angularjs, Reactjs hay Vuejs, những framework với đầy các tính năng tuyệt vời? Vậy thì trong bài viết này, hãy cùng mình đổi gió với một micro-framework có dung lượng vỏn vẹn chỉ 1kB - Hyperapp, xem nó làm được gì nhé
Bắt đầu thôi
Cùng xem qua một ví dụ bắt đầu của Hyperapp để biết được các tính năng cơ bản của nó nhé:
Dùng const state để lưu object chứa các trạng thái của ứng dụng (count)
Const actions để lưu các methods dùng để update giá trị của các state
Hàm view dùng để tạo ra virtualDOM khi chạy ứng dụng, nhận 2 tham số là state và actions, dùng 2 tham số này để biểu diễn và update state của ứng dung. Ở đây, khi gọi sự kiện onclick vào button - sẽ thực hiện gọi actions down để giảm đi giá trị của state.count, tương tự với +. Hiển thị state: {state.count}, DOM cũng được cập nhật ngay khi state được thay đổi.
Và gọi hàm app(state, actions, view, document.body) để thực thi ứng dụng, hàm app sẽ mount ứng dụng vào document.body
Cài đặt
Cài đặt sử dụng npm hoặc yarn
npm i hyperapp
// hoặc
yarn add hyperapp
Import để sử dụng
import { h, app } from"hyperapp"
Hoặc sử dụng cdn, sử dụng được trên tất cả các trình duyệt hổ trợ ES5, dùng window.hyperapp
<scriptsrc="https://unpkg.com/hyperapp"></script>
Tổng quan
Qua ví dụ bên trên chúng ta cũng biết được ứng Hyperapp gồm 3 thành phần liên kết với nhau: state, view, và actions.
Khi được khởi tạo, ứng dụng sẽ thực thi các vòng lặp liên tục, thực hiện các actions của người dùng từ các event và cập nhật state và biểu diễn thông qua DOM ảo. Sau mỗi action được thực thi, state sẽ được biểu diễn lại cho người dùng.
Các khái niệm
State
Là nơi chứa các trạng thái của ứng dụng, phải là một plain object và một khi được khởi tạo, không thể thay đổi thay đổi trực tiếp giá trị mà phải thông qua actions.
Hyperapp không có khái niêm Local State. Thay vào đó, các components là các hàm thuần túy trả về một biểu diễn DOM ảo của global state.
Actions
Là cách duy nhất để update state của ứng dụng, các hàm action chỉ nhận 1 tham số
Kết quả trả về của action là một object chứa một phần của state mới. State mới là kết quả của việc shallow merge giữa state hiện tại và object trả về của action. Ngoài cách trả về một object, action còn có thể trả về một hàm nhận tham số là state hiện tại và các action với kết quả trả về là một object chứa một phần của state mới:
constactions= {down: value => state => ({ count:state.count - value }),up: value => state => ({ count:state.count + value })}
Actions bất đồng bộ
Các actions thực hiện side effects (ghi database, gọi đến server) không cần có giá trị trả về. Có thể gọi đến các action khác trong action. Các action trả về một Promise, undefined hay null sẽ không kích hoạt việc cập nhật state.
constactions= {upLater: value => (state, actions) => {setTimeout(actions.up,1000, value) },up: value => state => ({ count:state.count + value })}
Các actions có thể là async function, vì async function trả về Promise, không phải một phần của state object nên bạn cần gọi một action khác để update state:
constactions= {upLater: () =>async (state, actions) => {awaitnewPromise(done =>setTimeout(done,1000))actions.up(10) },up: value => state => ({ count:state.count + value })}
Nested Actions
Actions có thể nằm trong 1 namespace, dùng để update state nằm trong một namespace cùng tên:
conststate= { counter: { count:0 }}constactions= { counter: {down: value => state => ({ count:state.count - value }),up: value => state => ({ count:state.count + value }) }}
Interoperability
Bạn có thể gọi đến actions từ bên ngoài ứng dụng theo cách dưới đây:
Một action trả về tham chiếu của state sẽ ko làm cập nhật lại view:
constactions= {getState: () => state => state}
View
Mỗi khi state thay đổi, hàm view sẽ được gọi để cập nhật virtual DOM biểu diện giao diện của ứng dụng. Hàm view sẽ trả về virtualDOM mới, Hyperapp sẽ đảm nhiệm việc cập nhật lại DOM thật cho phù hợp
Một component là một hàm thuần (pure function) trả về một virtual node. Không giống như view, component không kết nối với state và action của ứng dụng.
Dưới đây là ví dụ về cách sử dụng component trong ứng dụng của bạn:
<main id=app></main>
@import "https://unpkg.com/water.css@2/out/water.min.css";
body {
font-size: 1.5em;
}
ul {
padding: 0;
}
li {
list-style: none;
}
form {
display: flex;
}
input[type=checkbox]:checked + span {
text-decoration: line-through;
}
Nếu bạn không biết trước tất cả các thuộc tính mà bạn muốn đặt trong một component, bạn có thể sử dụng spead syntax. Lưu ý rằng các component Hyperapp có thể trả về một mảng các phần tử như trong ví dụ sau. Kỹ thuật này cho phép bạn nhóm một danh sách các children mà không cần thêm các node phụ vào DOM.
Component thông thường chỉ nhận thuộc tính và component con từ component cha. Các lazy component, tương tự với hàm view, có thể nhận state và action của ứng dụng. Để tạo một lazy component, khai báo một component thông thường và trả về một hàm view bên trong component này.
Giả sử bạn có một danh sách các câu hỏi với các câu trả lời được thu gọn ban đầu. Flag answerIsOpen được sử dụng để xác định câu trả lời của câu hỏi có được mở hay không. Vì không có khái niệm local state trong Hyperapp, global state luôn được cập nhật thay vì state của một component riêng lẻ. Để cập nhật trạng thái của một câu hỏi, toàn bộ mảng câu hỏi sẽ được ánh xạ tới một mảng mới, nơi thuộc tính answerIsOpen sẽ được bật lên nếu câu hỏi khớp với câu hỏi thuộc về thành phần đó, mời xem ví dụ dưới đây:
Children Composition
Các component nhận các children element thông qua đố số thứ 2, cho phép bạn cũng như các component khác truyền children tùy ý xuống cho chúng.
constBox= ({ color }, children) => ( <divclass={`box box-${color}`}>{children}</div>)constHelloBox= ({ name }) => ( <Boxcolor="green"> <h1class="title">Hello, {name}!</h1> </Box>)
Supported Attributes
Các thuộc tính được hỗ trợ bao gồm các thuộc tính HTML, các thuộc tính của SVG, các sự kiện của DOM, các sự kiện của Lifecycle và khóa. Các thuộc tính HTML không chuẩn không được hỗ trợ, chẳng hạn onClick hay className.
Style
Thuộc tính style sẽ nhận một object thay vì string như trong HTML. Tên thuộc tính trong style sẽ được viết dưới dạng camelCase.
import { h } from"hyperapp"exportconstJumbotron= ({ text }) => ( <divstyle={{ color:"white", fontSize:"32px", textAlign: center, backgroundImage:`url(${imgUrl})` }} > {text} </div>)
Các sự kiện của Lifecycle
Có 4 sự kiện trong Lifecycle của một phần tử được quản lý bỏi virtualDOM, các sự kiện này được kích hoạt khi phần tử này được tạo, cập nhật hay bị xóa.
oncreate
Sự kiện này được kích hoạt sau khi phần tử được tạo vào gắn vào DOM. Sử dụng sự kiện này để xử lý DOM node, tạo network request hay các hiệu ứng slide/fade
Sự kiện này được kích hoạt khi chúng ta cập nhật các thuộc tính của phần tử. Sử dụng tham số oldAttributes bên trong hàm xử lý để kiểm tra xem thuộc tính nào bị thay đổi.
Sự kiện này được kích hoạt trước khi phần tử bị xóa khỏi DOM. Sử dụng sự kiện này để tạo các hiệu ứng slide/fade out. Gọi tham số done bên trong hàm xử lý để xóa phần tử. Sự kiện này sẽ không kích hoạt bên trong phần tử con của phần tử bị xóa.
import { h } from"hyperapp"exportconstMessageWithFadeout= ({ title }) => ( <divonremove={(element, done) =>fadeout(element).then(done)}> <h1>{title}</h1> </div>)
ondestroy
Sự kiện này được kích hoạt sau khi phần tử bị xóa khỏi DOM một cách trực tiếp hoặc phần tử cha của phần tử này bị xóa. Sử dụng sự kiện này để xóa bỏ các timer, hủy network request, bỏ các hàm bắt sự kiện, v..v..
Keys giúp xác định node mỗi khi DOM được cập nhật. Bằng cách chỉ định keys trên một virtual node, node đó sẽ tương ứng với một phần tử DOM nào đó. Điều này cho phép phần tử được dời đến vị trí mới nếu vị trí của nó thay đổi, thay vì bị xóa bỏ.
Keys phải duy nhất giữa các node cùng cha. Không sử dụng index của mảng làm khóa nếu index dùng để chỉ định thứ tự của các node cùng cha này. Nếu thứ tự và số lượng các phần tử này cố định thì không có vấn đề gì, nhưng nếu chúng không cố định, keys sẽ bị thay đổi mỗi khi DOM được tạo lại.