Vì sao React sau 13 năm vẫn còn sống khỏe?
Vì sao React sau 13 năm vẫn còn sống khỏe?
React đã hơn mười năm tuổi. Trong quãng thời gian đó, thế giới frontend đổi thay liên tục, framework lên rồi xuống, nhưng tư tưởng cốt lõi của React gần như không hề lung lay. Thậm chí sau khi chuyển sang kiến trúc Fiber, nó còn sống khỏe hơn trước. Chính điều đó làm mình thấy tò mò, và bài viết này ra đời từ sự tò mò đó.
1. Điều khiến người ta thắc mắc: Vì sao React lại không chịu làm một "framework"?
Quay lại năm 2013. Khi ấy AngularJS gần như thống trị frontend, routing có sẵn, HTTP client có sẵn, dependency injection mở ra là dùng được, form validation cũng có luôn. Đó là thời kỳ hoàng kim của khái niệm "framework": mở terminal lên, gõ một lệnh, bộ khung dự án coi như xong.
Nhưng React lại chọn đi đường khác.
Nó chỉ đưa cho chúng ta đúng một năng lực: biến state thành UI. Routing? Tự chọn. Quản lý state? Tự chọn. HTTP request? Dùng gì cũng được. React thậm chí còn không trực tiếp đụng vào DOM; phần render giao cho react-dom, còn Native giao choreact-native.
Thế nên nhiều câu hỏi mới được đặt ra khi chúng ta tìm hiểu về React.
Câu hỏi 1: Vì sao React lại chủ động từ bỏ vị trí hấp dẫn của một "full-stack framework", chấp nhận chỉ là một thư viện "không đầy đủ"? Nó làm vậy để làm gì?
Câu hỏi 2: Tính bất biến của state dùng lúc nào cũng thấy hơi gò bó.
this.state.count++rõ ràng rất tự nhiên, vậy tại sao React cứ bắt chúng ta phải tạo object mới mỗi lần update?Câu hỏi 3: Công thức
UI = f(state)xuất hiện ở khắp nơi, nhưng rốt cuộc nó có ý nghĩa gì? Vì sao API của React lại phát triển thành hình dạng như bây giờ, mà không phải kiểu khác?Câu hỏi 4: Trong mười năm qua, Angular đã đi từ 1.0 tới 21, Vue cũng đi từ Options API sang Composition API. Vậy tại sao phần cốt lõi của React như component, state, props và luồng dữ liệu một chiều lại gần như không đổi về bản chất? Sự ổn định đó đến từ đâu?
Nhìn kỹ thì mấy câu hỏi này đều đang chỉ về cùng một đáp án: mỗi quyết định thiết kế của React đều được cân nhắc để phục vụ khả năng thích nghi dài hạn. Không phải vì nó "lười làm framework", mà vì nó cố ý không làm framework. Tư duy tối giản này, phía sau nó là cả một hệ thống đánh đổi đã được cân nhắc rất kỹ (tất nhiên đây vẫn chỉ là góc nhìn cá nhân).
2. Bốn lựa chọn đã định hình số phận của React
2.1 Lựa chọn thứ nhất: Library, không phải Framework
Mở package.json của React ra, dòng đầu tiên viết rất rõ:
"React is a JavaScript library for building user interfaces."
Không phải framework, không phải platform, cũng không phải suite, mà là library. Hồi mới học React, lúc mình clone source code xuống và nhìn thấy câu này, phản ứng đầu tiên là: tại sao phải nhấn mạnh vậy? Khác nhau ở đâu? Người ta vẫn gọi React là framework suốt đó thôi? Hay tác giả viết nhầm? Rồi mình quay sang đọc source code, mà lúc ấy cũng chẳng hiểu bao nhiêu, nên chuyện này cứ thế trôi qua.
Thử đặt React cạnh Angular để so sánh
| React (thư viện) | Angular (framework) | |
|---|---|---|
| Quyền quản trị | Chúng ta quyết định cấu trúc ứng dụng, React chỉ lo vẽ UI ra màn hình | Angular kiểm soát vòng đời ứng dụng, chúng ta nhét code vào những chỗ nó chuẩn bị sẵn |
| Routing | react-router, cộng đồng duy trì, muốn thay lúc nào cũng được | @angular/router, tích hợp chính thức, gần như không thay được |
| HTTP | axios, fetch, React Query, chọn gì cũng được | HttpClient, tích hợp sẵn, không dùng cũng khó |
| Quản lý state | Redux, Zustand, Jotai, Recoil... trăm hoa đua nở | NgRx, Akita, các hướng được khuyến nghị chính thức không nhiều |
| Chi phí nâng cấp | Thấp, thay renderer hoặc reconciler là xong | Cao, nâng cả framework, động một chỗ là động cả hệ thống |
React trả quyền kiểm soát lại cho lập trình viên. Nó chỉ giải một bài toán: làm sao để đồng bộ phần mô tả component theo kiểu khai báo lên màn hình cho hiệu quả. Còn những phần khác, để hệ sinh thái tự phát triển.
Hệ quả trực tiếp của lựa chọn này là: React có thể sống lâu hơn bất kỳ xu hướng nào trong hệ sinh thái. Flux lỗi thời thì Redux lên thay; Redux nặng quá thì Zustand xuất hiện; REST không còn đủ tốt thì React Query nổi lên. React không cần tự mình lo hết những chuyện đó, nó chỉ cần chăm cho phần việc cốt lõi của nó.
Nói cách khác, chính sự "không đầy đủ" lại là bí quyết giúp React sống dai.
2.2 Lựa chọn thứ hai: UI = f(state)
Nguyên tắc thiết kế API quan trọng nhất của React được giấu trong một công thức mà hầu như ai học React cũng từng thấy, và nhìn qua thì cứ như bài tập toán:
UI = f(state)Nhưng công thức này rất gọn, và cũng rất đẹp. Nó quyết định hình dáng của toàn bộ API React - từ cách viết component, cách truyền state, cho đến cách kích hoạt update. Mọi thứ đều có thể suy ra từ đây.
| Khái niệm | Thành phần tương ứng trong React |
|---|---|
Định nghĩa hàm f | Component function, hoặc phương thức render của class |
Tham số đầu vào state | props + useState / useReducer |
Giá trị đầu ra UI | Biểu thức JSX, về bản chất là một ReactElement |
| Ràng buộc hàm thuần (không side effect) | React.memo, PureComponent, yêu cầu về tính bất biến |
Hợp thành hàm f ∘ g | Component lồng nhau: <Parent><Child /></Parent> |
{ count: 0, user: ... }{ initialCount: 5 }<div>Count: 0</div>Công thức này kéo theo vài ảnh hưởng rất sâu:
Thứ nhất, component phải là thứ có thể gọi được. Vì thế function component trở thành lựa chọn rất tự nhiên. Function vốn là đơn vị ánh xạ gần với toán học nhất: đưa state vào, lấy mô tả UI ra.
Thứ hai, đầu vào quyết định đầu ra. Nếu props và stategiống nhau, component phải render ra cùng một UI. Nghe có vẻ hiển nhiên, nhưng thực ra nócấm side effect trong giai đoạn render — không gửi HTTP request trong render, không thao tác DOM trong render, cũng không đọc local storage trong render. Vì làm thế thì "cùng đầu vào" sẽ không còn đảm bảo "cùng đầu ra" nữa.
Thứ ba, composition tốt hơn inheritance. Hợp thành hàm (f(g(x))) tự nhiên hơn nhiều so với kế thừa class. Component lồng component trong React thực chất là syntax sugar của hợp thành hàm. Bởi vậy React chưa bao giờ thực sự dựa vào inheritance —extends Component chỉ là di sản lịch sử, chứ bản thân extends không nằm ở trung tâm thiết kế của React.
2.3 Lựa chọn thứ ba: Luồng dữ liệu một chiều
Quy tắc dòng chảy dữ liệu của React đơn giản gần như đến mức thẳng tay: dữ liệu đi từ trên xuống, callback đi từ dưới lên.
Component cha (sở hữu state)
↓ props
Component con (chỉ đọc)
↓ callback
Component cha (cập nhật state)Component con không thể sửa trực tiếp state của component cha. Nó chỉ có thể dùng callback mà component cha đưa xuống để "xin" cha update. Component cha nhận yêu cầu, tạo state mới, React thấy có thay đổi rồi render lại.
Thiết kế này đã triệt tiêu một cơn ác mộng đeo bám frontend suốt nhiều năm:"rốt cuộc ai là người sửa cục dữ liệu này?"
Trong thế giới two-way binding, bất kỳ component nào cũng có thể lén sửa shared state ở một góc nào đó. Lúc debug, chúng ta phải làm như thám tử: nhảy qua lại giữa cả chục file, đặt breakpoint rồi xóa breakpoint, thêm console.log rồi lại xóa console.log, cuối cùng mới phát hiện ra thủ phạm là một component cháu đời thứ ba sửa global variable trong một lifecycle hook.
Luồng dữ liệu một chiều của React cưỡng chế một khế ước rất rõ: ai sở hữu state thì người đó mới được sửa nó. Component con không có quyền sửa, chỉ có quyền yêu cầu sửa. Nghe thì giống như đang bó buộc tự do, nhưng thật ra chính sự bó buộc đó làm cho hành vi của chương trình trở nên có thể suy luận được — thấy bug thì lần ngược theo chiều dữ liệu, cuối cùng kiểu gì cũng tìm ra nguồn.
2.4 Lựa chọn thứ tư: Ràng buộc bất biến
React không cho phép chúng ta sửa state trực tiếp. Mỗi lần thay đổi, phải tạo ra object mới:
// Sai. React sẽ không phát hiện thay đổi vì reference không đổi.
this.state.count = 1;
this.setState({ count: this.state.count });
// Đúng. Tạo object mới, React thấy reference mới và kích hoạt render lại.
this.setState({ count: 1 });
// Hoặc dùng dạng function, an toàn hơn
this.setState(prev => ({ count: prev.count + 1 }));Ràng buộc này đúng là khiến người mới thấy hơi khó chịu, thậm chí có lúc code mệt quá còn thấy nó ngớ ngẩn. Nó buộc chúng ta đổi thói quen viết code — không push thẳng vào array, mà phải dùng concat hoặc spread; không obj.key = value, mà phải copy object ra rồi sửa trên bản mới. Trước ES6 thì chuyện này đúng là ác mộng, có spread operator rồi mới đỡ hơn.
Nhưng React kiên quyết giữ ràng buộc này vì hai mục tiêu lớn:
Thứ nhất, kiểm tra reference equality (===). React so sánh reference của state trước và sau (oldState === newState) để quyết định rất nhanh xem có cần render lại hay không. Nếu cho phép mutate trực tiếp state thì tối ưu này coi như mất hẳn — vì reference vẫn y nguyên, React sẽ nghĩ là chẳng có gì thay đổi.
Thứ hai, debug du hành thời gian (Time-Travel Debugging). Nếu mỗi state là một snapshot bất biến, ta có thể lưu toàn bộ lịch sử state của ứng dụng vào một array, muốn quay lại mốc nào cũng được. Redux DevTools có thanh trượt hoạt động được cũng là nhờ state của Redux là bất biến. Mỗi state là một snapshot hoàn chỉnh, không dẫm chân lên nhau.
3. Source code: Khi tư duy thiết kế được viết thành code
Ý đồ thiết kế của React không chỉ nằm trong tài liệu, mà được khắc rất rõ vào source code. Hãy mở repository facebook/react và xem những ý tưởng đó được hiện thực ra sao.
3.1 packages/shared/ReactElementType.js —— Chứng cứ kiểu dữ liệu cho UI = f(state)
Đơn vị đầu ra UI của React có tên là ReactElement. Nhìn vào định nghĩa kiểu của nó:
// https://github.com/facebook/react/blob/main/packages/shared/ReactElementType.js
export type ReactElement = {
$$typeof: any, // Đánh dấu bằng Symbol để ngăn tấn công XSS
type: any, // Component function, hoặc string tag như 'div'
key: any, // Định danh dùng cho thuật toán Diff
ref: any, // Tham chiếu DOM
props: any, // Tham số đầu vào — "đối số thực" của lời gọi hàm
_owner: any, // Component tạo ra phần tử này (chỉ dùng để debug trong DEV)
_store: { validated: 0 | 1 | 2, ... },
_debugInfo: null | ReactDebugInfo,
_debugStack: Error,
_debugTask: null | ConsoleTask,
};Nhìn vào cấu trúc này sẽ thấy ngay: ReactElement là một cấu trúc dữ liệu thuần túy. Nó mô tả UI nên trông như thế nào, chứ bản thân nó không tự làm gì cả. Không có method, không có side effect, chỉ là một "bản mô tả".
Điểm quan trọng nằm ở field type. Nó có thể là string ('div','span'), hoặc cũng có thể là function (component function). Khi type là function, React sẽ gọi function đó, truyền props vào, nhận về mộtReactElement mới, rồi tiếp tục lặp lại quá trình này theo kiểu đệ quy cho tới khitype trở thành string, lúc đó mới sinh ra DOM thật.
ReactElement(type=FunctionComponent, props={state})
→ gọi FunctionComponent(props)
→ trả về ReactElement(type='div', props={children: ...})
→ reconcile đệ quy cho tới khi type là string
→ sinh ra DOM thậtĐây chính là cách UI = f(state) được hiện thực trong source code. ReactElement là "biểu diễn trung gian" của toàn bộ quá trình đệ quy này, và tính bất biến của nó giúp quá trình đó giữ được tính thuần.
3.2 packages/shared/ReactSymbols.js —— Dùng Symbol để dựng tường an toàn
React dùng Symbol để đánh dấu các loại phần tử khác nhau:
// https://github.com/facebook/react/blob/main/packages/shared/ReactSymbols.js
export const REACT_LEGACY_ELEMENT_TYPE: symbol = Symbol.for('react.element');
export const REACT_ELEMENT_TYPE: symbol = Symbol.for('react.transitional.element');
export const REACT_PORTAL_TYPE: symbol = Symbol.for('react.portal');
export const REACT_FRAGMENT_TYPE: symbol = Symbol.for('react.fragment');
export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for('react.strict_mode');
export const REACT_PROFILER_TYPE: symbol = Symbol.for('react.profiler');
export const REACT_CONSUMER_TYPE: symbol = Symbol.for('react.consumer');
export const REACT_CONTEXT_TYPE: symbol = Symbol.for('react.context');
export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref');
export const REACT_SUSPENSE_TYPE: symbol = Symbol.for('react.suspense');
export const REACT_MEMO_TYPE: symbol = Symbol.for('react.memo');
export const REACT_LAZY_TYPE: symbol = Symbol.for('react.lazy');Mỗi Symbol là một dấu kiểu không thể giả mạo. Field $$typeof:REACT_ELEMENT_TYPE đảm bảo rằng chỉ object do chính React tạo ra mới được xem là ReactElement hợp lệ.
Vì sao phải làm thế? Để chặn tấn công XSS. Giả sử server bị xâm nhập và trả về JSON như sau:
{ "type": "script", "props": { "src": "evil.com/steal.js" } }Nếu frontend render thẳng dữ liệu này như một ReactElement thì script độc hại sẽ chạy ngay. Nhưng Symbol không thể được serialize vào JSON — kẻ tấn công không thể giả mạo $$typeof:Symbol.for('react.element'), vì JSON không biểu diễn được Symbol. React sẽ kiểm tra$$typeof trước khi render; không khớp là loại thẳng.
Điều này thể hiện rõ tư duy defensive programming: kể cả khi không tin đầu vào, hệ thống vẫn phải tự bảo vệ được mình.
3.3 packages/react/src/ReactBaseClasses.js —— Class component theo phong cách tối giản
Lớp cơ sở Component của class component trong React được rút gọn đến mức khá bất ngờ:
// https://github.com/facebook/react/blob/main/packages/react/src/ReactBaseClasses.js
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject; // Bộ chứa ref, dùng lại object rỗng
this.updater = updater || ReactNoopUpdateQueue; // updater được renderer bên ngoài inject vào
}
Component.prototype.isReactComponent = {}; // Đánh dấu: đây là một React component
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};Đọc từng dòng mới thấy rõ các đánh đổi bên trong.
isReactComponent = : đây là ví dụ rất điển hình của duck typing. React không kiểm tra instanceof Component, mà chỉ kiểm tra object đó có thuộc tínhisReactComponent hay không. Cách này linh hoạt hơn tưởng tượng rất nhiều — bất kỳ class nào, chỉ cần có dấu này, React đều coi là component. Không coupling chặt, cũng không bị khóa cứng vào kế thừa.
updater được inject từ bên ngoài: bản thân setStatekhông trực tiếp làm gì, nó chỉ đẩy yêu cầu vào queue. Thứ thực sự thực hiện update làreact-dom hoặc react-native, thông qua updater được inject vào. Chuyện này lại một lần nữa cho thấy định vị của React là một thư viện — lõi React không buộc chặt vào bất kỳ logic render cụ thể nào, nó chỉ đóng vai trò như một tầng điều phối.
Câu comment nổi tiếng trong source code:
You should treat
this.stateas immutable.
Đây không phải lời nhắc cho vui, mà là một khế ước. Ngay trong source code, team React đã nói rất rõ: state là bất biến. Vi phạm khế ước này có thể chưa làm code nổ ngay, nhưng sớm muộn cũng để lại rắc rối ở production.
3.4 packages/react/src/jsx/ReactJSXElement.js —— JSX biến thành dữ liệu thuần như thế nào
JSX chúng ta viết, sau khi được Babel hoặc TypeScript biên dịch, sẽ trở thành lời gọi hàmjsx(). Hãy xem hàm đó tạo ReactElement như thế nào:
// https://github.com/facebook/react/blob/main/packages/react/src/jsx/ReactJSXElement.js
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols';
function jsx(type, config, maybeKey) {
// Trích xuất key, ref, hợp nhất props mặc định...
return ReactElement(
type,
key,
ref,
undefined, // source (bỏ qua trong môi trường production)
undefined, // self (bỏ qua trong môi trường production)
props,
);
}
function ReactElement(type, key, ref, source, self, props) {
const element = {
$$typeof: REACT_ELEMENT_TYPE, // Đánh dấu Symbol, chống XSS
type: type, // Component function hoặc DOM tag
key: key,
ref: ref,
props: props, // Dữ liệu thuần, không có method
_owner: __DEV__ ? ReactSharedInternals.A?.getOwner?.() : null,
};
if (__DEV__) {
element._store = { validated: 0 };
element._debugStack = new Error();
}
return element;
}Có vài chi tiết rất đáng để dừng lại nhìn kỹ.
$$typeof: REACT_ELEMENT_TYPE: mọi JSX element sau khi biên dịch đều mang một dấu Symbol. Điều đó đồng nghĩa với việc kẻ tấn công không thể dùng JSON để giả mạo ReactElement — Symbol không thể bị JSON.stringify, và đó là một trong những nền móng an toàn của React.
props là object thuần: props của ReactElement không chứa method nào cả, nó chỉ là nơi mang dữ liệu. Điều này cưỡng chế mô hình lập trìnhdata-driven — component giao tiếp với nhau bằng dữ liệu, chứ không gọi method của nhau.
Không dùng new, mà dùng factory function: ReactElementchỉ là function thông thường, không phải constructor. React dùng phong cách hàm để tạo object, tránh hành vi ngầm của new và chi phí của prototype chain. Đây là tư duy hàm len vào tận chi tiết của source code.
3.5 packages/react/src/ReactHooks.js —— Gắn "động cơ state" cho function component
Hooks là tính năng được React 16.8 mang vào, cho phép function component có state. Nhưng cách Hooks được hiện thực lại khá bất ngờ — chúng không "gắn" trên component, mà được inject tạm thời trong lúc render thông qua một global dispatcher:
// https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js
export function useState(initialState) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useReducer(reducer, initialArg, init) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useEffect(create, deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}resolveDispatcher() là chìa khóa để hiểu Hooks. Mỗi lần render, React sẽ đặt dispatcher tương ứng với component hiện tại vào một vị trí global có thể truy cập được, rồi mới chạy component function. Các lệnh gọi useState, useEffectbên trong component thực chất là đang lấy hook implementation từ dispatcher toàn cục này. Render xong thì dispatcher bị xóa.
Đó cũng là lý do có quy tắc Hooks — chỉ gọi Hooks ở top-level, không gọi trong vòng lặp hoặc trong điều kiện. Vì Hooks dựa vào thứ tự gọi để ánh xạ state; một khi thứ tự lệch đi thì toàn bộ mapping sẽ rối.
Thiết kế của Hooks cho thấy React rất kiên định với API kiểu composition: mỗi Hook là một primitive độc lập (useState, useEffect, useContext), chúng ta ghép chúng lại theo nhu cầu, thay vì bị trói vào một bộ lifecycle method do framework áp đặt. Muốn thêm state? Thêm useState. Muốn gọi request? Thêm useEffect. Mọi thứ rất gọn.
3.6 Ánh xạ source code
| Ý đồ thiết kế | File source code | Ký hiệu/hàm then chốt |
|---|---|---|
| UI = f(state) | packages/shared/ReactElementType.js | Kiểu ReactElement, field type |
| An toàn kiểu (chống XSS) | packages/shared/ReactSymbols.js | Symbol REACT_ELEMENT_TYPE |
| Tính độc lập của thư viện | packages/react/src/ReactBaseClasses.js | inject updater, isReactComponent |
| JSX → dữ liệu thuần | packages/react/src/jsx/ReactJSXElement.js | hàm jsx(), factory ReactElement() |
| State theo kiểu hàm | packages/react/src/ReactHooks.js | useState, resolveDispatcher |
| Tính thuần của component | packages/react/src/ReactMemo.js | higher-order component React.memo() |
4. Điều rút ra: Tư duy kỹ thuật mọc lên từ source code
4.1 Khi chọn công nghệ, hãy nhìn vào tư duy nền trước
Mỗi lần đưa ra quyết định kỹ thuật, team React dường như đều tự hỏi cùng một câu:"Mười năm nữa quyết định này còn đúng không?"
- Chọn thư viện thay vì framework — bộ full-stack của framework rồi sẽ lỗi thời, nhưng nhu cầu điều phối UI thì không. React chỉ tập trung vào phần thứ hai.
- Chọn hàm thay vì hướng đối tượng — hàm toán học ổn định hơn kế thừa class, composition linh hoạt hơn inheritance. Nền móng của lập trình hàm giúp nó bền lâu hơn.
- Chọn tính bất biến thay vì reactive tracking — dữ liệu bất biến có ngữ nghĩa đơn giản hơn, dễ debug hơn, và tự nhiên hỗ trợ concurrency. Reactive tracking (Proxy) tuy tiện, nhưng loại "ma thuật" đó thường phản tác dụng khi hệ thống trở nên phức tạp.
Khi làm technology selection cho một dự án mới, ta không nên chỉ so xem "framework nào nhiều tính năng hơn". Tính năng là thứ thay đổi rất nhanh; hôm nay có, mai có thể hết thời. Câu đáng hỏi hơn là:
"Công nghệ này đang đưa ra cam kết cốt lõi nào? Cam kết đó năm năm nữa còn đứng vững không?"
Cam kết cốt lõi của React là UI = f(state). Mà thứ đến từ toán học thì thường không có hạn sử dụng.
4.2 Dùng "ràng buộc" để đổi lấy "tự do"
React có một kiểu khôn ngoan rất xuyên suốt: chủ động đưa vào những ràng buộc có lợi, chấp nhận bớt tiện trong ngắn hạn để đổi lấy khả năng kiểm soát về dài hạn.
- Ràng buộc bất biến → đổi lấy debug du hành thời gian, tối ưu bằng reference equality, render có thể dự đoán
- Ràng buộc luồng dữ liệu một chiều → đổi lấy khả năng truy vết thay đổi state
- Ràng buộc hàm thuần → đổi lấy khả năng test, cache, và song song hóa
Lúc mới nhìn, mỗi ràng buộc đều giống một kiểu "bó tay bó chân" — không được viết kiểu này, không được sửa kiểu kia. Nhưng khi dự án phình từ vài nghìn dòng lên vài trăm nghìn dòng, khi team từ 2 người thành 20 người, những ràng buộc đó lại biến thành hào bảo vệ. Nó khiến hành vi của chương trình có thể suy luận được, bug có thể lần ra được, và người mới vào dự án không bị lạc trong mê cung dữ liệu.
Trong chính hệ thống của mình, chúng ta cũng có thể chủ động đưa những ràng buộc kiểu này vào:
• Cưỡng chế khế ước bất biến cho API — versioning, immutable infrastructure
• Cưỡng chế hướng chảy dữ liệu — event sourcing, CQRS
• Cưỡng chế lõi hàm thuần không side effect — tách triệt để domain logic và side effect
Ràng buộc không phải xiềng xích. Nó là thiết kế để hệ thống sống được lâu hơn.
4.3 Sự khôn ngoan hệ sinh thái của inversion of control
React không làm routing, không làm HTTP, cũng không làm form validation. Nhưng nó lại nuôi ra hệ sinh thái frontend lớn nhất thế giới.
Đây là một kiểu tư duy platform — không ôm hết mọi thứ, nhưng định nghĩa luật chơi. React định nghĩa mô hình component, quy ước Hook, giao thức scheduling, rồi để hệ sinh thái phát triển tự do phía trên đó. react-router, React Query, Zustand, Next.js, Remix... tất cả đều không phải sản phẩm chính thức của React, nhưng ghép lại thì tạo thành một bức tranh công cụ phong phú hơn bất kỳ framework đơn lẻ nào.
Khi thiết kế platform hoặc infrastructure, có một câu rất đáng tự hỏi:
"Chúng ta nên tự sở hữu tính năng này, hay nên định nghĩa một giao thức để cộng đồng sở hữu nó?"
Đôi khi, thiết kế tốt nhất không phải là làm thêm nhiều thứ, mà là vẽ ranh giới cho rõ rồi lùi lại một bước.
App của bạn đang gặp tình trạng component re-render hàng chục lần dù đã dùng useMemo khắp nơi? Để giải quyết triệt để vấn đề này, chúng ta cần nhìn sâu vào cách engine của React hoạt động thay vì tối ưu theo cảm tính. Khám phá ngay khoá học React Nâng Cao: Hiểu React từ bên trong để vạch trần những bí mật bên dưới framework này và làm chủ hoàn toàn performance.




