React: Virtual DOM, Fiber, Server Components và Compiler

React đã trở thành một trong những công cụ phát triển frontend phổ biến nhất.

Trong bài viết này, chúng ta sẽ khám phá:

  • Cơ chế render của React (Virtual DOM, Reconciliation, Fiber architecture)
  • Các vấn đề hiệu suất phổ biến trong React
  • Kỹ thuật tối ưu hiệu suất React
  • Server-side Rendering (SSR) và Static Site Generation (SSG)
  • React Server Components
  • React Compiler và xu hướng “profile-first” (React 19, 2024+)

1. Cơ chế render của React

Để tối ưu hiệu suất React, trước tiên chúng ta cần hiểu cách React render UI.

Virtual DOM

Virtual DOM là một trong những khái niệm cốt lõi của React. Đây là một biểu diễn nhẹ của DOM thật trong bộ nhớ.

Cách hoạt động:

  1. React tạo một cây Virtual DOM khi ứng dụng khởi động
  2. Khi state hoặc props thay đổi, React tạo một Virtual DOM mới
  3. React so sánh Virtual DOM mới với phiên bản trước đó (quá trình “diffing”)
  4. Chỉ những phần thay đổi mới được cập nhật trong DOM thật (quá trình “reconciliation”)
// Khi state thay đổi (hooks, cách viết phổ biến 2025+)
setCount(count + 1);

// React sẽ:
// 1. Tạo Virtual DOM mới với count đã cập nhật
// 2. So sánh với Virtual DOM cũ (Fiber reconciler)
// 3. Tìm ra sự khác biệt (count đã thay đổi)
// 4. Chỉ cập nhật phần DOM thật chứa count

Virtual DOM giúp tối ưu hiệu suất bằng cách giảm thiểu các thao tác DOM trực tiếp, vốn rất tốn kém về mặt hiệu suất.

Reconciliation

Reconciliation là thuật toán React sử dụng để so sánh hai cây Virtual DOM và xác định những gì cần được cập nhật.

Nguyên tắc cơ bản:

  1. Các phần tử có kiểu khác nhau sẽ tạo ra cây khác nhau

    • Nếu một <div> thay đổi thành <span>, React sẽ xóa cây cũ và xây dựng cây mới
  2. Các phần tử có key khác nhau được coi là khác nhau

    • Thuộc tính key giúp React xác định phần tử nào đã thay đổi, thêm mới hoặc xóa trong danh sách
// Không tốt - không có key
{
  items.map((item) => <ListItem item={item} />);
}

// Tốt - có key ổn định
{
  items.map((item) => <ListItem key={item.id} item={item} />);
}
  1. Reconciliation từ trên xuống dưới, một lần duyệt
    • React bắt đầu từ gốc của cây và đi xuống, so sánh từng cặp nút

Fiber Architecture

Từ React 16, React đã giới thiệu Fiber - một kiến trúc mới cho thuật toán reconciliation, cho phép render “có thể ngắt” (interruptible).

Đặc điểm của Fiber:

  1. Chia nhỏ công việc render

    • Thay vì xử lý toàn bộ cây component trong một lần, Fiber chia nhỏ công việc thành các đơn vị nhỏ
  2. Ưu tiên và tạm dừng công việc

    • Có thể tạm dừng, hủy bỏ hoặc ưu tiên các công việc render khác nhau
  3. Hai giai đoạn commit

    • Giai đoạn 1: Render/reconciliation (có thể ngắt)
    • Giai đoạn 2: Commit (không thể ngắt, cập nhật DOM)
Render Phase ( thể ngắt):
1. componentWillReceiveProps (deprecated)
2. static getDerivedStateFromProps
3. shouldComponentUpdate
4. componentWillUpdate (deprecated)
5. render

Commit Phase (không thể ngắt):
1. getSnapshotBeforeUpdate
2. Cập nhật DOM
3. componentDidMount / componentDidUpdate

Fiber giúp React ưu tiên các tác vụ quan trọng như animation hoặc input người dùng, cải thiện trải nghiệm người dùng đặc biệt trên thiết bị có hiệu suất thấp.

2. Các vấn đề hiệu suất phổ biến trong React

Hiểu các vấn đề hiệu suất phổ biến sẽ giúp bạn tránh chúng trong ứng dụng của mình.

Re-render không cần thiết

Một trong những vấn đề hiệu suất phổ biến nhất trong React là re-render không cần thiết.

Nguyên nhân:

  1. Props thay đổi tham chiếu nhưng không thay đổi giá trị
