Lần đầu tiên mình biết đến React là khoảng gần 3 năm trước. Cho đến tận bây giờ, có một sự thật đáng buồn là dù sử dụng React tương đối nhiều, nhưng nếu phải viết lại React thì thực sự mình không biết bắt đầu từ đâu. Quan niệm của mình luôn là "Know your tools. Enough that you can craft an inferior one" (it's easier said than done). Chính vì vậy mình đã thử viết lại một phiên bản đơn giản của React và đây là những ghi chép trong quá trình mày mò của mình. React là một thư viện tương đối lớn nên mình chỉ tập trung vào kiến trúc Fiber, xương sống của React core. Bạn có thể xem đầy đủ code ở đây (https://github.com/Goose97/tiny-react)
Fiber là một thiết kế mới (reimplementation) của React-core. Tuy nói là mới nhưng Fiber chính thức được trình làng từ React 16 (https://reactjs.org/blog/2017/09/26/react-v16.0.html). Selling point của Fiber chính là khả năng incremental rendering**,** có thể chia nhỏ render phase ra làm nhiều phần là thực hiện asynchronous (thay vì hoàn toàn synchronous như thiết kế cũ). Lợi ích rõ ràng nhất từ cải tiến này là giúp cho render phase của React không block các tác vụ quan trọng của browser. Với một ứng dụng đủ phức tạp thì việc xử lý render phase có thể kéo dài 100-200ms là chuyện có thể xảy ra (while our budget is only 16ms).
Khi chúng ta sử dụng React, phần lớn thời gian chúng ta làm việc với các React element. Khi ta sử dụng JSX, hay đúng hơn là sử dụng React.createElement, kết quả trả về là một React element.
const simpleDiv = <div>I am a React element</div>;
const notSoSimpleDiv = React.createElement('div', {}, 'I am also a React element');
Ta có thể model một React element như sau:
type Element = {
// Can be either string (span, div) or component (class or function)
// null is the text element
type: string | Function | null;
props: Props;
key: string | null;
textContent?: string;
};
Với mỗi một React element chúng ta sẽ có một Fiber node**.** Khác với React element (stateless and immutable), Fiber node là một stateful and mutable data structure. Ta có thể model một Fiber node như sau:
class Fiber {
pendingProps: TinyReact.Props;
memoizedProps: TinyReact.Props;
memoizedState: TinyReact.State;
updateQueue: Array<TinyReact.State>;
stateNode: TinyReact.Component | HTMLElement | Text | null;
childrenRenderer: (
props?: TinyReact.Props,
) => TinyReact.ChildrenElements | null;
key: string | null;
element: TinyReact.Element;
elementType: TinyReact.Element['type'];
textContent?: string;
// Pointers for traversal purpose
return?: Fiber;
child?: Fiber;
sibling?: Fiber;
previousSibling?: Fiber;
effectTag: Set<TinyReact.EffectTag>;
// These are pointers forming a linked list of fibers represents all the effect that needs to perform
nextEffect?: Fiber;
// This is the root of the linked list
rootEffect?: Fiber;
// This is the reference to the alternate tree during render phase
alternate?: Fiber;
// This is useful in commit phase when we building the html tree from bottom up
// This is the html element which a fiber produce when they are rendered to the screen
output?: HTMLElement | Text;
// This serves iteration purpose in render phase
visited?: boolean;
recreatingSubtree?: boolean;
// Store the event handler so we can detach them in needs
eventListeners: Map<string, Function>;
}