🚀 Tối ưu Frontend x100 tốc độ trong dự án thực tế [Part 1]
Trong series này, mình sẽ chia sẻ cho anh em những câu chuyện về hiệu năng Frontend mình đã gặp phải và các cách thức, chiến lược tối ưu Frontend mà mình đã áp dụng vào dự án thực tế để gia tăng tốc độ render trang web và cải thiện trải nghiệm người dùng.
Bắt đầu câu chuyện thôi anh em ...
Vào một ngày đẹp trời, mình đang ultra hỗ trợ một khách hàng thì phát hiện ra một vài vấn đề về hiệu năng Frontend khá nghiêm trọng, giao diện thì giật lag, thậm chí không thể thao tác được trong một khoảng thời gian. Điều này khiến cho cảm xúc của mình hết sức bối rối và nhận được lời chê với chữ ê khá dài từ KH "Sao phầm mềm càng ngày càng chậm thế em" ; )) Ôi nghe sao đau lòng, thực sự không phục nên ngay lập tức mình đã quay về máy công ty để tối ưu.
Trước khi đi vào diễn biến chính, mình mô tả qua đôi chút về giao diện, để anh em mình cùng nắm bắt vấn đề dễ hơn nhé.
Màn hình mình cần tối ưu ở đây là màn chi tiết Mẫu bảng lương. Trong màn này chứa một danh sách các Thành phần lương, người dùng sẽ thực hiện chỉnh sửa công thức Thành phần lương để thực hiện tính lương. Màn hình có chức năng kéo thả, thay đổi vị trí các Thành phần lương nên mình sử dụng thư viện bên thứ 3 là vuedraggable
Oke sơ qua giao diện vậy đã, anh em chỉ cần nắm được màn này là một danh sách chứa rất nhiều item bên trong.
Vấn đề 1: Khi vào màn chi tiết Mẫu bảng lương xảy ra hiện tượng trắng màn hình khoảng 3s.
KH này có gần 100 Thành phần lương trong Mẫu bảng lương. Đối với Vue thì việc render 100 item quá đơn giản. Tuy nhiên, do mình đang dùng thư viện vuedraggable nên việc render 100 item khá chậm, đó là lý do tại sao khi mới vào màn Mẫu bảng lương lại gây ra hiện tượng trắng màn hình.
Chiến lược: Infinite Scroll + giảm thiểu DOM Size
Lúc này mình nghĩ ngay đến tư tưởng Infinite Scroll để tối ưu render danh sách. Khi mới vào trang, chỉ thực hiện render 10 Thành phần lương, do đó DOM Size chỉ có 10 item được khởi tạo. Khi người dùng tiếp tục scroll gần đến bottom, mới thực hiện render 10 thằng tiếp theo và cứ lặp lại như vậy. Bản chất ở đây là DOM size càng ít thì render khởi tạo càng nhanh.
Để triển khai code, mình chỉ đơn giản tạo thêm 1 property isShow vào từng item trong mảng, và sử dụng v-if để hiển thị item trên DOM thông qua giá trị true/false từ isShow.
Kết quả: Màn hình được render khởi tạo gần như ngay lập tức khi vào trang.
Vấn đề 2: Khi bấm Lưu, nếu các Thành phần lương được đặt công thức không đúng, giao diện sẽ hiển thị thông báo công thức lỗi và KH sẽ phải thực hiện sửa lại công thức.
Vấn đề bắt đầu xảy ra từ đây, khi KH thực hiện sửa lại công thức, cứ mỗi lần gõ 1 ký tự bất kì thì phần mềm lại có hiện tượng đơ cứng, giật lag, thậm chí không thể thao tác được trong vòng 3-5s.
Chiến lược 1: Đo hiệu năng bằng tab Performance.
Mình đo hiệu năng 2 lần khi KH thực hiện sửa công thức (trước và sau khi bấm Lưu).
Trước khi bấm lưu Mẫu bảng lương, lúc này việc sửa công thức vẫn mượt mà không có hiện tượng giật lag.
Tuy nhiên, sau khi bấm lưu Mẫu bảng lương và hiển thị những công thức đang lỗi, mình thực hiện đo hiệu năng khi sửa lại công thức. Mình nhận thấy 1 điểm bất thường, function có tên là fn chạy tiêu tốn 1.15s, trong khi trước đấy chỉ tốn 4.21ms
Mình điều tra sâu thêm các hàm bên dưới hàm fn, mình phát hiện hàm dependArray được gọi rất nhiều lần và tiêu tốn nhiều thời gian.
Ảo quá, mình chả biết hàm dependArray này là gì. Sau khi tra google thì mình khá mơ hồ nên quyết định dùng chatGPT và nhận được kết quả dưới đây, nghe cũng khá hợp lý anh em à. Đại khái nó liên quan đến Reactivity trong Vue.
Đến đây mình khá oải, vì cùng 1 code UI mà trước và sau khi lưu lại có sự thay đổi về hiệu năng kinh khủng đến vậy, mà trước đấy chưa bao giờ có vấn đề gì.
Chiến lược 2: Khoanh vùng gây lỗi
Mình tiếp tục co hẹp phạm vi gây lỗi bằng cách tập trung vào đúng hàm save, mình comment lại từng đoạn code để xác định đoạn nào đang gây chậm trong luồng Lưu. Sau một hồi comment code, thật ngạc nhiên khi mình phát hiện ra đoạn code gây chậm, đó chính là hàm xử lý validate sau khi save xong.
Do code trên công ty private nên mình sẽ giả lập đoạn code tương tự dưới đây:
Lúc này mình kiểu wtf, đoạn này có gì đâu mà chậm, mình tiếp tục debug và phát hiện ra điểm mấy chốt nằm ở biến validateInfo đang được khởi tạo giá trị là [], mà thực chất giá trị đúng phải là {}. Đến đây mình có 2 câu hỏi:
- Việc khởi tạo sai giá trị mà chức năng lại vẫn chạy đúng ?
- Tại sao lại chậm lòi mà trước giờ có sao đâu ?
Để trả lời câu hỏi 1, mình thực hiện kiểm tra bằng 1 phép thử đơn giản
Anh em có thể thấy, với cả 2 cách khởi tạo là [] hay {} thì chúng ta đều có thể truy xuất được Value thông qua Key nên trước giờ app vẫn hoạt động tốt. Tuy nhiên, điểm mấu chốt ở chỗ khi anh em truyền 1 số n vào biến validateInfo_1 là anh em đang khởi tạo 1 mảng có n phần tử và vị trí thứ n chính là Value anh em gắn vào.
Để trả lời câu hỏi thứ 2, mình xuất phát từ câu trả lời phía trên, đó chính là giá trị n anh em truyền vào biến validateInfo_1. Lúc trước app không lag vì n có giá trị nhỏ, thực chất n ở đây chính là ID của các Thành phần lương trong phần mềm và giá trị ID này sẽ tự động tăng dần mỗi khi có Thành phần lương mới được insert vào database. Qua thời gian, khi nhiều thành phần lương được thêm, ID sẽ tăng lên rất lớn, cụ thể trong trường hợp của KH thì giá trị ID lúc này đang khoảng đầu số 2 triệu.
Một mảng mà có 2 triệu phần tử thì anh em biết hiệu năng thế nào rồi đấy.
Kết quả: Sau khi sửa về đúng giá trị khởi tạo là {} mọi thứ lại mượt mà trở lại.
Anh em thấy đấy, chỉ với vài đường cơ bản, mình đã biến màn hình Mẫu bảng lương đạt được tốc độ tối ưu nhanh gấp vài trăm lần, nghe ghê không anh em ; ))
Tổng kết các keyword quan trọng trong bài cho anh em:
- DOM size càng nhỏ tốc độ render càng nhanh.
- Infinite Scroll.
- Đo hiệu năng tab Performance.
- Rà soát và khoanh vùng.
Rất cảm ơn anh em đã đọc bài viết. Hi vọng bài viết sẽ giúp ích cho anh em và đừng quên đón chờ những bài viết tiếp theo của mình nha.
Anh em hãy kết nối với mình qua Youtube và LinkedIn để đọc nhiều bài viết hay khác nhé:
- Youtube: https://www.youtube.com/@pdthien
- LinkedIn: www.linkedin.com/in/pdthien
All rights reserved