// Không tốt - tạo object mới mỗi lần render
function ParentComponent() {
  return <ChildComponent options={{ color: "red" }} />;
}

// Tốt - sử dụng useMemo để giữ tham chiếu ổn định
function ParentComponent() {
  const options = useMemo(() => ({ color: "red" }), []);
  return <ChildComponent options={options} />;
}
  1. Không sử dụng React.memo, useMemo, useCallback
// Không tốt - component con sẽ re-render mỗi khi parent re-render
function ChildComponent({ name }) {
  return <div>{name}</div>;
}

// Tốt - component con chỉ re-render khi props thay đổi
const ChildComponent = React.memo(function ChildComponent({ name }) {
  return <div>{name}</div>;
});
  1. Context Provider bao quanh quá nhiều component
// Không tốt - mọi component con sẽ re-render khi value thay đổi
function App() {
  const [state, setState] = useState({ user: {}, theme: "light" });
  return (
    <AppContext.Provider value={state}>
      <DeepComponent />
    </AppContext.Provider>
  );
}

// Tốt - tách context để giảm re-render
function App() {
  return (
    <UserProvider>
      <ThemeProvider>
        <DeepComponent />
      </ThemeProvider>
    </UserProvider>
  );
}

Props drilling

Props drilling xảy ra khi bạn cần truyền props qua nhiều lớp component để đến component cần sử dụng chúng.

// Props drilling
function App() {
  const [user, setUser] = useState({ name: "John" });
  return <Header user={user} />;
}

function Header({ user }) {
  return <Navigation user={user} />;
}

function Navigation({ user }) {
  return <UserMenu user={user} />;
}

function UserMenu({ user }) {
  return <div>Hello, {user.name}</div>;
}

Props drilling không chỉ làm code khó đọc mà còn có thể gây ra re-render không cần thiết ở các component trung gian.

Quản lý state không hiệu quả

Cách bạn tổ chức và quản lý state có thể ảnh hưởng lớn đến hiệu suất.

Vấn đề phổ biến:

  1. State quá lớn và không được chia nhỏ
// Không tốt - một state lớn
const [state, setState] = useState({
  user: {
    /* ... */
  },
  posts: [
    /* ... */
  ],
  comments: [
    /* ... */
  ],
  preferences: {
    /* ... */
  },
});

// Tốt - chia nhỏ state theo chức năng
const [user, setUser] = useState({
  /* ... */
});
const [posts, setPosts] = useState([
  /* ... */
]);
const [comments, setComments] = useState([
  /* ... */
]);
const [preferences, setPreferences] = useState({
  /* ... */
});
  1. Cập nhật state không đúng cách
// Không tốt - cập nhật trực tiếp state object
function updateUser(newName) {
  const newState = state;
  newState.user.name = newName;
  setState(newState); // Không gây re-render vì tham chiếu không thay đổi
}

// Tốt - tạo state mới với spread operator
function updateUser(newName) {
  setState((prevState) => ({
    ...prevState,
    user: {
      ...prevState.user,
      name: newName,
    },
  }));
}
  1. Lựa chọn thư viện quản lý state không phù hợp
    • Redux có thể quá nặng cho ứng dụng nhỏ
    • Context API có thể gây re-render không cần thiết
    • Zustand, Jotai, Recoil có thể phù hợp hơn cho các trường hợp cụ thể

3. Kỹ thuật tối ưu hiệu suất React

Sau khi hiểu các vấn đề, hãy xem các kỹ thuật để tối ưu hiệu suất React.

React Compiler, memo hoá tự động (React 19, 2024+)

Trước khi sa vào memo/useMemo/useCallback tay, nên biết: từ React 19 (GA cuối 2024), React Compiler (trước tên “React Forget”) có thể tự động memo hoá component và hook dependency, thay thế phần lớn nhu cầu viết tay các API này.

Cài và bật compiler (Next.js / Vite):

npm install --save-dev babel-plugin-react-compiler
# Cho React Compiler ESLint plugin kiểm tra code có tuân Rules of React
npm install --save-dev eslint-plugin-react-compiler
// babel.config.js (Vite, CRA-free)
module.exports = {
  plugins: [
    [
      "babel-plugin-react-compiler",
      {
        /* target: "19" */
      },
    ],
  ],
};
// next.config.js (Next.js 15+)
module.exports = {
  experimental: { reactCompiler: true },
};

Trước (phải memo thủ công):

