Này người anh em, đã bao giờ anh em code react mà dùng index để làm key như thế này chưa:
{items.map((item, index) => ( <div key={index}>{item}</div>
))}
Nếu tôi nói cách code này gây ảnh hưởng đến hiệu suất thì anh em biết tại sao không? Vậy cùng Sydexa đào thật sâu về cách React hoạt động và trả lời câu hỏi trên nha.
Hiện tại, Sydexa đang khuyễn mãi 50% cho khoá học React nâng cao - chuyên sâu tối ưu hiệu năng cho những bạn đăng ký sớm. Khoá học được hướng dẫn bởi mentor dày dặn kinh nghiệm và phù hợp với những học viên muốn nâng cao kiến thức để có thể đảm nhận những vị trí cao hơn trong công việc.
Có thể anh em đã nghe qua, một trang web ví dụ như này, thực chất được xây dựng từ một cấu trúc nền tảng gọi là DOM – viết tắt của Document Object Model.
Mỗi phần tử HTML là một nút trong cây DOM.
Ví dụ mã html như này:
<nav> <ul> <li><a href="#">HOME</a></li> <li><a href="#">ABOUT</a></li> <li><a href="#">SIGN IN</a></li> <li><a href="#" style="color: white; background: hotpink;">SUBSCRIBE</a></li> </ul> </nav> <section id="posts"> <div class="post"> <img src="zendesk.png" alt="Zendesk logo" /> <h2>Zendesk cắt giảm 80% chi phí lưu trữ</h2> <p>Chuyển đổi cơ sở dữ liệu giúp tiết kiệm đáng kể</p> <button>Read More</button> </div> </section>
Thì cây dom tương ứng được xây sẽ như này:
Document └── <html> ├── <head> └── <body> ├── <nav> │ └── <ul> │ ├── <li><a>HOME</a></li> │ └── ... └── <section id="posts"> └── <div class="post"> ├── <img /> ├── <h2>Zendesk cắt giảm 80%...</h2> ├── <p>Chuyển đổi cơ sở dữ liệu...</p> └── <button>Read More</button>
Mã javascript của anh em có thể thao tác để chỉnh sửa, tìm kiếm trên cây DOM này
Việc cập nhật trực tiếp các phần tử trong DOM không hề nhẹ nhàng. Mỗi khi cấu trúc DOM bị thay đổi, trình duyệt có thể phải kích hoạt một quá trình reflow (tính toán lại bố cục và hình học của các phần tử) và một quá trình repaint (vẽ lại các pixel của các phần tử lên màn hình). Những thao tác này có thể tiêu tốn đáng kể hiệu năng, đặc biệt là trong các ứng dụng web phức tạp hoặc có nhiều tương tác động
Và React bước vào và đưa ra một cách làm thông minh hơn. Thay vì ban đầu làm việc trực tiếp so sánh, tìm kiếm với DOM chính, thì sao chúng ta không làm việc với 1 bản sao gọn nhẹ của nó nhỉ?
Và đó chính là Dom ảo hay còn gọi và virtual dom mà anh em hay nghe
Bản chất nó đơn giản chỉ là một đối tượng javascript như sau:
const virtualDOM_v1 = { type: 'div', props: { className: 'counter-app', children: [ { type: 'h1', props: { children: 'Count: 0' } }, { type: 'button', props: { onClick: () => { /* function to update state */ }, children: 'Increment' } } ] }
};
Virtual Dom như một bản sao của DOM thực tế. Và việc cập nhật virtual dom sẽ nhanh và hiệu quả hơn nhiều so với DOM thực tế vì việc cập nhật virtual DOM không yêu cầu xử lý trình duyệt web nặng nề như vẽ lại và điều chỉnh không gian. Nó liên quan đến việc cập nhật trực tiếp đối tượng JavaScript.
Ví dụ trên khi người dùng bấm nút, biến count tăng lên một, giao diện cần thay đổi, thì điều gì sẽ diễn ra?
React sẽ không chạm vào dom thật mà nó sẽ tạo một Virtual Dom mới trong bộ nhớ cho giao diện mới.
Rồi quá trình đối chiếu diễn ra: React so sánh virutal dom mới và virtual dom cũ để xác định thành phần nào vừa thay đổi và cần render lại
const virtualDOM_v1 = { type: 'div', props: { className: 'counter-app', children: [ { type: 'h1', props: { children: 'Count: 0' } }, { type: 'button', props: { onClick: () => { /* function to update state */ }, children: 'Increment' } } ] }
}; // VDOM mới khi count = 1 (đã được đơn giản hóa)
const virtualDOM_v2 = { type: 'div', props: { className: 'counter-app', children: [ { type: 'h1', props: { children: 'Count: 1' } // <-- Đã thay đổi! }, { type: 'button', props: { onClick: () => { /* function to update state */ }, children: 'Increment' } } ] }
};
Và cuối cùng thực hiện thay đổi trên DOM thực tế
Mặc dù có thể xảy ra nhiều thay đổi trạng thái trong quá trình xử lý, nhờ cơ chế điều phối thông minh, DOM thực tế chỉ được cập nhật một lần sau khi so sánh hoàn tất. Điều này giúp tối ưu hiệu suất hiển thị và mang lại trải nghiệm người dùng mượt mà
Vậy qua trình so sánh trên diễn ra như nào?
React không sử dụng thuật toán ưu tiên chỉ tìm ra các thành phần thay đổi mà thay vào đó là một thuật toán không hoàn hảo bằng nhưng lại cực kỳ nhanh và "đủ tốt" cho hầu hết mọi trường hợp sử dụng UI.
Thuật toán sẽ dò và so sánh các phần tử, đầu tiên nó kiểm tra loại phần tử. Nếu các loại này khác nhau. ví dụ: một div
thay đổi thành một section. React sẽ không cố gắng so sánh các con hay thuộc tính của chúng. mà sẽ coi là bộ cây con đã thay đổi, buộc phải xây dựng lại hoàn toàn
// Nếu cấu trúc này...
<div> <Sydexa />
</div> //...thay đổi thành cấu trúc này trong lần render tiếp theo...
<section> <Sydexa />
</section> // Thì instance của component Counter sẽ bị phá hủy và một instance mới được tạo ra, reset lại trạng thái của nó.
Trường hợp 2:
Nếu loại phần tử (element type) không thay đổi giữa các lần render, React sẽ giữ nguyên node DOM tương ứng, chỉ cập nhật những thuộc tính (props) đã thay đổi và tái sử dụng toàn bộ cây con (children) nếu không có sự khác biệt.
// Lần render đầu tiên:
<div> <Sydexa />
</div> // Lần render tiếp theo:
<div> <Sydexa version="2.0" />
</div>
Trường hợp 3:
Chính là câu hỏi ở đầu bài viết 😗
React sẽ đệ quy vào một mảng gồm các nút con và so sánh dựa trên vị trí của chúng
Ví dụ:
// Render ban đầu:
<ul>
<li>Database</li>
<li>Backend</li>
</ul> // Render tiếp theo (thêm một mục vào đầu):
<ul>
<li>Devops</li>
<li>Database</li>
<li>Backend</li>
</ul>
Trong trường hợp này, React so sánh thẻ list: Database cũ ở key 0 với thẻ list: Devops mới ở key 0. Nó thấy có sự khác biệt, thuật toán sẽ đánh dấu và sẽ thay đổi DOM ở vị trí đó. Tương tự, khi so sánh phần tử thứ hai, thẻ list: Backend, và thẻ list:
Database , nó lại thấy hai thẻ khác nhau và cần tạo lại phần đó trên DOM. Phương pháp này dẫn đến việc toàn bộ danh sách bị tái tạo lại khi một phần tử đầu tiên thay đổi, dẫn đến việc tốn tài nguyên không cần thiết
Để giải quyết vấn đề này thì Thuật toán so sánh của React đủ thông minh để dựa vào key
nhằm xác định chính xác phần tử nào đã thay đổi, từ đó chỉ cập nhật những phần cần thiết trong DOM. Cơ chế này giúp tăng hiệu quả kết xuất và giảm thiểu thao tác không cần thiết."
"Chính vì vậy, React luôn đưa ra cảnh báo khi bạn quên cung cấp key
cho các phần tử trong danh sách — bởi thiếu key
sẽ khiến quá trình so sánh trở nên kém chính xác và ảnh hưởng đến hiệu suất."
Và vì vậy nhiều anh em thường sai lầm khi dùng index (số thứ tự của phần từ trong mảng) làm key
{items.map((item, index) => ( <div key={index}>{item}</div>
))}
thì khi danh items thay đổi, các phần tử khác lại có key trùng với key của phần tử trong virtual dom cũ và có thể dẫn đến các hành vi không mong muốn khi danh sách thay đổi — như mất trạng thái của component con, cập nhật sai nội dung, hoặc gây nhấp nháy không cần thiết trong UI.
Và đó là một chút đào sâu về React dành cho anh em frontend developer, còn rất nhiều thứ hay ho về tối ưu hiệu năng và đào sâu nữa bọn mình sẽ chia sẻ thêm trong thời gian tới nha.