Tại sao không code “chay” JavaScript mà phải dùng framework?
Vì sao dùng thư viện / framework JavaScript khi phát triển sản phẩm?
Các bạn có thể không cần dùng tới thư viện / framework nào mà vẫn có thể làm được những web application hoàn chỉnh, tuy nhiên sau khi phát triển nhiều sản phẩm, bạn sẽ thấy rằng có nhiều đoạn mã giống nhau xuất hiện ở nhiều nơi. Nhu cầu tất yếu là tách những đoạn mã ấy ra làm một thư viện để có thể được tái sử dụng nhiều lần. Bất kỳ công ty nào cũng sẽ gặp tình huống như thế, đa số các công ty đều có một số thư viện dùng chung (shared library) của riêng mình, kể cả những công ty lớn như Facebook, Google, Microsoft lại càng như thế. Điều tuyệt vời là họ đã công khai thư viện đó cho cộng đồng mạng cùng sử dụng, chẳng hạn như React của Facebook, Angular của Google…
Code với framework khác gì với code “chay” (Vanilla JS)?
Vanilla JS nghĩa là sử dụng API gốc của JavaScript mà không thông qua thư viện nào. Điểm khác biệt lớn nhất mà các framework đem lại là chúng áp đặt các nguyên tắc và cách tư duy (mindset) cho bạn. Hãy lấy thư viện React và framework Angular làm ví dụ:
Về kiến trúc
Khi code “chay”, người ta có xu hướng dùng API JavaScript để truy cập đến các element HTML một cách tùy tiện. Khi có một thay đổi nào đó phản ánh ra giao diện, rất khó để truy ra xem đoạn mã nào đã làm điều đó. Chẳng hạn với cấu trúc HTML đơn giản như sau:
<body> <header id="topbar"></header> <main id="content"></main> <footer id="copyright"></footer> </body>
Ta có file router.js
thay đổi nội dung ở “content” khi người dùng nhấp vào menu, nhưng router.js
lại truy cập tới nội dung bên trong “topbar” để highlight cái menu item đại diện cho trang đang xem. Bên cạnh đó có file login-status.js
lại thay đổi câu chào mừng ở trong “topbar”. Không những thế còn có file footer.js
…. khoan… footer mà cũng can thiệp vào “topbar” là sao? Thì ra ở footer có 3 cái nút cho phép chọn theme của cả trang web, bao gồm màu nền của “topbar”.
Với React và Angular, lập trình viên buộc phải tư duy trừu tượng dựa trên “component”, trong đó mã nguồn HTML, CSS và JavaScript của mỗi component đều độc lập với nhau.
Ví dụ layout của React
<body> <TopBar /> <Content> <SideBar /> </Content> <Footer> <CopyRight /> </Footer> </body>
Ví dụ với Angular
<body> <top-bar></top-bar> <content> <side-bar></side-bar> </content> <footer> <copyright></copyright> </footer> </body>
Nó đặt ra quy tắc cho mã nguồn JavaScript trong một component không được phép truy cập đến HTML element ngoài phạm vi của component đó. Ví dụ component CopyRight
của React không được phép truy cập trực tiếp tới HTML element bên trong component TopBar
.
// CopyRight.js function CopyRight() { document.querySelector('header'); return (<footer></footer>); }
Về hiệu năng
Tối ưu khi cập nhật DOM
Khi code “chay”, vì được truy xuất trực tiếp tới DOM nên trong mỗi chức năng người ta cứ vô tư cập nhật nội dung của các HTML element. Khi làm app có giao diện phức tạp và nội dung thay đổi realtime, rất dễ xảy ra tình trạng giật lag.
React, Angular và Vue đều có cơ chế phát hiện những chỗ cần thay đổi (change detection) và chỉ cập nhật những gì cần thiết lên DOM, không cho lập trình viên can thiệp trực tiếp vào DOM, giúp cho trình duyệt đỡ gánh nặng phải render trang web liên tục, dẫn đến lag giao diện.

