Frontend performance pattern

Tại sao nên đọc bài này?

  • Các pattern thường được sử dụng ở frontend để tối ưu performance
  • Ứng dụng vào việc optimize perform cho website của bạn
  • Thuyết phục sếp và đồng nghiệp

Khi nào thì nên quan tâm tới performance?

Đầu tiên phải công nhận là khi bắt đầu thiết kế hệ thống, structure cho app nghĩa là mình đang tradeoff, mình thêm phần này và bớt phần kia, để từ đó xây dựng được một hệ thống fit nhất với problem đang giải quyết. Nếu muốn chọn hết thì sao? Gần như là không thế, vì resource là khi bắt đầu tham gia vào project luôn luôn là hữu hạn, còn problem thì muôn hình vạn trạng. Nó giống như kiểu phải có bạn gái vừa xinh, vừa thùy mị nết na, vừa bốc lửa, vừa học giỏi, vừa nữ công gia chánh, vừa abc...xyz vậy.
  • Centralize vs Decentralize
  • Monolithic vs Microservice
  • SSR vs CSR
  • OOP vs FP
  • SQL vs No-SQL
  • Language X vs Language Y
  • API vs GraphQL
  • Stream vs Batch
 
Vậy khi nào thì nên quan tâm tới Performance? Khi muốn có performance thì bạn mất những gì?
Đối với mình, một frontend app có 3 khía cạch mà cần phải quân tâm nhất
  • Functional - App của bạn chạy có đúng logic không
  • Maintainable/Readable - Chạy đúng rồi thì thằng khác lao vào sửa, thêm feature mới có dễ không
  • Performance - Code ổn rồi thì nó có nhanh không, hay là chậm rì
 
Functional thì dễ hiểu rồi, nó là thứ duy nhất không thể đánh đổi được. Vậy bây giờ app frontend trở thành cái cần gạt giữa Clean code vs Performance, tùy vào từng project sẽ cần quan tâm tới 2 yếu tố này để có thể trade off tốt hơn.
 
🐣
Vậy bạn là người trong project, bạn là người nắm rõ nhất thứ gì quan trọng hơn? Bạn muốn feature ra nhanh hơn hay bạn muốn feature của mình chạy nhanh hơn?
 
Mình đã từng rất ám ảnh về performance, và cũng từng gặp rất nhiều bạn trẻ có ảm ảnh giống mình. Có lần mình tốn gần ngày để parallel được một flow verify CSV trong khi kết quả làm xong là mình tiết kiệm được từ 15 phút run time thành 5 phút (tùy vào số core của máy). Tiếc cái là script trên chỉ chạy 1 tuần 1 lần. Nghĩa là mình bỏ ra 2 ngày, chỉ để tiết kiệm 40p một tháng cho một con server ngôi chơi hết quãng thời gian còn lại.
Cũng có lần mình phải sửa lại một flow logic react mà có form khoảng gần 20 fields, và bạn dev dùng useMemo cho tất cả các state đó, dẫn tới bug logic khi chạy mà đến bạn đó cũng không biết sửa làm sao mà phải nhờ đến free lancer để debug giúp. Sui rủi sao mình là thằng free lancer đó!
 

Performance pattern

Đây là những pattern rất phổ biến mà được nhiều người dùng để optimize performance. Và vì nó khá phổ biến nên hầu hết đều khá dễ để apply cho project của các bác mà không phải hy sinh quá nhiều về Maintainable/Readable nhé
 

Split code/Lazyload

Độ khó: Dễ
Khi nào nên apply: Nên apply ngay từ đầu project, và split từ những phần đơn giản nhất như mình có thể split theo pages. Sau này muốn tăng độ khó lên thì sẽ split code và lazy load theo user action.
Thần chú: Chỉ load những gì user cần
How: Tùy vào framework bạn đang sài là gì thì search Google với cú pháp: Framework + code splitting
Vd trên React
import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }
 

Coi xem mình có cài thêm lib nào tương tự lib có sẵn không?

Độ khó: Không khó lắm
Khi nào nên apply: Khi bạn bắt đầu suy nghĩ phải cài thêm một lib mới. Sau đó có 3 cách giải quyết
  • Bạn dùng lib có sẵn, cầu mong nó fit với những thứ mình sắp làm
  • Bạn dùng lib do bạn chọn, sửa lại code cũ, cầu mong nó fit với problem cũ, không có regression bug
  • Bạn dùng cả 2 lib ⇒ Chỉ dùng khi đây là option cuối cùng của vũ trụ gửi tới cho bạn... Kiểu như PM kề dao vào cổ bảo “mai làm xong feature nhé em”
Trong project mình đang làm có đủ cả bộ 3 thư viện kinh điển: momment, date-fnsdayjs . Và bạn hiểu cái cục momment với date-fns nó nặng như thế nào rồi đó. Always dùng dayjs nha
Thần chú: Check packages.json trước khi search một lib mới

Chọn các thư viện ES6, support tree shaking

