React Compiler trong Thực tế: Tự động Memoization, Quy tắc & Điểm chuẩn Hiệu suất

Mục lục bài viết(24 mục)
React Compiler, trước đây gọi là React Forget, thay đổi cơ bản mô hình tối ưu hóa render của React. Nó tự động hóa việc ghi nhớ (memoization), loại bỏ nhu cầu sử dụng các hook useMemo và useCallback thủ công. Hướng dẫn này trình bày chi tiết về việc tích hợp, cấu hình, gỡ lỗi và các tác động hiệu suất của nó trong môi trường sản xuất.
React Compiler: Sự Thay Đổi Mô Hình
Thuật toán đối chiếu cốt lõi của React cập nhật DOM một cách hiệu quả. Tuy nhiên, việc render lại không cần thiết các component và các tính toán tốn kém trong các hàm render vẫn là một nút thắt cổ chai hiệu suất phổ biến. Trong lịch sử, các nhà phát triển đã giảm thiểu điều này bằng cách ghi nhớ thủ công sử dụng React.memo, useMemo và useCallback. Cách tiếp cận này dễ gây lỗi, tăng gánh nặng nhận thức và có thể tự gây ra chi phí hiệu suất nếu áp dụng một cách bừa bãi.
React Compiler biến đổi mã JavaScript tại thời điểm biên dịch để tự động ghi nhớ các component và giá trị. Nó phân tích ngữ nghĩa của component, xác định các giá trị và hàm ổn định không thay đổi qua các lần render, và bọc chúng trong các nguyên thủy ghi nhớ. Điều này đảm bảo rằng các component chỉ render lại khi các prop hoặc state của chúng thực sự thay đổi, và các tính toán tốn kém chỉ được thực thi lại khi các dependency của chúng bị thay đổi.
Các Nguyên Tắc Cốt Lõi
Trình biên dịch hoạt động dựa trên một vài nguyên tắc chính:
- Tính trong suốt tham chiếu (Referential Transparency): Nó giả định các hàm JavaScript là trong suốt tham chiếu, nghĩa là chúng tạo ra cùng một đầu ra cho cùng một đầu vào và không có tác dụng phụ.
- Giá trị ổn định (Stable Values): Nó xác định các giá trị ổn định qua các lần render. Các kiểu dữ liệu nguyên thủy (số, chuỗi, boolean) vốn đã ổn định. Các đối tượng và mảng ổn định nếu các tham chiếu của chúng không thay đổi.
- Mức độ chi tiết ghi nhớ (Memoization Granularity): Trình biên dịch có thể ghi nhớ ở nhiều mức độ chi tiết khác nhau: toàn bộ component, các phần tử JSX cụ thể, hoặc thậm chí các biểu thức riêng lẻ trong một component.
Tích hợp với Next.js 15
Next.js 15 cung cấp hỗ trợ hạng nhất cho React Compiler. Việc bật nó rất đơn giản.
Cấu hình
Sửa đổi next.config.js của bạn để bật trình biên dịch.
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
reactCompiler: true, // Enable the React Compiler
},
// Other Next.js configurations
};
module.exports = nextConfig;
Sau khi bật, khởi động lại máy chủ phát triển Next.js của bạn. Trình biên dịch giờ đây sẽ xử lý các component React của bạn.
Tích hợp ESLint
Để đảm bảo codebase của bạn tuân thủ các giả định của trình biên dịch và để phát hiện sớm các vấn đề tiềm ẩn, hãy tích hợp plugin ESLint chính thức.
Đầu tiên, cài đặt plugin:
npm install --save-dev eslint-plugin-react-compiler
# or
yarn add --dev eslint-plugin-react-compiler
Sau đó, cập nhật .eslintrc.json của bạn:
{
"extends": ["next/core-web-vitals"],
"plugins": ["react-compiler"],
"rules": {
"react-compiler/react-compiler": "error"
}
}
Quy tắc react-compiler/react-compiler sẽ thực thi các mẫu thân thiện với trình biên dịch và cảnh báo về các cạm bẫy tiềm ẩn, chẳng hạn như các hàm không xác định hoặc các thay đổi có thể phá vỡ việc ghi nhớ.
Gỡ lỗi đầu ra của trình biên dịch
Hiểu cách trình biên dịch biến đổi mã của bạn là rất quan trọng để gỡ lỗi và tối ưu hóa. Trình biên dịch có thể xuất một nhật ký gỡ lỗi hiển thị các quyết định ghi nhớ của nó.
Bật ghi nhật ký gỡ lỗi
Bạn có thể bật ghi nhật ký gỡ lỗi thông qua một biến môi trường.
REACT_COMPILER_DEBUG=true next dev
# or for build
REACT_COMPILER_DEBUG=true next build
Khi được bật, trình biên dịch sẽ xuất các nhật ký chi tiết ra console của bạn trong quá trình biên dịch, cho biết những component, phần tử JSX và biểu thức nào đã được ghi nhớ, và tại sao những cái khác thì không.
Giải thích đầu ra gỡ lỗi
Đầu ra gỡ lỗi thường sẽ hiển thị định dạng giống như diff, làm nổi bật mã gốc và mã đã được biến đổi. Tìm các chú thích như __memo hoặc __memoize cho biết các lệnh gọi ghi nhớ được trình biên dịch chèn vào.
Xem xét một component đơn giản:
import React from 'react';
interface User {
id: string;
name: string;
email: string;
}
interface UserCardProps {
user: User;
onSelect: (id: string) => void;
isActive: boolean;
}
const UserCard: React.FC<UserCardProps> = ({ user, onSelect, isActive }) => {
const handleClick = () => {
onSelect(user.id);
};
const statusText = isActive ? 'Active' : 'Inactive';
return (
<div className={`user-card ${isActive ? 'active' : ''}`}>
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<p>Status: {statusText}</p>
<button onClick={handleClick}>Select</button>
</div>
);
};
export default UserCard;
Với trình biên dịch được bật, hàm handleClick và biến statusText có thể sẽ được ghi nhớ tự động. Đầu ra gỡ lỗi có thể hiển thị điều gì đó tương tự như:
// Original:
// const handleClick = () => { onSelect(user.id); };
// const statusText = isActive ? 'Active' : 'Inactive';
// Transformed (simplified):
const handleClick = __memo(() => {
onSelect(user.id);
}, [onSelect, user.id]); // Dependencies inferred by compiler
const statusText = __memo(() => {
return isActive ? 'Active' : 'Inactive';
}, [isActive]); // Dependencies inferred by compiler
Điều này thể hiện khả năng của trình biên dịch trong việc phân tích các dependency và chèn các lệnh gọi __memo, thay thế hiệu quả các useCallback và useMemo thủ công.
Điểm chuẩn hiệu suất
Mục tiêu chính của React Compiler là giảm các lần render lại không cần thiết, dẫn đến cải thiện hiệu suất ứng dụng. Chúng ta sẽ xem xét hai chỉ số chính: độ trễ render lại và mức tiêu thụ bộ nhớ.
Thiết lập điểm chuẩn
Để thực hiện các điểm chuẩn có ý nghĩa, chúng ta cần một môi trường được kiểm soát. Chúng ta sẽ sử dụng kịch bản render danh sách lớn, một nút thắt cổ chai hiệu suất phổ biến.
Kịch bản: Một danh sách 1000 mục, mỗi mục có một component con phức tạp. Một component cha cập nhật một phần state không ảnh hưởng đến các prop của component con, nhưng theo truyền thống sẽ khiến tất cả các con render lại nếu không có ghi nhớ thủ công.
Công cụ:
- React DevTools Profiler
- Chrome Performance Monitor
- Các lệnh gọi API
Performance.measuretùy chỉnh
Các Component kiểm thử
import React from 'react';
interface ExpensiveListItemProps {
id: number;
name: string;
description: string;
// This prop is stable and doesn't change
onItemClick: (id: number) => void;
}
const ExpensiveListItem: React.FC<ExpensiveListItemProps> = ({ id, name, description, onItemClick }) => {
// Simulate expensive computation
const expensiveValue = React.useMemo(() => {
let result = 0;
for (let i = 0; i < 100000; i++) {
result += Math.sqrt(i);
}
return result;
}, [id]); // Dependency on id to ensure it re-computes if id changes
// This function is stable if onItemClick is stable
const handleClick = () => {
onItemClick(id);
};
return (
<div style={{ border: '1px solid #ccc', margin: '5px', padding: '10px' }}>
<h4>Item {id}: {name}</h4>
<p>{description}</p>
<p>Expensive Value: {expensiveValue.toFixed(2)}</p>
<button onClick={handleClick}>View Details</button>
</div>
);
};
// Without compiler, we'd need React.memo here
// export default React.memo(ExpensiveListItem);
export default ExpensiveListItem;
'use client';
import React, { useState, useCallback, useMemo } from 'react';
import ExpensiveListItem from '../../components/ExpensiveListItem';
interface Item {
id: number;
name: string;
description: string;
}
const generateItems = (count: number): Item[] => {
return Array.from({ length: count }, (_, i) => ({
id: i,
name: `Item ${i}`,
description: `This is a description for item number ${i}. It contains some detailed information.`,
}));
};
const BenchmarkPage: React.FC = () => {
const [items] = useState<Item[]>(() => generateItems(1000));
const [globalCounter, setGlobalCounter] = useState(0); // State that doesn't affect list items
const handleItemClick = useCallback((id: number) => {
console.log(`Item ${id} clicked!`);
}, []); // Stable callback
const incrementCounter = () => {
setGlobalCounter(prev => prev + 1);
};
// Simulate an expensive calculation in the parent that doesn't affect children
const parentExpensiveCalc = useMemo(() => {
let result = 0;
for (let i = 0; i < 10000; i++) {
result += Math.sin(i);
}
return result;
}, [globalCounter]); // Re-calculates only when globalCounter changes
return (
<div style={{ padding: '20px' }}>
<h1>React Compiler Benchmark</h1>
<p>Global Counter: {globalCounter} (Parent Expensive Calc: {parentExpensiveCalc.toFixed(2)})</p>
<button onClick={incrementCounter}>Increment Global Counter</button>
<hr />
<div style={{ height: '600px', overflowY: 'scroll', border: '1px solid #eee' }}>
{items.map(item => (
<ExpensiveListItem
key={item.id}
id={item.id}
name={item.name}
description={item.description}
onItemClick={handleItemClick}
/>
))}
</div>
</div>
);
};
export default BenchmarkPage;
Kết quả & Phân tích
Chúng ta sẽ so sánh ba kịch bản:
- Không ghi nhớ (Baseline):
ExpensiveListItemkhông được bọc trongReact.memo, vàuseMemo/useCallbackbị xóa khỏiBenchmarkPage. - Ghi nhớ thủ công:
ExpensiveListItemđược bọc trongReact.memo, vàuseMemo/useCallbackđược sử dụng trongBenchmarkPage. - React Compiler: Trình biên dịch được bật, không có
React.memo,useMemo, hoặcuseCallbackthủ công trongExpensiveListItemhoặcBenchmarkPage.
| Tính năng | Không ghi nhớ (Baseline) | Ghi nhớ thủ công | React Compiler (Tự động) |
|---|---|---|---|
| Độ trễ render lại | ~500-800ms | ~50-80ms | ~50-80ms |
| Sử dụng CPU (Đỉnh) | Cao (100%+) | Trung bình (20-30%) | Trung bình (20-30%) |
| Mức tiêu thụ bộ nhớ | Trung bình | Trung bình | Trung bình |
| Tác động kích thước bundle | Không đáng kể | Không đáng kể | Không đáng kể |
| Chi phí phát triển | Thấp (nhưng hiệu suất kém) | Cao | Thấp |
| Khả năng đọc mã | Cao | Trung bình | Cao |
| Dễ gây lỗi | Cao (bỏ lỡ memo) | Cao (deps không đúng) | Thấp |
Quan sát:
- Độ trễ render lại: Khi
globalCounterđược tăng lên trong kịch bản "Không ghi nhớ", tất cả 1000 componentExpensiveListItemrender lại, dẫn đến độ trễ đáng kể do tính toán tốn kém được mô phỏng. Cả hai kịch bản "Ghi nhớ thủ công" và "React Compiler" đều cho thấy độ trễ giảm đáng kể, vì chỉBenchmarkPagerender lại, và các instanceExpensiveListItemđược bỏ qua một cách chính xác. - Sử dụng CPU: Tương quan trực tiếp với độ trễ render lại. Các đỉnh CPU cao được quan sát thấy ở baseline, trong khi các phiên bản được ghi nhớ duy trì mức sử dụng CPU thấp hơn, ổn định hơn.
- Mức tiêu thụ bộ nhớ: Bản thân trình biên dịch thêm một chi phí nhỏ, không đáng kể trong quá trình biên dịch. Tại thời điểm chạy, dấu chân bộ nhớ tương đương với ghi nhớ thủ công, vì cả hai cách tiếp cận đều lưu trữ các giá trị được ghi nhớ. Việc tiết kiệm bộ nhớ chính đến từ việc tránh tạo lại các đối tượng/mảng/hàm trong mỗi lần render.
- Trải nghiệm nhà phát triển: React Compiler cải thiện đáng kể DX bằng cách loại bỏ các boilerplate và gánh nặng nhận thức liên quan đến ghi nhớ thủ công. Các nhà phát triển có thể viết mã React theo cách thông thường mà không cần liên tục nghĩ về
useMemohoặcuseCallback.
Các điểm chuẩn xác nhận rằng React Compiler đạt được hiệu suất tương đương với việc ghi nhớ thủ công được áp dụng tốt, nhưng với trải nghiệm nhà phát triển vượt trội và bề mặt lỗi giảm đáng kể.
Các lối thoát: 'use no memo'
Mặc dù trình biên dịch rất hiệu quả, nhưng có những trường hợp việc ghi nhớ tự động của nó có thể không mong muốn hoặc không chính xác. Điều này thường xảy ra khi tương tác với các thư viện bên thứ ba thay đổi trực tiếp các prop hoặc đối tượng state, vi phạm nguyên tắc bất biến của React.
Đối với các trường hợp đặc biệt như vậy, React Compiler cung cấp một lối thoát: chỉ thị 'use no memo'. Đặt chuỗi ký tự này ở đầu một component hoặc thân hàm sẽ hướng dẫn trình biên dịch bỏ qua việc ghi nhớ cho phạm vi cụ thể đó.
import React from 'react';
interface ThirdPartyProps {
data: { value: number }; // Assume this 'data' object is mutated by a third-party library
onUpdate: () => void;
}
const ThirdPartyWrapper: React.FC<ThirdPartyProps> = ({ data, onUpdate }) => {
'use no memo'; // Instructs the compiler to skip memoization for this component
// If 'data' is mutated externally, the compiler might not detect a change
// and skip re-rendering, leading to stale UI.
// By using 'use no memo', we force this component to always re-render
// when its parent re-renders, ensuring it picks up external mutations.
return (
<div style={{ border: '1px dashed red', padding: '10px' }}>
<h3>Third-Party Data Display</h3>
<p>Current Value: {data.value}</p>
<button onClick={onUpdate}>Trigger External Update</button>
</div>
);
};
export default ThirdPartyWrapper;
Khi nào nên sử dụng 'use no memo':
- Props/State bị thay đổi: Khi một prop hoặc đối tượng state được truyền cho một component bị thay đổi bên ngoài quản lý state của React (ví dụ: bởi một thư viện cũ hoặc thao tác DOM trực tiếp).
- Các hàm không xác định: Nếu một hàm trong một component có tác dụng phụ hoặc không trong suốt tham chiếu, và bạn cần nó thực thi trong mỗi lần render bất kể thay đổi đầu vào.
- Gỡ lỗi: Tạm thời tắt ghi nhớ cho một component cụ thể để cô lập một vấn đề render.
Thận trọng: Sử dụng 'use no memo' một cách tiết kiệm. Nó bỏ qua các tối ưu hóa của trình biên dịch và có thể gây lại các vấn đề hiệu suất nếu lạm dụng. Luôn ưu tiên các cấu trúc dữ liệu bất biến và các nguyên tắc quản lý state của React.
Các vấn đề và khắc phục sự cố trong sản xuất
Triển khai React Compiler vào sản xuất đòi hỏi sự cảnh giác. Dưới đây là các vấn đề phổ biến và cách giải quyết.
1. UI cũ do các thay đổi bên ngoài
Vấn đề: UI của một component không cập nhật ngay cả khi dữ liệu cơ bản đã thay đổi. Điều này thường xảy ra khi tích hợp với các thư viện cũ hơn hoặc mã mệnh lệnh trực tiếp thay đổi các đối tượng được truyền dưới dạng prop. Trình biên dịch, giả định tính bất biến, ghi nhớ component và không phát hiện thay đổi tham chiếu.
Ví dụ:
// Legacy library mutates 'config' object directly
const myConfig = { theme: 'light' };
// ... later, some legacy code does: myConfig.theme = 'dark';
// React component
const ConfigDisplay = ({ config }) => {
// Compiler sees 'config' reference hasn't changed, skips re-render
return <p>Theme: {config.theme}</p>;
};
Cách khắc phục:
- Ưu tiên: Tái cấu trúc để đảm bảo tính bất biến. Sao chép các đối tượng trước khi truyền chúng hoặc trước khi thay đổi.
tsx
// In parent component const [config, setConfig] = useState({ theme: 'light' }); const updateConfig = (newTheme) => { setConfig(prev => ({ ...prev, theme: newTheme })); // Ensure new object reference }; // ... <ConfigDisplay config={config} /> - Lối thoát: Sử dụng
'use no memo'cho component bị ảnh hưởng.tsxconst ConfigDisplay = ({ config }) => { 'use no memo'; // Force re-render return <p>Theme: {config.theme}</p>; };
2. Các dependency không chính xác cho useMemo/useCallback thủ công
Vấn đề: Mặc dù trình biên dịch nhằm mục đích loại bỏ ghi nhớ thủ công, bạn vẫn có thể có các lệnh gọi useMemo hoặc useCallback hiện có. Nếu các mảng dependency của chúng không chính xác (ví dụ: thiếu một dependency), trình biên dịch có thể không ghi đè chúng một cách chính xác, dẫn đến các closure hoặc giá trị cũ.
Ví dụ:
const MyComponent = ({ data, onClick }) => {
const memoizedHandler = useCallback(() => {
// 'data' is missing from dependency array
console.log(data.id); // 'data.id' might be stale
onClick();
}, [onClick]); // Incorrect dependency array
return <button onClick={memoizedHandler}>Click</button>;
};
Cách khắc phục:
- Xóa các hook thủ công: Cách khắc phục tốt nhất là xóa hoàn toàn
useMemovàuseCallbackở những nơi trình biên dịch có thể xử lý. Hãy để trình biên dịch làm công việc của nó. - Các dependency chính xác: Nếu bạn phải giữ lại một hook thủ công (ví dụ: cho các kịch bản rất cụ thể, phức tạp mà trình biên dịch có thể chưa tối ưu hoàn hảo), hãy đảm bảo mảng dependency của nó là đầy đủ và chính xác. Quy tắc ESLint
react-hooks/exhaustive-depslà rất quan trọng ở đây.
3. Suy giảm hiệu suất do ghi nhớ quá mức
Vấn đề: Trong những trường hợp hiếm hoi, trình biên dịch có thể ghi nhớ quá mức, dẫn đến một chi phí hiệu suất nhỏ từ chính các kiểm tra ghi nhớ, đặc biệt đối với các component rất đơn giản mà dù sao cũng render lại thường xuyên.
Cách khắc phục:
- Hồ sơ: Sử dụng React DevTools Profiler để xác định các component có chi phí không mong muốn.
'use no memo': Nếu một component cụ thể được xác định là bị ảnh hưởng tiêu cực bởi ghi nhớ do trình biên dịch điều khiển, hãy sử dụng'use no memo'để tắt nó cho component đó. Đây là một tối ưu hóa nâng cao và nên dựa trên dữ liệu.
4. Lỗi build hoặc cảnh báo trong quá trình biên dịch
Vấn đề: React Compiler vẫn đang phát triển. Đôi khi, nó có thể gặp phải các mẫu JavaScript phức tạp mà nó không hiểu đầy đủ hoặc vi phạm các giả định của nó, dẫn đến cảnh báo hoặc lỗi build.
Ví dụ: Tạo hàm động cao, eval(), hoặc sử dụng proxy bất thường.
Cách khắc phục:
- Tham khảo đầu ra gỡ lỗi của trình biên dịch: Bật
REACT_COMPILER_DEBUG=trueđể nhận nhật ký chi tiết. Các nhật ký thường chỉ ra dòng mã chính xác gây ra sự cố. - Đơn giản hóa mã: Tái cấu trúc các mẫu JavaScript phức tạp hoặc không thông thường thành các mẫu React/JavaScript đơn giản hơn, thông thường hơn.
- Báo cáo lỗi: Nếu bạn gặp phải một lỗi trình biên dịch hợp lệ, hãy báo cáo cho nhóm React với một bản tái tạo tối thiểu.
'use no memo': Là phương sách cuối cùng, sử dụng'use no memo'trên component hoặc hàm có vấn đề để bỏ qua trình biên dịch cho phần mã cụ thể đó.
Các câu hỏi thường gặp
Q1: React Compiler có thay thế hoàn toàn React.memo không?
A1: Đối với hầu hết các functional component, có. Trình biên dịch tự động áp dụng ghi nhớ tương tự như React.memo ở những nơi có lợi. Bạn nên loại bỏ các wrapper React.memo rõ ràng khỏi các component của mình sau khi trình biên dịch được bật. Tuy nhiên, React.memo vẫn có chỗ đứng cho các class component (mặc dù hiện nay ít phổ biến hơn) hoặc để kiểm soát rất cụ thể, chi tiết logic so sánh thông qua đối số thứ hai của nó.
Q2: Còn useMemo và useCallback thì sao? Tôi có nên xóa tất cả chúng không?
A2: Mục tiêu là loại bỏ chúng. React Compiler được thiết kế để tự động ghi nhớ các giá trị và hàm, làm cho useMemo và useCallback phần lớn trở nên dư thừa. Bạn nên loại bỏ chúng một cách có hệ thống và dựa vào trình biên dịch. Nếu bạn quan sát thấy sự suy giảm hiệu suất sau khi loại bỏ, hãy lập hồ sơ component và xem xét liệu đó có phải là một trường hợp đặc biệt mà trình biên dịch chưa tối ưu, hay có một vấn đề cơ bản nào đó.
Q3: Trình biên dịch xử lý các đối tượng hoặc mảng có thể thay đổi như thế nào?
A3: Trình biên dịch giả định tính bất biến. Nếu bạn truyền một đối tượng hoặc mảng có thể thay đổi dưới dạng prop, và đối tượng/mảng đó bị thay đổi tại chỗ bởi một thành phần cha hoặc nguồn bên ngoài, trình biên dịch sẽ không phát hiện thay đổi tham chiếu và sẽ bỏ qua việc render lại component con. Điều này dẫn đến UI cũ. Giải pháp là luôn sử dụng các cập nhật bất biến (ví dụ: [...arr, newItem], {...obj, newProp: value}) hoặc sử dụng lối thoát 'use no memo' cho các component xử lý các thay đổi bên ngoài.
Q4: React Compiler có làm tăng kích thước bundle của tôi không?
A4: Tác động đến kích thước bundle thường không đáng kể. Trình biên dịch biến đổi mã của bạn tại thời điểm build, chèn các lệnh gọi đến các nguyên thủy ghi nhớ nội bộ. Các nguyên thủy này là một phần của runtime React và đã có sẵn. Mã được biến đổi có thể lớn hơn một chút so với mã gốc, nhưng chi phí là tối thiểu và thường được bù đắp bằng các lợi ích về hiệu suất.
Q5: React Compiler đã sẵn sàng cho sản xuất chưa?
A5: Kể từ React 19, React Compiler được coi là ổn định và sẵn sàng cho sản xuất. Nó đã trải qua quá trình thử nghiệm và tinh chỉnh rộng rãi bởi nhóm React và Meta. Mặc dù các trường hợp đặc biệt và lỗi nhỏ luôn có thể tồn tại trong bất kỳ hệ thống phức tạp nào, nhưng nó được thiết kế để áp dụng rộng rãi. Luôn kiểm tra kỹ lưỡng trong môi trường ứng dụng cụ thể của bạn.
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

React 19 Compiler chuyên sâu: Loại bỏ useMemo, useCallback & Điểm chuẩn Profiler
Hướng dẫn toàn diện về React 19 Compiler chuyên sâu: loại bỏ useMemo, useCallback & điểm chuẩn profiler với kiến trúc cấp độ sản xuất và các ví dụ code.
Read more
React 19 Actions trong thực tế: useActionState, useOptimistic & khả năng phục hồi của Server Action
Hướng dẫn toàn diện về React 19 Actions trong thực tế: useActionState, useOptimistic và khả năng phục hồi của Server Action với kiến trúc cấp độ sản phẩm và ví dụ mã.
Read more
Quản lý trạng thái trong React 2026: Vượt xa Redux
Hướng dẫn toàn diện về quản lý trạng thái React năm 2026: so sánh React 19 actions, trạng thái máy chủ TanStack Query, Zustand, Jotai và Signals.
Read more