React Mental Model: Tư duy đúng trước khi viết dòng code đầu tiên
Trước khi học hooks hay state management, bạn cần hiểu React thực sự hoạt động như thế nào bên dưới. Bài này sẽ xây dựng mental model đúng từ đầu.
Nguyễn Nhật Long
@nguyennhatlong1303
Mình từng thấy rất nhiều bạn học React theo kiểu: xem tutorial, copy code, chạy được là thôi. Vài tuần sau gặp bug liên quan đến re-render hay state không update như kỳ vọng thì bắt đầu loạn. Lý do là vì chưa có mental model đúng về cách React vận hành.
Bài này là bài đầu tiên trong series từ React đến Next.js. Mình sẽ không dạy bạn cú pháp mình muốn bạn hiểu React nghĩ như thế nào trước. Khi có nền tảng này rồi, mọi thứ về sau sẽ click nhanh hơn rất nhiều.
Imperative vs Declarative cái gốc của mọi thứ
Nếu bạn từng làm việc với jQuery hoặc vanilla JS, bạn đã quen với kiểu imperative: bảo trình duyệt từng bước cụ thể phải làm gì.
1// Imperative jQuery style2const btn = document.querySelector('#toggle-btn');3const panel = document.querySelector('#panel');45btn.addEventListener('click', () => {6 panel.classList.toggle('hidden');7 btn.textContent = panel.classList.contains('hidden') ? 'Show' : 'Hide';8});
Bạn phải tự tay: tìm element, lắng nghe event, mutate DOM, sync lại text của button. Mọi bước đều phải nói rõ.
React thì khác. React là declarative bạn chỉ cần mô tả UI trông như thế nào ứng với từng state, còn việc update DOM như thế nào là React lo.
1// Declarative React style2function TogglePanel() {3 const [isVisible, setIsVisible] = useState(false);45 return (6 <div>7 <button onClick={() => setIsVisible(!isVisible)}>8 {isVisible ? 'Hide' : 'Show'}9 </button>10 {isVisible && <div className="panel">Content here</div>}11 </div>12 );13}
Bạn không cần nghĩ đến DOM. Bạn chỉ nói: "Khi isVisible là true thì UI trông thế này, khi false thì trông thế kia." React tự figure out phần còn lại.
Theo kinh nghiệm của mình, đây là shift tư duy quan trọng nhất khi chuyển sang React. Nhiều bạn vẫn mang mindset jQuery vào React rồi cố dùng document.querySelector trong component đó là dấu hiệu rõ nhất của việc chưa internalize được cái này.
Component là gì thực ra
Định nghĩa đơn giản nhất: component là một function nhận vào props, trả ra JSX.
1function UserCard({ name, avatar, role }) {2 return (3 <div className="card">4 <img src={avatar} alt={name} />5 <h2>{name}</h2>6 <span>{role}</span>7 </div>8 );9}
Một điểm mình thấy nhiều bạn hay nhầm: JSX không phải HTML. JSX là JavaScript expression được transpile thành React.createElement() calls. Khi bạn viết:
1<UserCard name="Nam" role="Engineer" />
Thực ra bạn đang viết:
1React.createElement(UserCard, { name: 'Nam', role: 'Engineer' })
Hiểu điều này giúp bạn không bị bối rối khi gặp các rule như "tại sao không dùng class mà phải dùng className", hay tại sao expression trong JSX phải wrap trong {}.
Về props: chúng là read-only. Component không được phép tự modify props của mình. Data flow là một chiều từ parent xuống child. Nếu child cần thay đổi gì đó ở parent, nó phải gọi callback function được truyền xuống qua props.
1// Parent truyền callback xuống2function Parent() {3 const [count, setCount] = useState(0);4 return <Child count={count} onIncrement={() => setCount(c => c + 1)} />;5}67// Child gọi callback khi cần8function Child({ count, onIncrement }) {9 return <button onClick={onIncrement}>Count: {count}</button>;10}
Pattern children prop cũng rất hay dùng cho composition:
1function Card({ children, title }) {2 return (3 <div className="card">4 <h3>{title}</h3>5 <div className="content">{children}</div>6 </div>7 );8}910// Dùng như này11<Card title="Profile">12 <UserAvatar />13 <UserInfo />14</Card>
Virtual DOM và cái gọi là Reconciliation
Đây là phần mà nhiều bạn nghe tên nhưng không thực sự hiểu cơ chế.
Khi bạn render một component, React không tạo ra DOM thật ngay. Nó tạo ra một Virtual DOM về bản chất là một cây JavaScript objects mô tả UI trông như thế nào.