function ProductList({ products, filter }) {
  const visible = useMemo(
    () => products.filter((p) => p.category === filter),
    [products, filter]
  );
  const handleClick = useCallback((id) => onSelect(id), [onSelect]);
  return visible.map((p) => (
    <Item key={p.id} product={p} onClick={handleClick} />
  ));
}

Sau (compiler tự thêm memo):

function ProductList({ products, filter }) {
  const visible = products.filter((p) => p.category === filter);
  const handleClick = (id) => onSelect(id);
  return visible.map((p) => (
    <Item key={p.id} product={p} onClick={handleClick} />
  ));
}

Điều kiện để compiler “làm được việc”:

  • Component phải tuân Rules of React (pure render, không mutate props/state). ESLint plugin sẽ cảnh báo.
  • Nếu vi phạm, compiler bỏ qua file đó (không memo), app vẫn chạy, chỉ không hưởng lợi.
  • Không “chiếc đũa thần” cho code xấu: quá nhiều re-render do state sai chỗ, Context không stable, v.v. compiler cũng không cứu được.

Nguyên tắc “profile-first”, nên làm quen với React DevTools Profiler:

  1. Đo trước khi optimize: <Profiler> API hoặc tab “Profiler” trong React DevTools → xem component nào render bao lâu, bao nhiêu lần.
  2. Tìm re-render gốc (cao trên tree) thay vì memo khắp nơi.
  3. Với React 19 + compiler: thường chỉ cần fix rule violation, không cần useMemo/useCallback tay.
  4. useMemo tay vẫn hữu ích cho: giữ identity stable để truyền vào useEffect dependency; tính toán cực nặng compiler không suy ra được (rare).

Tóm lại: 2025+ khuyến nghị bật React Compiler cho dự án React ≥ 19; các section memo/useMemo/useCallback dưới đây vẫn cần hiểu vì (a) compiler không cover 100%, (b) hầu hết codebase kế thừa chưa upgrade.


React.memo, useMemo, useCallback

Đây là các API chính để ngăn re-render không cần thiết trong React (vẫn cần hiểu dù có compiler, xem mục trên).

React.memo: Là một HOC (Higher Order Component) giúp bỏ qua việc render lại component nếu props không thay đổi.

// Chỉ re-render khi props thay đổi
const MemoizedComponent = React.memo(function MyComponent(props) {
  /* render using props */
});

// Tùy chỉnh logic so sánh props
const MemoizedComponent = React.memo(
  function MyComponent(props) {
    /* render using props */
  },
  (prevProps, nextProps) => {
    // Return true nếu muốn bỏ qua render
    return prevProps.id === nextProps.id;
  }
);

useMemo: Hook để ghi nhớ kết quả của một phép tính tốn kém, chỉ tính toán lại khi dependencies thay đổi.

// Chỉ tính toán lại khi dependencies thay đổi
const memoizedValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

// Giữ tham chiếu ổn định cho objects
const memoizedObject = useMemo(() => {
  return { id, name, details };
}, [id, name, details]);

useCallback: Hook để ghi nhớ một callback, tránh tạo hàm mới mỗi lần render.

// Không tốt - tạo hàm mới mỗi lần render
function ParentComponent() {
  const handleClick = () => {
    console.log("Clicked!");
  };

  return <ChildComponent onClick={handleClick} />;
}

// Tốt - giữ tham chiếu ổn định cho callback
function ParentComponent() {
  const handleClick = useCallback(() => {
    console.log("Clicked!");
  }, []);

  return <ChildComponent onClick={handleClick} />;
}

Code splitting với React.lazy và Suspense

Code splitting giúp chia nhỏ bundle JavaScript, chỉ tải những phần cần thiết khi người dùng cần.

React.lazy: Cho phép tải động component khi cần.

// Thay vì import trực tiếp
import OtherComponent from "./OtherComponent";

// Sử dụng import động
const OtherComponent = React.lazy(() => import("./OtherComponent"));

function MyComponent() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <OtherComponent />
    </Suspense>
  );
}

Suspense: Hiển thị fallback UI trong khi đợi component tải.

// Suspense với nhiều component lazy, phải định nghĩa component TRƯỚC rồi mới dùng
const Comments = React.lazy(() => import("./Comments"));
const Photos = React.lazy(() => import("./Photos"));

function MyComponent() {
  return (
    <Suspense fallback={<Loading />}>
      <Section>
        <Comments />
        <Photos />
      </Section>
    </Suspense>
  );
}

React.lazy(...) trả về một component type, không phải JSX element. Viết trực tiếp <React.lazy(...) /> không hợp lệ. Phải gán vào biến/tên (PascalCase) rồi dùng như component bình thường.

