Tuyệt vời! Đây là bài viết blog về Kiến trúc Fiber của React, được viết bằng tiếng Việt, tuân thủ các yêu cầu của bạn.
Chào mừng bạn quay trở lại với hành trình khám phá thế giới React cùng series “React Roadmap”! Chúng ta đã cùng nhau đi qua rất nhiều kiến thức nền tảng quan trọng, từ việc React là gì, hiểu về JSX, làm quen với Props và State, cách render có điều kiện, đến việc kết hợp Component và quản lý vòng đời của chúng, sử dụng Hooks như useState và useEffect, và thậm chí là các kỹ thuật nâng cao hơn như Custom Hooks hay các giải pháp quản lý state phức tạp. (Nếu bạn bỏ lỡ phần nào, hãy quay lại xem Lộ trình học React 2025 nhé!).
Chúng ta thường tương tác với React ở tầng component, state, props, và hooks. Nhưng có bao giờ bạn tự hỏi, điều gì đang xảy ra “dưới mui xe” để React có thể cập nhật giao diện (UI) một cách nhanh chóng và hiệu quả đến vậy không? Đặc biệt là khi ứng dụng của bạn ngày càng lớn và phức tạp, chứa hàng trăm, thậm chí hàng nghìn component?
Câu trả lời nằm ở một khái niệm cốt lõi, là trái tim của React hiện đại: Kiến trúc Fiber. Mặc dù là một chi tiết triển khai nội bộ của React, việc hiểu về Fiber sẽ giúp bạn có cái nhìn sâu sắc hơn về cách React hoạt động, giải thích tại sao một số tính năng lại khả thi (như Suspense hay Concurrent Mode), và giúp bạn debug các vấn đề về hiệu năng hiệu quả hơn.
Trong bài viết này, chúng ta sẽ cùng nhau “giải mã” Fiber, biến nó từ một khái niệm nghe có vẻ phức tạp thành một điều gì đó dễ hiểu và thú vị. Hãy cùng bắt đầu nhé!
Mục lục
Vấn Đề Của Thế Giới “React Cũ”: Stack Reconciler
Trước khi Fiber ra đời (trong các phiên bản React 15 trở về trước), React sử dụng một kiến trúc gọi là Stack Reconciler. “Reconciliation” (Đối soát/Hòa giải) là quá trình React quyết định xem UI cần được cập nhật như thế nào sau khi state hoặc props thay đổi. Nó so sánh cây element mới với cây element cũ (hay còn gọi là Virtual DOM) để tìm ra sự khác biệt và chỉ cập nhật những phần cần thiết trên DOM thật.
Stack Reconciler hoạt động theo kiểu truyền thống của ngăn xếp (stack): khi React bắt đầu xử lý một bản cập nhật (ví dụ: khi bạn gọi setState
hoặc dispatch một action Redux), nó sẽ duyệt toàn bộ cây component *một cách đồng bộ* (synchronous) để tính toán những thay đổi cần thiết. Quá trình này giống như việc gọi một hàm và phải chờ hàm đó hoàn thành toàn bộ công việc của nó trước khi có thể làm bất cứ điều gì khác.
Tưởng tượng thế này: bạn có một danh sách rất dài các nhiệm vụ cần làm. Với cách làm cũ (Stack), bạn phải làm *toàn bộ* danh sách đó từ đầu đến cuối mà không được dừng lại, không được ưu tiên nhiệm vụ nào hơn. Nếu danh sách quá dài hoặc một nhiệm vụ nào đó mất nhiều thời gian, bạn sẽ bị kẹt lại, không thể phản hồi các yêu cầu khác (ví dụ: tương tác của người dùng).
Nhược điểm lớn nhất của Stack Reconciler:
- Không thể bị ngắt (Non-interruptible): Khi quá trình reconciliation bắt đầu, nó sẽ chạy liên tục cho đến khi hoàn thành. Nếu cây component đủ lớn hoặc các component chứa logic tính toán phức tạp, quá trình này có thể chiếm giữ luồng chính (main thread) của trình duyệt trong một khoảng thời gian đáng kể.
- Giao diện bị “đơ” (Janky UI): Khi luồng chính bị bận, trình duyệt không thể xử lý các công việc quan trọng khác như phản hồi sự kiện của người dùng (click, gõ phím), chạy animation, hoặc cập nhật UI một cách mượt mà ở 60 khung hình/giây. Điều này dẫn đến giao diện bị giật, lag, gây khó chịu cho người dùng.
- Không có khả năng ưu tiên: Mọi bản cập nhật đều được xử lý với mức độ ưu tiên như nhau. React không thể ưu tiên các tương tác quan trọng (như animation, gõ input) so với các cập nhật nền (như tải dữ liệu).
Đây chính là động lực để React phát triển một kiến trúc mới, có khả năng xử lý các bản cập nhật một cách linh hoạt và hiệu quả hơn.
Fiber Ra Đời: Cuộc Cách Mạng Dưới Lớp Vỏ React
Fiber Reconciler (được giới thiệu từ React 16) là một cuộc viết lại hoàn toàn kiến trúc cốt lõi của React. Mục tiêu chính là giải quyết các hạn chế của Stack Reconciler, cho phép React:
- Chia nhỏ quá trình rendering thành các đơn vị công việc nhỏ hơn.
- Tạm dừng, hủy bỏ, hoặc tiếp tục công việc rendering khi cần.
- Ưu tiên các loại cập nhật khác nhau.
- “Treo” quá trình rendering và tiếp tục sau khi dữ liệu đã sẵn sàng (điều này làm nền tảng cho Suspense).
- Sử dụng nhiều luồng (tưởng tượng) hoặc xử lý công việc “bên lề” để giải phóng luồng chính.
Trở lại với ví dụ danh sách nhiệm vụ: Với Fiber, bạn vẫn có danh sách đó, nhưng giờ bạn có thể chia nhỏ từng nhiệm vụ thành các bước nhỏ hơn. Bạn có thể làm một bước của nhiệm vụ A, rồi chuyển sang làm một bước của nhiệm vụ B (vì B quan trọng hơn), rồi quay lại làm tiếp nhiệm vụ A. Bạn có thể tạm dừng, ghi chú lại đang làm đến đâu, và tiếp tục sau. Điều này giúp bạn luôn phản ứng được với các yêu cầu khẩn cấp (tương tác người dùng) mà vẫn hoàn thành được các công việc dài hơn.
Vậy “Fiber” Là Gì?
Trong kiến trúc Fiber, “Fiber” (sợi) là đơn vị công việc chính. Về cơ bản, mỗi Fiber là một đối tượng JavaScript đại diện cho:
- Một instance của component (functional hoặc class).
- Một element DOM (host component như
div
,span
, v.v.). - Hoặc một phần công việc cần thực hiện (như chạy effect, cập nhật state).
Mỗi Fiber object lưu trữ thông tin về component tương ứng, state của nó, props, và đặc biệt là các con trỏ liên kết nó với các Fiber khác để tạo thành một cấu trúc cây (giống như cây DOM hoặc cây component):
child
: Con đầu tiên của Fiber hiện tại.sibling
: Anh/em liền kề của Fiber hiện tại.return
(hoặcparent
): Fiber cha của Fiber hiện tại.
Ngoài ra, mỗi Fiber còn mang thông tin quan trọng khác như:
type
: Kiểu của element (ví dụ: function component, class component, ‘div’, ‘span’).stateNode
: Tham chiếu đến instance của component hoặc node DOM thật (nếu có).pendingProps
,memoizedProps
: Props mới và cũ.updateQueue
: Hàng đợi các bản cập nhật state, effects, v.v.effectTag
: Cờ đánh dấu loại “side effect” cần thực hiện trong giai đoạn Commit (ví dụ: thêm, xóa, cập nhật DOM, chạy effect).alternate
: Tham chiếu đến Fiber tương ứng trong cây Fiber *khác*.
Nghe có vẻ phức tạp? Đừng lo. Điều quan trọng cần nhớ là:
- Fiber là đơn vị làm việc nhỏ nhất của React.
- React xây dựng một cây Fiber để biểu diễn trạng thái hiện tại của ứng dụng và một cây Fiber “đang làm việc” (work-in-progress) để tính toán các bản cập nhật.
- Việc chuyển đổi từ Stack sang Fiber giống như việc thay đổi cách React “đi bộ” qua cây component/element để thực hiện công việc. Thay vì đi bộ sâu vào một nhánh đến cuối rồi mới quay lại (Stack), Fiber cho phép React đi bộ theo từng bước nhỏ, ngang qua các nhánh, và có thể dừng lại bất cứ lúc nào.
Cách Fiber Hoạt Động: Hai Giai Đoạn Chính
Fiber Reconciler làm việc thông qua một vòng lặp (work loop) và chia quá trình xử lý thành hai giai đoạn chính:
Giai đoạn 1: Render/Reconciliation (Còn gọi là Giai đoạn Work-in-Progress)
Đây là giai đoạn React xây dựng cây Fiber “đang làm việc” (work-in-progress tree) bằng cách duyệt qua cây Fiber hiện tại và so sánh nó với cây element mới (Virtual DOM mới). Quá trình này diễn ra như sau:
- React bắt đầu từ Fiber gốc (thường là Fiber của component App).
- Nó duyệt xuống theo thứ tự ưu tiên (depth-first search, nhưng có thể bị ngắt).
- Tại mỗi Fiber node, React thực hiện “công việc” (work):
- Gọi hàm của function component hoặc phương thức
render
của class component. - Tính toán state và props mới.
- So sánh element mới trả về với Fiber con hiện tại để xem có thay đổi gì không (quá trình diffing).
- Nếu có thay đổi (hoặc cần chạy effects), React đánh dấu Fiber đó bằng một
effectTag
(ví dụ:Placement
– thêm,Deletion
– xóa,Update
– cập nhật,Callback
– chạy lifecycle/effects). - Xây dựng Fiber “alternate” (đang làm việc) và liên kết nó vào cây work-in-progress.
- Gọi hàm của function component hoặc phương thức
- Sau khi xử lý xong một Fiber và các Fiber con của nó, React quay lại Fiber cha để xử lý Fiber anh em (sibling) tiếp theo.
- Điểm mấu chốt: Sau khi xử lý xong một hoặc một vài Fiber, React có thể tạm dừng công việc nếu thời gian cho phép đã hết hoặc có công việc ưu tiên cao hơn cần xử lý (ví dụ: tương tác người dùng). Nó ghi lại đang làm đến đâu và trả lại quyền điều khiển cho trình duyệt. Khi có thời gian rảnh trở lại, React sẽ tiếp tục công việc từ chỗ đã dừng.
- Quá trình này tiếp tục cho đến khi toàn bộ cây work-in-progress được xây dựng và tất cả các thay đổi (effects) được xác định.
Kết thúc Giai đoạn Render, React có một cây Fiber “đang làm việc” đã hoàn chỉnh và một danh sách các “effects” (effect list) – chính là những Fiber được đánh dấu cần thực hiện thay đổi.
Giai đoạn Render có thể bị tạm dừng hoặc hủy bỏ. Nếu một bản cập nhật mới có mức độ ưu tiên cao hơn đến trong khi Giai đoạn Render đang diễn ra, React có thể hủy bỏ công việc hiện tại, xử lý bản cập nhật ưu tiên cao hơn, và sau đó (nếu cần) bắt đầu lại công việc đã bị hủy bỏ.
Giai đoạn 2: Commit
Sau khi Giai đoạn Render hoàn thành thành công (không bị hủy bỏ), React chuyển sang Giai đoạn Commit. Giai đoạn này diễn ra *một cách đồng bộ* (synchronous) và không thể bị ngắt. Trong giai đoạn này, React thực hiện các thay đổi thực tế trên DOM của trình duyệt dựa trên danh sách effects đã thu thập ở Giai đoạn Render.
Các công việc trong Giai đoạn Commit bao gồm:
- Thêm, xóa, hoặc cập nhật các node DOM thật dựa trên
effectTag
của các Fiber. - Chạy các lifecycle methods như
componentDidMount
,componentDidUpdate
,componentWillUnmount
(đối với class components). - Chạy các hooks như
useLayoutEffect
(đồng bộ với DOM) vàuseEffect
(sau khi DOM đã được cập nhật và trình duyệt đã “vẽ” lại). (Chúng ta đã tìm hiểu vềuseState
vàuseEffect
trong bài trước).
Khi Giai đoạn Commit kết thúc, cây Fiber “đang làm việc” (work-in-progress tree) sẽ trở thành cây Fiber “hiện tại” (current tree), sẵn sàng cho bản cập nhật tiếp theo. Giao diện người dùng trên trình duyệt giờ đây đã phản ánh trạng thái mới nhất của ứng dụng.
Bảng So Sánh: Stack Reconciler vs Fiber Reconciler
Để dễ hình dung sự khác biệt, hãy xem bảng so sánh dưới đây:
Đặc điểm | Stack Reconciler (React < 16) | Fiber Reconciler (React >= 16) |
---|---|---|
Cách xử lý | Đồng bộ (Synchronous) | Không đồng bộ (Asynchronous) & Có thể bị ngắt |
Đơn vị làm việc | Duyệt cây component theo cấu trúc Stack (đệ quy sâu) | Fiber (đơn vị làm việc nhỏ) |
Khả năng tạm dừng/tiếp tục | Không thể | Có thể (ở Giai đoạn Render) |
Khả năng ưu tiên công việc | Không | Có |
Hiệu năng trên thiết bị chậm/bản cập nhật lớn | Có thể gây đơ UI (blocking) | Mượt mà hơn (non-blocking), phân bổ thời gian linh hoạt |
Hỗ trợ tính năng hiện đại | Không (hoặc hạn chế) | Có (Concurrent Mode, Suspense, Transitions) |
Mục tiêu chính | Tìm và áp dụng thay đổi hiệu quả | Đảm bảo UI mượt mà, phản hồi nhanh, cho phép các tính năng tương lai |
Tại Sao Fiber Quan Trọng Với Lập Trình Viên?
Mặc dù không trực tiếp thao tác với Fiber trong code ứng dụng hàng ngày, việc hiểu về nó là cực kỳ hữu ích vì:
- Hiểu cách React hoạt động: Nó giải thích tại sao React lại hiệu quả và phản hồi nhanh trong nhiều trường hợp.
- Gỡ lỗi hiệu năng: Khi gặp tình trạng UI bị giật lag, biết về Giai đoạn Render (có thể bị ngắt) và Giai đoạn Commit (không thể bị ngắt) giúp bạn khoanh vùng vấn đề dễ dàng hơn. Các công việc tốn thời gian trong Giai đoạn Render có thể do logic tính toán nặng trong component, còn các vấn đề trong Giai đoạn Commit thường liên quan đến thao tác DOM hoặc các effect không hiệu quả (ví dụ: effect gây ra một vòng lặp cập nhật state vô hạn).
- Chuẩn bị cho các tính năng tương lai: Fiber là nền tảng cho Concurrent Mode (Chế độ Đồng thời). Concurrent Mode là một tập hợp các tính năng mới (như Suspense, Transitions, automatic batching) tận dụng khả năng tạm dừng và ưu tiên của Fiber để cải thiện đáng kể trải nghiệm người dùng, đặc biệt là trên các mạng hoặc thiết bị chậm. Khi sử dụng
useTransition
hoặcuseDeferredValue
, bạn đang gián tiếp yêu cầu React sử dụng cơ chế ưu tiên của Fiber.
Ví dụ Đơn giản về tính năng Fiber mang lại (Transitions)
Hãy xem một ví dụ đơn giản sử dụng useTransition
, một hook được xây dựng dựa trên Fiber, giúp bạn giữ cho UI phản hồi ngay cả khi đang thực hiện một bản cập nhật “nặng”.
Tưởng tượng bạn có một ô input để lọc một danh sách rất lớn. Mỗi lần gõ phím, bạn cập nhật state của input và state của danh sách đã lọc.
import React, { useState, useTransition } from 'react';
function FilteredList({ items }) {
const [inputValue, setInputValue] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
// Without transition:
// const handleInputChange = (e) => {
// const value = e.target.value;
// setInputValue(value);
// // This filtering might be slow for a large list
// setFilteredItems(items.filter(item => item.includes(value)));
// };
// With transition:
const [isPending, startTransition] = useTransition();
const handleInputChange = (e) => {
const value = e.target.value;
setInputValue(value);
// Mark the filtering update as a transition (lower priority)
startTransition(() => {
// This update will be interruptible
setFilteredItems(items.filter(item => item.includes(value)));
});
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Filter items..."
/>
{isPending && <p>Đang lọc...</p>}
<ul style={{ opacity: isPending ? 0.5 : 1 }}>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
// Example Usage:
// <FilteredList items={['Apple', 'Banana', 'Cherry', ... huge list ...]} />
Trong ví dụ trên:
setInputValue(value)
là một bản cập nhật có độ ưu tiên cao (cần phản hồi ngay lập tức khi người dùng gõ phím). React sẽ ưu tiên hiển thị giá trị nhập vào ô input.setFilteredItems(...)
nằm trongstartTransition
là một bản cập nhật được đánh dấu là “transition” (chuyển đổi). React biết rằng bản cập nhật này có thể mất một chút thời gian và không cần phải hiển thị ngay lập tức. Nhờ Fiber, React có thể bắt đầu công việc lọc và cập nhật danh sách, nhưng nếu người dùng tiếp tục gõ phím, React có thể tạm dừng công việc lọc đang dang dở (trong Giai đoạn Render của Fiber), xử lý bản cập nhật input mới (độ ưu tiên cao), và sau đó tiếp tục hoặc bắt đầu lại việc lọc.- Biến
isPending
giúp bạn hiển thị một indicator (như “Đang lọc…”) trong khi bản cập nhật transition đang diễn ra ở chế độ nền.
Điều này mang lại trải nghiệm người dùng mượt mà hơn đáng kể. Ngay cả khi việc lọc tốn thời gian, ô input vẫn phản hồi ngay lập tức với mỗi lần gõ phím, và danh sách sẽ được cập nhật sau khi quá trình lọc hoàn tất. Đây là một ví dụ rõ ràng về cách Fiber (cụ thể là Concurrent Mode được xây dựng trên Fiber) cải thiện hiệu năng cảm nhận được mà không cần bạn phải quản lý các tác vụ không đồng bộ phức tạp theo cách thủ công.
Kết Luận
Kiến trúc Fiber là “người hùng thầm lặng” đằng sau sự mượt mà và khả năng mở rộng của React hiện đại. Nó thay thế kiến trúc đồng bộ cũ bằng một mô hình xử lý không đồng bộ và có thể bị ngắt, cho phép React kiểm soát tốt hơn quá trình rendering, ưu tiên các công việc quan trọng, và mở ra cánh cửa cho các tính năng mạnh mẽ như Concurrent Mode, Suspense, và Transitions.
Với tư cách là lập trình viên React, đặc biệt là khi bạn tiến sâu hơn vào việc xây dựng các ứng dụng lớn và phức tạp, việc có một hiểu biết cơ bản về Fiber sẽ giúp bạn không chỉ sử dụng các tính năng mới hiệu quả hơn mà còn có khả năng chẩn đoán và giải quyết các vấn đề về hiệu năng một cách tự tin. Bạn không cần phải đi sâu vào từng dòng code của Fiber, nhưng nắm vững mục đích và cơ chế hoạt động hai giai đoạn của nó là một bước tiến quan trọng trên con đường làm chủ React.
Chúng ta đã cùng nhau “giải mã” một phần quan trọng trong bộ máy của React. Hy vọng bài viết này đã giúp bạn có cái nhìn rõ ràng hơn về Fiber và vai trò của nó. Việc hiểu sâu hơn về cách các bản cập nhật được xử lý sẽ giúp bạn viết code React hiệu quả và tối ưu hơn.
Hãy tiếp tục hành trình của chúng ta trong các bài viết tiếp theo của series “React Roadmap”. Chúng ta sẽ còn rất nhiều điều thú vị để khám phá!
Hẹn gặp lại trong bài viết tiếp theo!