Engineering

2 kĩ thuật đơn giản để optimize React render

Lễ được nghỉ dài ngày nhưng lại trúng mùa dịch COVID-19, cũng không được đi đâu chơi nên thôi ở nhà viết blog cho các bạn. Bài viết hôm nay sẽ chia sẻ cho các bạn 2 kĩ thuật đơn giản để optimize React render.

Giới thiệu

Chắc hẳn một số bạn đã từng gặp những khung search "giật lag" hoặc các input text khi gõ chữ rồi nhưng vài giây sau mới hiện trên giao diện. Đó là vấn đề mà chúng ta sẽ khắc phục ngày hôm nay. Optimize React render có thể hiểu là việc loại bỏ các lượt render thừa, giúp tăng tốc độ của ứng dụng cũng như gia tăng trải nghiệm của người dùng.

Các "component" thường được optimize là form (tất nhiên là tính luôn cả input bên trong) và table, đặc biệt là datatable với dữ liệu trích xuất từ API.

Lưu ý: Chỉ optimize khi ứng dụng của bạn có dấu hiệu chậm. Lạm dụng optimize quá nhiều đôi khi sẽ phản tác dụng và làm ứng dụng của bạn chậm hơn so với lúc chưa optimize cũng như phát sinh một số lỗi đi kèm (do sơ xuất trong quá trình thực hiện), rất khó để debug.

Chuẩn bị

Cài đặt extension React Developer Tools (Firefox | Google Chrome) để kiểm tra và hỗ trợ quá trình optimize.

Các kĩ thuật bao gồm:

Github repo được sử dụng trong bài viết.

  • Nhánh main: code chưa optimize
  • Nhánh optimized: như tên nhánh

Repo chủ yếu nhằm mục đích demo nên tổ chức khá đơn giản:

  • components/index.js: khai báo các component sẽ sử dụng trong trang, chủ yếu là form input được styled bằng Tachyons CSS
  • pages/index.js: trang chính, sử dụng các components đã build để dụng một form đơn giản bao gồm name, emailbutton để submit form

1. Xài gì truyền nấy

Hạn chế việc truyền dư props cho component.

Mình gặp rất nhiều trường hợp (tất nhiên là có mình của "ngày hôm qua") truyền dư props cho component, đặc biệt là các wrapper component. Ví dụ

Như các bạn thấy thì component Dashboard chỉ sử dụng isMobile. Nó chủ yếu là trung gian để truyền props tới các component con, cụ thể là SearchInputDatatable.

Để giải quyết vấn đề này, chúng ta có thể viết lại như sau:

Bên trên chỉ là code ví dụ để các bạn dễ hình dung, không có trong repo đâu nhé 🙂

Tuy không trực tiếp giải quyết vấn đề render thừa nhưng mình nghĩ đây là một thói quen tốt mà các bạn nên áp dụng. Tất nhiên mọi thứ luôn có 2 mặt của nó, nếu các bạn muốn tìm hiểu sâu hơn thì có thể sử dụng từ khóa props drilling in react để đọc thêm nhé.

2. Profiling với React Developer Tools

Các bước hướng dẫn bên dưới chỉ áp dụng cho repo mẫu của mình. Các bạn tự điều chỉnh lại theo repo của các bạn nhé.

Start ứng dụng ở development mode với lệnh yarn dev. Truy cập vào ứng dụng tại http://localhost:3000

Mở Chrome DevTools hoặc Firefox Developer Tools (phím tắt F12 hoặc right-click vào trang và chọn Inspect)

Chọn Profiler như hình:

Nhấn vào icon bánh răng (cog) và thiết lập như trong hình:



Sau khi thiết lập, các bạn có thể click vào icon tròn màu xanh dương để bắt đầu profile.

Để profile hiệu quả, bạn chỉ nên nhập một input bạn nghi ngờ là lý do làm cho ứng dụng bị chậm. Ở đây mình chọn input name (Họ và tên). Sau khi nhập xong, nhấn vào icon tròn màu đỏ để dừng profile.

Kết quả sẽ là một biểu đồ (chart) như hình, gọi là Flamegraph chart. Đây là biểu đồ thể hiện thời gian render của các component. Màu càng nhạt đồng nghĩa với việc component render càng ít (màu xám là không render, màu đỏ là render lâu nhất).

Bạn có thể chuyển qua Ranked chart để xem chi tiết thứ hạng render (sắp xếp từ lâu nhất tới nhanh nhất từ trên xuống) cũng như chi tiết vì sao component render bằng cách hover lên component muốn xem.


Như các bạn thấy, tuy mình chỉ đổi duy nhất name (Họ và tên) nhưng email và các component khác cũng render theo.