Độ khó: Dễ, tuy nhiên phụ thuộc vào thư viện đó có support hay không
Khi nào nên apply: Mọi lúc, cữ mối lần cần tìm thư viện gì thì bundle size và có support tree shaking nên là một point cần cân nhắc
Thần chú: Thư viện chuẩn mới hơn thì xịn hơn (nhưng chưa chắc chạy đúng, ổn định hơn)
How: Check thư viện trên https://bundlephobia.com/
Thư viện redux có dung lượng 1.6kB khi Gzip và hỗ trợ tree-shaking
Thư viện redux có dung lượng 1.6kB khi Gzip và hỗ trợ tree-shaking

Debounce user input

Độ khó: Khá dễ
Khi nào lên apply: Khi mình đang hook vào event user typing hoặc scrolling để làm một tác vụ gì đó
Thần chú: Search input ⇒ Debounce
 
Tăng độ khó lên thì có thể debounce cả response API trả về nữa. Case dễ thấy là debounce response mấy cái trading/order book trên máy yếu.

Thêm tag loading=lazy cho tag img, iframe

Độ khó: Dễ
Khi nào nên apply: Hầu hết cứ bỏ vô thôi. Hình nào chắc chắn là above the fold thì thôi khỏi
Thần chú: Image + loading=lazy ⇒ ✈️
notion image

Memorized function

Độ khó: Bình thường
Khi nào nên apply: Khi function bạn chạy cần hít CPU, RAM liều cao
Thần chú: Cache the expensive task
 
Ngoài ra, bạn có thể dùng Web Worker để đẩy mấy thứ tính toán xuống background job

Cache frontend assets dùng Service Worker

Độ khó: Trung bình, khó. Lúc mới làm thì hơi mệt xíu, nhưng làm được rồi thì phê lắm
Khi nào nên apply: Khi bạn đang làm trong web app rất nặng, bundle size lớn kiểu như mấy trang admin/CRM phức tạp.
Thần chú: Web app client site phức tạp, nặng ⇒ Service Worker
VD apply cho react
 
Tin mình đi, cài thằng này xong là user gần như chỉ gặp cái vòng loading lần đầu cũng là lần cuối cùng. Sau đó mình có thể update app ở background. Mình sẽ viết một bài detail cách mình dùng Service Worker để cache assets và background update app.
 

Virtual list

Độ khó: Khá khó
Khi nào nên apply: Khi bạn có một list dài item cần render và user gần như cứ phải scroll hoài hoài thì mới xem hết được
Thần chú: Có một table hơn 100 item, hoặc đang làm new feed giống Facebook, Twitter ⇒ Virtual list
Mình recommend thằng này. Vừa mạnh vừa nhẹ vừa dễ sài. Quên mấy thằng dbrr react-window, react-virtualize đi nha.
Tuy nhiên khi dev cũng phải hiểu về cơ chế của nó kha khá, cũng như nắm chắc được khi nào react-component thì mới control được nó nha. Không là lấy kiếm tự đâm mình á

Tách một long run function thành nhiều short run function

Độ khó: Khó
Khi nào nên apply: Khi chạy function là cái máy nó đơ mọe luôn
Thần chú: Ở dòng trên
How: Bạn tách thằng function chạy lâu, tốn CPU thành nhiều thằng nhỏ bằng cách đẩy nó vào setTimeOut , requestAnimationFrame . Tuy nhiên việc bẻ một hàm to thành nhiều thằng nhỏ không phải là việc dễ, đôi khi mình cần giữ các hàm chạy sequence để đảm bảo là nó luôn chạy đúng.
 

Optimistic update

Độ khó: Dễ, Trung bình, Khó
Dễ khi chỉ apply cho những entity đơn giản, có độ sai số chấp nhận được
Trung bình khi bắt đầu có conflict giữa local state và server state gửi xuống
Khó khi vừa bị conflict mà cái entity đó còn phức tạp nữa
 
Vd: Nút like trên facebook là dễ, gửi comment là trung bình khó, đăng bài post là khó chết mọe luôn 🙂
 
Khi nào nên apply: Khi feature chấp nhận một độ sai số chấp nhận được, đơn giản, logic không quá phức tạp. Call API tỉ lệ thành công là 99,99%
Thần chú: API nào logic cực đơn giản, 99,99% thành công ⇒ Optimistic update

Lazy polyfill/Dynamic polyfill

Độ khó: Trung bình, Khó
Khi nào nên apply: Khi bạn quá vã rồi, không biết optimize cái gì nữa 🙃
Thần chú: Đâu ra một đống polyfill trong khi hầu hết user là high-tech
How: Leading đang là thằng dưới đây. Tuy nhiên cái này khá khó vì cần phải biết setup cả bên frontend lẫn backend nữa
 
Còn pattern nào bạn hay sử dụng để optimize performance không? Comment bên dưới nhé
 

Loading Comments...

Follow me @thanhledev

Xin lỗi các bạn vì thời gian qua mình không dành thời gian viết nhiều. Dạo này mình khá bận cho dự án https://getnimbus.io. Check it out 🥳