Khi state thay đổi, flow diễn ra như sau:
- React render lại component → tạo Virtual DOM tree mới
- So sánh (diff) Virtual DOM mới với cái cũ
- Tìm ra chính xác những gì thay đổi
- Chỉ patch đúng những phần đó lên Real DOM
Tại sao cần Virtual DOM? Vì DOM operations rất tốn kém. Thay vì update DOM mỗi khi có bất kỳ thay đổi nhỏ nào, React batches lại và minimize số lần chạm vào Real DOM.
React hiện tại dùng Fiber architecture chia quá trình render thành các chunks nhỏ, có thể pause và resume. Điều này cho phép React ưu tiên các update quan trọng hơn (như user interaction) trước các update ít quan trọng hơn (như data fetching).
Một điểm quan trọng liên quan đến key prop:
1// Sai React không biết element nào là element nào khi list thay đổi2{items.map(item => <Item data={item} />)}34// Đúng key giúp React track từng element5{items.map(item => <Item key={item.id} data={item} />)}
Khi bạn có một list và không cung cấp key, React phải đoán xem element nào tương ứng với element nào sau khi list thay đổi. Kết quả là behavior không đoán trước được đây là nguồn gốc của rất nhiều bug mình đã debug cho junior trong team.
Unidirectional Data Flow tại sao one-way lại tốt hơn
Framework như Angular (two-way binding) hay Vue (v-model) cho phép data chảy hai chiều component có thể tự modify data từ parent. Nghe có vẻ tiện hơn, nhưng khi app lớn lên, việc trace xem data bị thay đổi ở đâu trở nên cực kỳ khó.
React enforce one-way flow:
- Data đi xuống qua props (parent → child)
- Events đi lên qua callbacks (child → parent)
Theo mình, cái verbose một chút của React thực ra là feature chứ không phải bug. Khi bạn đọc code của người khác, bạn luôn biết data đến từ đâu và thay đổi ở đâu. Không có magic mutation ẩn nào.
| Aspect | Two-way binding | One-way flow |
|---|---|---|
| Syntax | Ngắn gọn hơn | Verbose hơn một chút |
| Debug | Khó trace nguồn gốc thay đổi | Dễ trace data chỉ đi một chiều |
| Predictability | Thấp hơn khi app phức tạp | Cao luôn biết data đến từ đâu |
| Testing | Khó hơn | Dễ hơn component là pure function |
Khi nào component re-render?
Hiểu cái này sẽ giúp bạn tránh được rất nhiều performance issue sau này.
Có 4 trigger chính:
1. State thay đổi gọi setState hoặc dispatch từ useReducer
2. Parent re-render khi parent render lại, tất cả children đều render lại theo mặc định, dù props không đổi. Đây là điều nhiều bạn không biết.
3. Context value thay đổi nếu component đang consume một context và value của context đó thay đổi
4. Custom hooks trigger re-render nếu hook bên trong gọi setState

Anh em lưu ý một điểm cực kỳ quan trọng: re-render ≠ DOM update.
Khi component re-render, React chạy lại function component đó và tạo Virtual DOM mới. Nhưng nó chỉ update Real DOM nếu output thực sự khác với lần render trước. Nếu output giống hệt, DOM không bị touch.
Nghĩa là re-render không hẳn là xấu nó chỉ tốn CPU để chạy function và diff Virtual DOM. DOM manipulation mới là cái thực sự tốn kém. Tuy nhiên nếu component tree lớn và re-render quá nhiều không cần thiết, nó vẫn ảnh hưởng performance đó là lúc bạn cần đến memo, useMemo, useCallback.
Nhưng cái đó là chuyện của bài sau. Bây giờ cứ internalize 4 trigger này đã.
Mình biết bài này khá dense, nhưng đây là foundation. Nếu bạn hiểu rõ declarative mindset, component model, Virtual DOM, one-way flow, và re-render triggers bạn đã có mental model đủ vững để học bất kỳ thứ gì trong React ecosystem mà không bị lost.
Bài tiếp theo mình sẽ đi vào hooks bắt đầu với useState và useEffect, giải thích tại sao chúng được design như vậy thay vì chỉ dạy cú pháp.
Nguyễn Nhật Long
@nguyennhatlong1303Nguyễn Nhật Long is a Senior Frontend Engineer and Frontend Team Leader with 7 years of experience building real-time fintech platforms. Specializing in React, Next.js, TypeScript, and React Native, shipping 10+ products across Web, Mobile, Telegram Mini-Apps, and Web3.
Thấy hay? Chia sẻ cho bạn bè!