Route-based code splitting: Kết hợp với React Router để tải code theo route.

import { BrowserRouter, Routes, Route } from "react-router-dom";
import React, { Suspense } from "react";

const Home = React.lazy(() => import("./Home"));
const About = React.lazy(() => import("./About"));
const Dashboard = React.lazy(() => import("./Dashboard"));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Windowing và virtualization cho danh sách dài

Khi hiển thị danh sách dài, render tất cả các item cùng một lúc có thể gây ra hiệu suất kém. Windowing (hay virtualization) chỉ render những item hiện đang trong viewport.

react-window: Thư viện phổ biến cho virtualization trong React.

import { FixedSizeList } from "react-window";

function ListComponent({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>{items[index].name}</div>
  );

  return (
    <FixedSizeList
      height={500}
      width={300}
      itemCount={items.length}
      itemSize={35}
    >
      {Row}
    </FixedSizeList>
  );
}

react-virtualized: Một thư viện toàn diện hơn với nhiều tính năng.

import { List, AutoSizer } from "react-virtualized";

function VirtualizedList({ items }) {
  const rowRenderer = ({ key, index, style }) => {
    const item = items[index];
    return (
      <div key={key} style={style}>
        {item.name}
      </div>
    );
  };

  return (
    <div style={{ height: "500px" }}>
      <AutoSizer>
        {({ height, width }) => (
          <List
            width={width}
            height={height}
            rowCount={items.length}
            rowHeight={35}
            rowRenderer={rowRenderer}
          />
        )}
      </AutoSizer>
    </div>
  );
}

Profiler API và React DevTools

React cung cấp công cụ để đo lường và debug hiệu suất.

Profiler API: Đo lường tần suất và “chi phí” render của ứng dụng.

import { Profiler } from "react";

function onRenderCallback(
  id, // id của Profiler tree đã commit
  phase, // "mount" (lần đầu) hoặc "update" (re-render)
  actualDuration, // thời gian render component
  baseDuration, // thời gian ước tính nếu render toàn bộ subtree
  startTime, // thời điểm React bắt đầu render
  commitTime, // thời điểm React commit thay đổi
  interactions // Set các "interactions" đã gây ra update
) {
  console.log(`${id} took ${actualDuration}ms to render`);
}

function MyComponent() {
  return (
    <Profiler id="MyComponent" onRender={onRenderCallback}>
      <ChildComponent />
    </Profiler>
  );
}

React DevTools Profiler: Giao diện trực quan để phân tích hiệu suất render.

  • Flame Chart: Hiển thị thời gian render của từng component
  • Ranked Chart: Sắp xếp components theo thời gian render
  • Component Chart: Theo dõi re-renders của một component cụ thể
  • Interactions: Theo dõi các tương tác người dùng và updates

4. Server-side Rendering (SSR) và Static Site Generation (SSG)

SSR và SSG là các kỹ thuật render React ở server thay vì client, cải thiện thời gian tải trang đầu tiên và SEO.

Next.js và các giải pháp

Next.js là framework phổ biến nhất cho SSR và SSG với React.

Server-side Rendering (SSR): Render React components trên server cho mỗi request.

// pages/ssr-page.js
export async function getServerSideProps(context) {
  // Fetch data từ API
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();

  // Trả về props cho component
  return {
    props: { data }, // Sẽ được truyền vào component
  };
}

function SSRPage({ data }) {
  return <div>{data.title}</div>;
}

export default SSRPage;

Static Site Generation (SSG): Pre-render trang tại build time.

// pages/ssg-page.js
export async function getStaticProps() {
  // Fetch data tại build time
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();

  return {
    props: { data },
    // Revalidate sau 60 giây (Incremental Static Regeneration)
    revalidate: 60,
  };
}

function SSGPage({ data }) {
  return <div>{data.title}</div>;
}

export default SSGPage;

Incremental Static Regeneration (ISR): Kết hợp ưu điểm của SSG và SSR.

// pages/isr-page.js
export async function getStaticProps() {
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();

  return {
    props: { data },
    // Trang sẽ được re-generate sau 60 giây
    revalidate: 60,
  };
}

export async function getStaticPaths() {
  const res = await fetch("https://api.example.com/posts");
  const posts = await res.json();

  // Pre-render chỉ những paths này tại build time
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }));

  // { fallback: 'blocking' } sẽ server-render pages
  // khi path không được pre-render
  return { paths, fallback: "blocking" };
}