Trong demo thì việc này là chấp nhận được vì chủ yếu các component đều phục vụ mục đích styling, không xử lý logic quá nhiều. Thế nhưng trong thực tế sẽ xuất hiện một số component xử lý rất nhiều logic, thậm chí là có gọi API phía bên trong nên việc render dư thừa sẽ ảnh hưởng ít nhiều tới performance của trang.

Khi đã xác định được nguyên nhân render của component, mình sẽ bắt đầu optimize.

Optimize

1. React.memo

Nếu sử dụng Class Component, bạn có thể sử dụng Pure Component để thay thế cho React.memo

React.memohigher-order component (HOC) phục vụ cho mục đích optimize performance. React.memo sẽ "nhớ" lần render cuối của component và props tại thời điểm đó. Chỉ khi props thay đổi thì React.memo mới thực hiện render lại component.

Mình sẽ áp dụng nó để optimize Heading, StyledInput, StyledLabelStyledButton. Mình không áp dụng cho Wrapper hay StyledForm vì chúng là "wrapper" component, khi children thay đổi thì chúng sẽ render lại, mà tần suất thay đổi của các component bên trong khá là cao nên có "nhớ" thì cũng không có tác dụng gì.

Tiến hành profile lại sau khi optimize bằng React.memo, mình nhận thấy Heading, StyledLabel cũng như StyledButton không còn render nữa. Tốt hơn rất nhiều rồi phải không nào? Tuy nhiên, email input vẫn render vì props onChange thay đổi.

Tại sao onChange lại thay đổi?

Với đoạn code như trên, mỗi lần Home render lại, một function handleEmailChange (props onChange) lại được tạo ra khiến cho StyledInput của email bị thay đổi. Chúng ta có thể optimize vấn đề này bằng kĩ thuật tiếp theo: React.useCallback

2. React.useCallback

Cũng giống như cách React.memo hoạt động, React.useCallback sẽ "nhớ" function thay vì lần cuối render. Nếu React.memo thay đổi dựa trên props thì React.useCallback sẽ thay đổi dựa trên danh sách dependencies được khai báo.

handleEmailChange sẽ được viết lại như sau:

Do không phụ thuộc vào bất kỳ biến nào (từ props hoặc các thư viện bên ngoài, vd: router) nên dependencies của mình sẽ là danh sách rỗng []. setEmail do là state nên sẽ không đổi, các bạn có thể khai báo hoặc không đều được. Sẵn tiện thì mình áp dụng luôn cho handleNameChange nhé.

Kết quả profile sau khi áp dụng React.useCallback:

Lời kết

Chỉ với vài thao tác đơn giản, các bạn đã loại bỏ được khá nhiều lượt render thừa trong ứng dụng của mình. Ngoài các kĩ thuật được chia sẻ trong bài viết, các bạn có thể đọc thêm về React.useMemo hoặc debounce/throttle để optimize sâu hơn nữa nhé.

Hi vọng qua bài viết các bạn sẽ tự optimize performance cho các ứng dụng hiện tại của mình. Nếu có bất kì thắc mắc hoặc góp ý thì cứ comment lại bên dưới cho mình nhé. Đừng quên giữ gìn sức khỏe cho mình và người thân nhé trong lúc tình hình dịch đang diễn biến phức tạp.

Happy hacking!

Mình cũng có viết chuỗi bài Frontend Du Ký, kể về hành trình phát triển sản phẩm của team Frontend EGANY đã đi qua. Bạn cũng có thể ghé qua xem thử, mình tin nó sẽ giúp các bạn bớt "giẫm mìn" hơn rất nhiều

Nếu bạn thấy hành trình bọn mình thú vị và muốn cùng nhau viết tiếp thì xem thêm tại đây nhé.

Thành Nguyễn

Bug Contributor at EGANY.com

Recent Posts

AWS #3 – Kinh nghiệm triển khai EC2

Trong phần trước, mình đã chia sẻ với mọi người những khó khăn khi team…

3 years ago

Microservices #4: Làm việc đa môi trường và sự chờ đợi của Frontend

Xin chào, tiếp tục loạt bài về trải nghiệm của mình trong việc xây dựng…

3 years ago

Frontend Du Ký S2E3 | Cross-platform Apps (phần 3): Có công mài sắt, có ngày release

Cắm đầu cắm cổ dọn dẹp deadline và dọn nhà trước Tết nên ngâm bài…

3 years ago

Front-end Engineer (Reactjs, Nextjs, TypeScript, Svelte, TailwindCSS..)

Nếu bạn yêu thích phát triển sản phẩm với nhiều thử thách và cơ hội…

3 years ago

AWS #2 – Vấn đề và giải pháp

Trong phần 1, mình đã giải thích lý do tại sao EGANY chọn AWS làm…

3 years ago

AWS #1 – Sự lựa chọn của EGANY

Trong giới công nghệ hiện nay, Amazon Web Service không còn là một cái gì…

3 years ago