React 19 RC (Release Candidate) vừa được ra mắt, mang đến nhiều tính năng mới và các cải tiến quan trọng.
Bài viết này mình sẽ là tổng quan về những thay đổi và cách để bạn có thể trải nghiệm phiên bản React 19.
1. Tổng quát về các Actions
Trước đây, khi thực hiện thao tác như gửi form để cập nhật tên người dùng, bạn cần phải tự quản lý các trạng thái pending (đang xử lý), error (lỗi), optimistic updates (cập nhật lạc quan). Bạn phải code handle từng trạng thái với useState
.
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
setError(error);
return;
}
redirect("/path");
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
Trong ví dụ trên, bạn phải quản lý isPending
khi yêu cầu đang được xử lý và error
khi có lỗi.
Nhưng khi sử dụng React 19, bạn có thể sử dụng useTransition
để tự động quản lý trạng thái pending và giảm thiểu code lặp lại.
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
})
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
React 19 cho phép bạn sử dụng hook useActionState
để xử lý các trường hợp của Actions, chẳng hạn như: trạng thái đang xử lý (isPending) và lỗi (error).
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
1.1 useActionState
useActionState là một hook mới trong React 19 giúp bạn đơn giản hóa các trường hợp phổ biến của Actions. Hook này nhận vào một hàm bất đồng bộ và trả về Action đã được bọc lại, giúp quản lý trạng thái cách tự động.
useActionState trả về ba giá trị chính:
- error: là kết quả cuối cùng của Action, có thể là thông báo lỗi nếu Action gặp lỗi.
- submitAction: dùng để kích hoạt hành động khi cần.
- isPending: trạng thái chờ, sẽ tự động bật khi Action bắt đầu thực hiện và tắt khi Action hoàn thành.
Mình sẽ lấy một ví dụ sử dụng useActionState
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// Nếu có lỗi xảy ra, trả về lỗi
return error;
}
return null;
},
null,
);
useActionState sử dụng cơ chế "compose", cho phép các Actions kết hợp với nhau để quản lý trạng thái. Khi Action được kích hoạt thông qua submitAction, useActionState sẽ cập nhật kết quả cuối cùng (error) và trạng thái chờ (isPending).
Một lưu ý nhỏ rằng: trong các phiên bản Canary trước, hook React.useActionState được gọi là ReactDOM.useFormState, nhưng đã được đổi tên thành useActionState và loại bỏ useFormState.
1.2 <form>
Trong React 19, Actions được tích hợp trực tiếp vào các tính năng của <form> trong react-dom, giúp việc xử lý form dễ dàng hơn.
Bạn có thể truyền trực tiếp func vào thuộc tính action của <form> hoặc formAction của các element như: <input> và <button>. Hàm này sẽ được sử dụng như Action để tự động xử lý việc submit form mà không cần phải viết code xử lý.
<form action={actionFunction}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
Bên cạnh đó, khi Action của <form> success, React sẽ tự động reset form đối với uncontrolled components. Bạn không cần phải handle thêm code reset form.
Nhưng trường hợp bạn muốn tự handle việc reset form, React 19 cung cấp API requestFormReset
trong react-dom
.
import { requestFormReset } from 'react-dom';
function handleManualReset(formRef) {
requestFormReset(formRef.current);
}
1.3 useFormStatus
React 19 giới thiệu thêm hook useFormStatus
trong react-dom để hỗ trợ design systems cần truy cập trạng thái của <form> mà không cần phải truyền props qua nhiều cấp component.
useFormStatus
đọc trạng thái của parent <form> giống như cách hoạt động của Context provider.
import { useFormStatus } from 'react-dom';
function DesignButton() {
// `pending` sẽ cho biết form hiện đang trong trạng thái xử lý hay không
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>Submit</button>
}
Ở ví phía trên:
useFormStatus
lấy thông tin trạng thái của form cha.- { pending } trả về true nếu form đang trong trạng thái xử lý, và false khi đã hoàn tất.
- button submit sẽ tự động vô hiệu hóa (disabled) nếu form đang xử lý.
1.4 useOptimistic
useOptimistic tạo ra optimistic value mà bạn có thể cập nhật ngay khi có thay đổi, trong khi yêu cầu bất đồng bộ được thực hiện. Khi yêu cầu kết thúc, giá trị này sẽ tự động được cập nhật lại theo dữ liệu chính thức nhận được từ server (hoặc nếu có lỗi, nó sẽ trở lại giá trị ban đầu).
function ChangeName({ currentName, onUpdateName }) {
const [optimisticName, setOptimisticName] = useOptimistic(currentName);
const submitAction = async formData => {
const newName = formData.get("name");
setOptimisticName(newName);
const updatedName = await updateName(newName); onUpdateName(updatedName);
};
return (
<form action={submitAction}>
<p>Your name is: {optimisticName}</p>
<p>
<label>Change Name:</label>
<input
type="text"
name="name"
disabled={currentName !== optimisticName}
/>
</p>
</form>
);
}
Phân tích một chút ví dụ để các bạn dễ hiểu hơn nha
- useOptimistic(currentName): khởi tạo optimistic value là currentName.
- setOptimisticName(newName): cập nhật optimisticName ngay khi người dùng nhập tên mới, trước khi yêu cầu cập nhật được hoàn tất.
- khi updateName(newName) kết thúc, optimisticName sẽ được cập nhật thành updatedName hoặc sẽ back trở lại currentName nếu có lỗi xảy ra.
2. New API: use
Khi bạn gọi use với một Promise, React sẽ tự động Suspend (tạm dừng) đến khi Promise hoàn tất.
import { use } from 'react';
function Comments({ commentsPromise }) {
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({ commentsPromise }) {
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
- use(commentsPromise) sẽ tự động đợi cho đến khi commentsPromise được resolve.
- Trong khi đó, Suspense sẽ hiển thị fallback (<div>Loading...</div>) cho đến khi dữ liệu được load xong.
Lưu ý rằng: use không hỗ trợ các promise được tạo ra trong quá trình render. Nếu bạn thử truyền promise được khởi tạo trong render vào use, React sẽ cảnh báo. Do React yêu cầu promise phải caching để đảm bảo tính nhất quán khi render lại.
use
cũng cho phép bạn đọc context một cách có điều kiện. Điều này khác với useContext, vốn không thể sử dụng trong các khối điều kiện hoặc sau khi return sớm.
import { use } from 'react';
import ThemeContext from './ThemeContext';
function Heading({ children }) {
if (children == null) {
return null;
}
const theme = use(ThemeContext);
return (
<h1 style={{ color: theme.color }}>
{children}
</h1>
);
}
Trong ví dụ này:
- Nếu children là null, component sẽ return mà không cần gọi use.
- use(ThemeContext) đọc theme context khi cần, giúp linh hoạt hơn so với useContext cần được gọi ở mỗi render.
3. React Server Components
3.1 Server Components
React Server Components (RSC) trong React 19 là options mới, cho phép render các component trước khi đóng gói và tách biệt khỏi ứng dụng client hoặc server-side rendering (SSR).
- Cách hoạt động của React Server Components:
RSC có thể được render trước tại thời điểm build trên CI server (Continuous Integration), giảm thiểu công việc phải thực hiện khi người dùng truy cập ứng dụng.
Bạn cũng có thể chạy RSC cho mỗi request từ người dùng trên web server, cho phép tạo nội dung động dựa trên từng request cụ thể mà không ảnh hưởng đến hiệu suất client-side.
3.2 Server Actions
Khi bạn định nghĩa với "use server", reference (tham chiếu) sẽ tự động tạo đến hàm server đó. Tham chiếu này sau đó sẽ được truyền vào cho component ở phía client.
Khi hàm tham chiếu được gọi từ phía client, React sẽ gửi request tới server để thực thi hàm đó trên server và trả kết quả về cho client.
"use server";
async function saveNameToServer(name) {
const response = await fetch("/api/save-name", {
method: "POST",
body: JSON.stringify({ name }),
headers: { "Content-Type": "application/json" }
});
return response.json();
}
function NameForm() {
const handleSubmit = async (event) => {
event.preventDefault();
const name = event.target.elements.name.value;
const result = await saveNameToServer(name);
console.log(result);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" placeholder="Enter your name" />
<button type="submit">Save Name</button>
</form>
);
}
4. Các cải tiến trong React 19
4.1 ref as a prop
Trong React 19, ref có thể được sử dụng trực tiếp như một prop trong các function component, làm cho việc truyền và quản lý ref đơn giản hơn.
//Trước
import React, { forwardRef } from 'react';
const MyInput = forwardRef(({ placeholder }, ref) => {
return <input placeholder={placeholder} ref={ref} />;
});
// ...
<MyInput ref={ref} />;
//Sau
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// ...
<MyInput ref={ref} />;
4.2 <Context>
as a provider
Trong React 19, Context được cải tiến cho phép sử dụng <Context> trực tiếp như provider, thay vì phải sử dụng <Context.Provider>.
//Trước
const ThemeContext = createContext('');
function App({ children }) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
}
//Sau
const ThemeContext = createContext('');
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
4.3 useDeferredValue
initial value
React 19 bổ sung option initialValue cho hook useDeferredValue, giúp quản lý trải nghiệm người dùng mượt mà hơn khi có sự chênh lệch giữa giá trị hiển thị ban đầu và giá trị được xử lý sau.
function Search({ deferredValue }) {
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
5. Hỗ trợ Metadata
Với bản React 19, bạn có thể thêm các thẻ metadata như <title>, <link>, và <meta> trực tiếp vào component mà không cần phải về việc phải chèn chúng vào <head>.
Ví dụ:
function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="author" content="Josh" />
<link rel="author" href="https://twitter.com/joshcstory/" />
<meta name="keywords" content={post.keywords} />
<p>
Eee equals em-see-squared...
</p>
</article>
);
}
- Các thẻ <title>, <meta>, và <link> được khai báo ngay trong component BlogPost, giúp mỗi component tự quản lý metadata.
- Khi React render BlogPost, các thẻ metadata này sẽ tự động được hoist (chuyển lên) phần <head>.
6. Hỗ trợ Stylesheets
React 19 hỗ trợ quản lý stylesheets trực tiếp, giúp kiểm soát thứ tự và tránh trùng lặp khi chèn stylesheet vào DOM.
Bạn có thể kiểm soát thứ tự chèn stylesheets bằng thuộc tính precedence. React đảm bảo các stylesheet có độ ưu tiên cao sẽ ghi đè stylesheet khác.
function ComponentOne() {
return (
<Suspense fallback="loading...">
<link rel="stylesheet" href="foo" precedence="default" />
<link rel="stylesheet" href="bar" precedence="high" />
<article className="foo-class bar-class">
{/* Nội dung */}
</article>
</Suspense>
);
}
7. Hỗ trợ async scripts
React 19 cải tiến hỗ trợ async scripts giúp bạn chèn <script async> ngay trong component.
Bạn có thể chèn <script async> trong bất kỳ component nào phụ thuộc vào nó, giúp mã dễ đọc và dễ bảo trì hơn.
function MyComponent() {
return (
<div>
<script async src="..." />
Hello World
</div>
);
}
8. Kết luận
React 19 RC mang đến nhiều tính năng mới và cải tiến đáng chú ý. Từ việc tối ưu hóa các Actions, hỗ trợ tốt hơn cho async scripts và stylesheets, đến cải tiến các hook và Server Components. Mình thấy với hướng đi này, React 19 hứa hẹn cải thiện đáng kể hiệu quả cho các dự án frontend.
Hy vọng bài viết đã cung cấp cái nhìn toàn diện về các thay đổi trong React 19. Để tận dụng tối đa các tính năng mới này, hãy thử trải nghiệm và đánh giá nhé!
Các bài viết liên quan:
Bài viết liên quan
React Toastify là gì? Hướng dẫn sử dụng Toast Notification với React Toastify
Nov 21, 2024 • 7 min read
Hướng dẫn sử dụng Zustand trong NextJS
Nov 21, 2024 • 8 min read
Lazy Loading: Kỹ thuật Tối ưu Hiệu suất Website
Nov 17, 2024 • 14 min read
Hướng dẫn sử dụng Redux Toolkit và Redux Saga trong dự án React
Nov 15, 2024 • 10 min read
WebGL là gì? Hướng dẫn tạo đồ họa đơn giản với WebGL
Nov 13, 2024 • 7 min read
Test-Driven Development (TDD) là gì? Hướng dẫn thực hành TDD
Nov 13, 2024 • 6 min read