function Post({ data }) {
  return <div>{data.title}</div>;
}

export default Post;

Hydration và các vấn đề liên quan

Hydration là quá trình React “tiếp quản” HTML đã được render bởi server và thêm các event listeners.

Vấn đề phổ biến:

  1. Hydration mismatch: Xảy ra khi HTML từ server khác với HTML mà React sẽ render ở client.
// Server renders với timezone của server
function ServerComponent() {
  const date = new Date();
  return <div>{date.toString()}</div>;
}

// Client hydrates với timezone của client
// => Hydration mismatch!
  1. Hydration chậm: Quá trình hydration có thể chặn main thread, gây ra Largest Contentful Paint (LCP) và Time to Interactive (TTI) kém.

Giải pháp:

  1. Đảm bảo render đồng nhất giữa server và client:
// Sử dụng dữ liệu không phụ thuộc môi trường
function Component({ serverData }) {
  return <div>{serverData}</div>;
}
  1. Progressive Hydration: Hydrate các phần khác nhau của trang theo thứ tự ưu tiên.
// Hydrate component quan trọng trước
<Suspense fallback={<div />}>
  <CriticalComponent />
</Suspense>

// Hydrate component ít quan trọng sau
<Suspense fallback={<div />}>
  <NonCriticalComponent />
</Suspense>

Partial Hydration và Islands Architecture

Partial Hydration và Islands Architecture là các kỹ thuật mới để tối ưu quá trình hydration.

Islands Architecture: Chia trang thành các “islands” of interactivity trong “sea” of static HTML.

// Static HTML (không cần hydration)
<StaticHeader />

// Interactive island (cần hydration)
<InteractiveSearchBox />

// Static HTML (không cần hydration)
<StaticContent />

// Interactive island (cần hydration)
<InteractiveCommentSection />

// Static HTML (không cần hydration)
<StaticFooter />

Astro: Framework hỗ trợ Islands Architecture.

---
// Astro component
import ReactCounter from '../components/ReactCounter.jsx';
---

<html>
  <body>
    <h1>Static content</h1>

    <!-- Island of React -->
    <ReactCounter client:visible />

    <p>More static content</p>
  </body>
</html>

5. React Server Components

React Server Components (RSC) là một mô hình mới cho phép components chạy và render hoàn toàn trên server, không cần JavaScript ở client.

Cách hoạt động và lợi ích

Cách hoạt động:

  • Server Components chạy trên server và không bao giờ gửi JavaScript đến client
  • Client Components chạy trên client và được hydrate như thông thường
  • Server Components có thể truy cập tài nguyên server (database, filesystem)
  • Server Components có thể render Client Components
// ServerComponent.server.js (chạy trên server)
import { db } from '../database';