Rò rỉ bộ nhớ (memory leak)
Không chỉ như thế, thao tác trực tiếp với DOM còn dễ gây ra memory leak nếu không cẩn thận, đặt biệt với ứng dụng trên một trang (SPA). Hãy cùng xét ví dụ rất quen thuộc là thay đổi nội dung ở một phần của trang web mà không cần tải lại trang:
Trang “Giới thiệu”
<body> <main id="content"> <article id="intro"> Giới thiệu </article> </main> </body>
Trang “Thông báo”
<body> <main id="content"> <article id="announce"> Thông báo </article> </main> </body>
Kỹ thuật này quá quen thuộc: mỗi khi cần thay đổi nội dung thì chỉ cần gỡ node con của main#content
ra và thay bằng node mới. Nhưng rắc rối sẽ xảy ra khi có một biến đang nắm giữ node cũ và biến ấy không bao giờ được giải phóng vùng nhớ.
elem = document.querySelector('#intro')
Đây là biến tự động toàn cục, vì nó được gán giá trị mà không được khai báo với từ khóa var
, let
hay const
. Biến này sẽ sống mãi đến khi trang web được refresh, nếu node #intro
rất đồ sộ thì sẽ chiếm nhiều bộ nhớ. Tình hình sẽ tệ hơn nữa nếu sai lầm này lặp lại ở nhiều nơi.
Data binding
Data binding được ứng dụng rất nhiều trong web application ngày nay, cho nên việc code “chay” sẽ dẫn đến nhiều đoạn code lặp đi lặp lại (boilerplate).
<body> <main id="content"> <input id="txtName" type="text"> You name is <span id="spName"></span> </main> </body>
Những thư viện JavaScript ngày nay cho phép chúng ta dễ dàng đồng bộ giữa input#txtName
và span#spName
, khi người dùng nhập thông tin vào textbox thì data được hiển thị ở chỗ khác ngay tức thì. Nếu code “chay” thì đoạn mã sau boilerplate sau sẽ phải lặp lại ở những chỗ cần bind data:
let txtName = document.querySelector('#intro'); let spName = document.querySelector('#spName'); txtName.addEventListener('change', () => { spName.innerHTML = txtName.value; });
Tổ chức code
Vấn đề lớn nhất khi code “chay” là phải sắp xếp các thẻ <script>
theo đúng thứ tự được sử dụng. Lập trình viên phải tự theo dõi và nhớ xem hàm nào được dùng ở file nào, file nào phải được tham chiếu trước để những file sau đó sử dụng v.v.
Khi số lượng component lên đến vài chục sẽ tạo thành rắc rối lớn, lập trình viên khó phát hiện ra code dư thừa, còn trình duyệt web thì phải tải rất nhiều file rời rạc trong mỗi lần reload, kể cả những đoạn mã dư thừa.
Để mã nguồn chạy được trên nhiều loại trình duyệt, lập trình viên phải sử dụng chuẩn JavaScript phiên bản cũ nhất, và không biết khi nào nên sử dụng chức năng ngôn ngữ mới.
<head> <script src="./lib/lib01.js"></script> <script src="./lib/lib02.js"></script> <script src="./top-menu.js"></script> <script src="./slideshow.js"></script> <script src="./photo-popup.js"></script> <script src="./menu-stack.js"></script> </head>
Framework Angular có kèm sẵn một dàn vũ khí hỗ trợ dev tối tân (Dev Tools), có thể gỡ bỏ những gánh nặng đã kể trên, đối với những thư viện khác như React và Vue thì lập trình viên phải tự tích hợp những Dev Tools khác nhau, nhưng nói chung cũng khá dễ dàng. Chúng ta sẽ điểm qua các Dev Tool trong quá trình học về một framework cụ thể, những tác dụng chung của chúng là:
- Dễ quản lý sự phụ thuộc giữa các file
- Tự động loại bỏ code thừa (tree-shaking)
- Gom nhiều file thành ít file hơn (bundling)
- Khi nào cần file nào mới tải (lazy-loading)
- Code với chuẩn ES mới nhất (transpiling)
- Kiểm tra kiểu dữ liệu của biến (type-checking)
Bạn không cần thư viện hoặc framework, nếu…
- Ứng dụng nhỏ chỉ cần một, hai người làm và bảo trì.
- Ứng dụng yêu cầu performance cực cao, đòi hỏi phải viết mọi thứ từ API gốc.
- Công ty của bạn có nguyên tắc và kiến trúc rõ ràng cho web application, và các lập trình viên tự giác tuân thủ những nguyên tắc đó (hoặc có thể bị ép buộc một chút bằng dev tool).
- Tất cả các lập trình viên đều biết cách viết code tối ưu và tránh memory leak.
- Có thư viện nội bộ gồm những hàm có thể được tái sử dụng nhiều lần.
Trên thực tế có không ít công ty như thế, chứ không phải là không có
Bạn cần sử dụng thư viện / framework từ cộng đồng, nếu…
Muốn tận dụng kiến trúc cơ sở hạ tầng đã được liên tục cải tiến qua thời gian lâu, có nhiều công ty trên thế giới đã sử dụng và chứng minh hiệu quả.
Để cho các lập trình viên tập trung vào nghiệp vụ (business) của ứng dụng, trong khi an tâm rằng phần cơ sở vẫn được tối ưu nhất có thể.
Ứng dụng lớn cần một nhóm phát triển và bảo trì.
Facebook Comments