export default async function ServerComponent() {
  const data = await db.query('SELECT * FROM posts');

  return (
    <div>
      {data.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

// ClientComponent.client.js (chạy trên client)
'use client';

import { useState } from 'react';

export default function ClientComponent() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// App.js (kết hợp cả hai)
import ServerComponent from './ServerComponent.server';
import ClientComponent from './ClientComponent.client';

export default function App() {
  return (
    <div>
      <ServerComponent />
      <ClientComponent />
    </div>
  );
}

Lợi ích:

  1. Bundle size nhỏ hơn: Server Components không gửi JavaScript đến client

  2. Truy cập trực tiếp tài nguyên server: Không cần API layer trung gian

  3. Không có waterfalls: Server Components có thể fetch data song song

  4. Automatic code splitting: Client Components được tự động code-split

  5. Không cần hydration cho Server Components: Cải thiện Time to Interactive

So sánh với SSR truyền thống

SSR truyền thống:

  • Render toàn bộ trang trên server
  • Gửi HTML + toàn bộ JavaScript đến client
  • Hydrate toàn bộ ứng dụng trên client
  • Mỗi navigation thường yêu cầu tải lại toàn bộ trang

React Server Components:

  • Render components riêng lẻ trên server
  • Chỉ gửi HTML + JavaScript cần thiết (Client Components) đến client
  • Chỉ hydrate Client Components
  • Có thể streaming components khi chúng sẵn sàng
  • Navigation có thể chỉ cập nhật các phần cần thiết
// SSR truyền thống
// 1. Render toàn bộ App trên server
// 2. Gửi HTML đến client
// 3. Tải JavaScript
// 4. Hydrate toàn bộ App

// React Server Components
// 1. Render ServerComponents trên server
// 2. Gửi kết quả render + Client Components đến client
// 3. Hydrate chỉ Client Components

Next.js App Router: Framework đầu tiên triển khai React Server Components.

// app/page.js (Server Component mặc định)
export default async function Page() {
  const data = await fetch('https://api.example.com/data');
  const json = await data.json();

  return (
    <main>
      <h1>{json.title}</h1>
      <ClientComponent />
    </main>
  );
}

// app/client-component.js
'use client'; // Đánh dấu là Client Component

import { useState } from 'react';

export default function ClientComponent() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Streaming SSR và Partial Prerendering (Next.js 15+)

Hai tính năng nâng cấp SSR 2024–2025 đáng biết:

Streaming SSR, server gửi HTML dần qua <Suspense> thay vì đợi mọi data ready. Component chậm (ví dụ gọi API thứ 3) được stream sau, UI khung bên ngoài xuất hiện ngay:

export default function ProductPage() {
  return (
    <>
      <Header /> {/* render ngay */}
      <Suspense fallback={<SkeletonReviews />}>
        <Reviews /> {/* stream khi data sẵn sàng */}
      </Suspense>
      <Suspense fallback={<SkeletonRecs />}>
        <Recommendations /> {/* stream độc lập với Reviews */}
      </Suspense>
    </>
  );
}

Partial Prerendering (PPR), Next.js 15 kết hợp static shell (prerender build-time, trả về từ CDN ngay lập tức) với dynamic holes (stream khi có request). Một URL vừa có độ nhanh của SSG, vừa có personalization của SSR:

// app/products/[id]/page.jsx
export const experimental_ppr = true;

export default function Product({ params }) {
  return (
    <>
      <ProductDetail id={params.id} /> {/* static shell */}
      <Suspense fallback={<SkeletonCart />}>
        <CartStatus /> {/* dynamic, cookie-based */}
      </Suspense>
    </>
  );
}

Server Actions và useActionState

React 19 + Next.js 15 ổn định Server Actions (function chạy trên server, gọi từ client như form handler) và hook useActionState:

"use server";
export async function createPost(prev, formData) {
  const title = formData.get("title");
  const post = await db.post.create({ data: { title } });
  return { ok: true, id: post.id };
}
"use client";
import { useActionState } from "react";
import { createPost } from "./actions";

export function NewPostForm() {
  const [state, action, pending] = useActionState(createPost, null);
  return (
    <form action={action}>
      <input name="title" />
      <button disabled={pending}>Save</button>
      {state?.ok && <p>Created #{state.id}</p>}
    </form>
  );
}

Lợi ích cho perf: không cần API route + fetch + state machine tay → ít JS client-side, network tối giản, pending state có sẵn.

React Server Components là hướng đi, memo và hooks là công cụ hỗ trợ

Tối ưu hóa hiệu suất trong React đòi hỏi hiểu rõ cơ chế render của nó và áp dụng các kỹ thuật phù hợp. Từ việc ngăn re-render không cần thiết với React.memo, useMemo, và useCallback, đến việc sử dụng code splitting, windowing, và server-side rendering, có nhiều cách để cải thiện trải nghiệm người dùng.

React Server Components đại diện cho tương lai của React, cho phép cải thiện hiệu suất đáng kể bằng cách chỉ gửi JavaScript cần thiết đến client.


Câu hỏi hay gặp

useMemouseCallback có nên dùng khắp nơi không?

Trả lời: Không. Cả hai đều có cost (lưu reference, so sánh deps). Chỉ dùng khi: (1) truyền callback/value xuống React.memo child; (2) computation thật sự nặng; (3) reference stability cho useEffect deps. Với React Compiler (React 19+), auto-memoize sẽ giảm nhu cầu dùng thủ công.

React Server Components khác SSR thường như thế nào?

Trả lời: SSR render toàn bộ component tree trên server → HTML → hydrate toàn bộ trên client (vẫn gửi toàn bộ JS). RSC chia component thành server và client, server component không gửi JS về client, chỉ gửi serialized output. Kết quả: ít JS hơn đáng kể trên client.

Khi nào dùng React.lazy vs dynamic import?

Trả lời: React.lazy là wrapper của dynamic import() cho component. Dùng React.lazy + Suspense cho route-based code splitting. Dùng dynamic import() trực tiếp cho non-component code (utility, data parser). Next.js: dùng next/dynamic thay React.lazy để được SSR support.


Bài tiếp theo: Tối ưu hóa render trong Vue, reactivity, v-memo, Nuxt, Vapor Mode.

Tài liệu tham khảo