React v.19 - Mai-Nova/AIssue-Overview GitHub Wiki
ํ์ฌ ํ๋ก์ ํธ์ react version:
- react: 19.1.0
- react-dom: 19.1.0
- react-router-dom: 7.6.0
- vite: 6.3.5
- @vitejs/plugin-react-swc": 3.9.0
-
๊ธฐ์กด ์ฑ์์ ํ๋ ์์ํฌ๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ๊ฑฐ๋ Vite, Parcel, RSBuild ์ ๊ฐ์ ๋น๋ ๋๊ตฌ๋ก ๋ง์ด๊ทธ๋ ์ด์ ๊ถ์ฅ
-
Next.js , React Router , Expo ์ ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์๋ก์ด ํ๋ก์ ํธ์ ๊ถ์ฅ
-
ํ๋ ์์ํฌ๋ก ๋ง์ด๊ทธ๋ ์ด์
- ํด๋ผ์ด์ธํธ์ธก CSR, SPA ์ง์
์ ์ธ์ (declarative)/ ๋ฐ์ดํฐ(data) / framework ์ธ๊ฐ์ง๊ฐ ์๋ค.
- declarative
import { BrowserRouter } from "react-router";
ReactDOM.createRoot(root).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
- data
import { createBrowserRouter, RouterProvider } from "react-router";
let router = createBrowserRouter([
{
path: "/",
Component: Root,
loader: loadRootData,
},
]);
ReactDOM.createRoot(root).render(<RouterProvider router={router} />);
- framework mode : Vite ํ๋ฌ๊ทธ์ธ
- ํ์ ์ธ์ดํ href
- ์ ํ ์์ ๊ฒฝ๋ก ๋ชจ๋ API
- ์ง๋ฅํ ์ฝ๋ ๋ถํ
- SPA, SSR ๋ฐ ์ ์ ๋ ๋๋ง ์ ๋ต
// routes.ts
import { index, route } from "@react-router/dev/routes";
export default [index("./home.tsx"), route("products/:pid", "./product.tsx")];
//product.tsx
import { index, route } from "@react-router/dev/routes";
export default [index("./home.tsx"), route("products/:pid", "./product.tsx")];
import {RouterProvider, createBrowserRouter} from 'react-router';
import Home from './Home';
import Dashboard from './Dashboard';
// โ
Each route has it's own URL
const router = createBrowserRouter([
{path: '/', element: <Home />},
{path: '/dashboard', element: <Dashboard />}
]);
export default function App() {
return (
<RouterProvider value={router} />
)
- Network Waterfalls : ์ฝ๋ ๋ค์ด๋ก๋ ์ค์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๋ ฌ๋ก ๊ฐ์ ธ์ค๋ ๋์ ์ฑ์ด ๋ ๋๋ง๋ ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒฝ์ฐ ๋ฐ์ํฉ๋๋ค.
- ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋๊ธฐ ์ ์ ์์ฒญ์ด ์์๋๋๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์ค๋ ์ต์ ์ ์ ๊ณตํฉ๋๋ค. ๊ฒฝ๋ก ์์ค์์ ๋ฐ์ดํฐ ์ข ์์ฑ์ ์ง์ ํ๊ธฐ ์ํด ๋ผ์ฐํ "๋ก๋" ํจํด๊ณผ ํตํฉํ ๋ ๊ฐ์ฅ ์ ์๋ํ๋ฉฐ, ์ด๋ฅผ ํตํด ๋ผ์ฐํฐ๊ฐ ๋ฐ์ดํฐ ํจ์น๋ฅผ ์ต์ ํํ ์ ์์ต๋๋ค.
export default function Dashboard() {
const [data, setData] = useState(null);
// โ Fetching data in a component causes network waterfalls
useEffect(() => {
fetch("/api/data")
.then((response) => response.json())
.then((data) => setData(data));
}, []);
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
export async function loader() {
const response = await fetch(`/api/data`);
const data = await response.json();
return data;
}
// โ
Fetching data in parallel while the code is downloading
export default function Dashboard({ loaderData }) {
return (
<div>
{loaderData.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
-
์ด๊ธฐ ๋ก๋ ์ ๋ผ์ฐํฐ๋ ๊ฒฝ๋ก๊ฐ ๋ ๋๋ง๋๊ธฐ ์ง์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ์ฌ์ฉ์๊ฐ ์ฑ์ ํ์ํ ๋ ๋ผ์ฐํฐ๋ ๋ฐ์ดํฐ์ ๊ฒฝ๋ก๋ฅผ ๋์์ ๊ฐ์ ธ์์ ๊ฐ์ ธ์ค๊ธฐ ์์ ์ ๋ณ๋ ฌํํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ํ๋ฉด์ ์ฝํ ์ธ ๋ฅผ ํ์ํ๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ์ ์ค์ด๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
-
์ฑ์ ๋ก๋๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑํด์ผ ํ๋ฉฐ ๋ณต์ก์ฑ์ ๊ฐ์ํด์ผ ์ฑ๋ฅ์ด ์ ํ๋ฉ๋๋ค.
- ์ฝ๋๊ฐ ๋ค์ด๋ก๋๋๋ ๋์ ๋ณ๋ ฌ๋ก ์ฝ๋๋ฅผ ๊ฐ์ ธ์ค๋ ๋ผ์ฐํฐ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด, React Router๋ lazy๊ฒฝ๋ก๊ฐ ๋ก๋๋ ๋ ์ฝ๋ ๋ถํ ๋ฐ ์ต์ ํ๋๋๋ก ์ง์ ํ๋ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.
import Home from "./Home";
import Dashboard from "./Dashboard";
// โ
Routes are downloaded before rendering
const router = createBrowserRouter([
{ path: "/", lazy: () => import("./Home") },
{ path: "/dashboard", lazy: () => import("Dashboard") },
]);
- ๋ผ์ฐํฐ ๋ฐ ๋ฐ์ดํฐ ๋ก๋ฉ ์๋ฃจ์ ๊ณผ ํตํฉํ์ฌ ์บ์ฑ์ ๊ทน๋ํํ๊ณ , ํ์น๋ฅผ ๋ณ๋ ฌํํ๋ฉฐ, "์ํธ ์์ฉ ์ ๊ฐ์ ธ์ค๊ธฐ" ํจํด์ ์ง์ํ๋ ๊ฒ์ด ๊ฐ์ฅ ํจ๊ณผ์ ์ ๋๋ค.
- React Hook
- v.18์ด๋ ์กฐ๊ธ ๋ค๋ฆ
- UI์ ์ผ๋ถ๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ ๋๋ง ํ ์ ์๋๋ก ํด์ฃผ๋ React Hook - UI ๋ฐ์์ฑ์ ์ ์ง(state)
- ์ฐ์ ์์๋ฅผ ์ค์ startTransition
const [isPending, startTransition] = useTransition()
- ์ฌ์ฉ๋ฒ
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
- isPending ํ๋๊ทธ๋ ๋๊ธฐ ์ค์ธ Transition ์ด ์๋์ง ์๋ ค์ค๋๋ค.
- startTransition ํจ์๋ ์ํ ์ ๋ฐ์ดํธ๋ฅผ Transition ์ผ๋ก ํ์ํ ์ ์๊ฒ ํด์ฃผ๋ ํจ์์ ๋๋ค.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
- startTransition ๋ด์์ ํธ์ถ๋๋ ํจ์๋ฅผ โActionsโ์ด๋ผ๊ณ ํฉ๋๋ค. ๋ฐ๋ผ์ ์์ ํธ๋์ง์ ๋ด์์ ํธ์ถ๋๋ ๋ชจ๋ ์ฝ๋ฐฑ(์: ์ฝ๋ฐฑ ํ๋กํผํฐ)์ ์ด๋ฆ์ action์ด๊ฑฐ๋ โActionโ ์ ๋ฏธ์ฌ๋ฅผ ํฌํจํด์ผ ํฉ๋๋ค.
- ์งํ ์ค์ธ Transition์ ๋ํด ์ฌ์ฉ์์๊ฒ ํผ๋๋ฐฑ์ ์ ๊ณตํ๊ธฐ ์ํด isPending ์ํ๋ startTransition์ ์ฒ์ ํธ์ถํ ๋ true๋ก ์ ํ๋๋ฉฐ, ๋ชจ๋ Action์ด ์๋ฃ๋์ด ์ต์ข ์ํ๊ฐ ์ฌ์ฉ์์๊ฒ ํ์๋ ๋๊น์ง true ์ํ๋ฅผ ์ ์งํฉ๋๋ค.
- Transition์ด ์งํ ์ค์ผ ๋ useOptimistic์ ์ฌ์ฉํ์ฌ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
function SubmitButton({ submitAction }) {
const [isPending, startTransition] = useTransition();
return (
<button
disabled={isPending}
onClick={() => {
startTransition(() => {
submitAction();
});
}}
>
Submit
</button>
);
}
์ฐธ๊ณ -Action๊ณผ ์ผ๋ฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ์ ์ฐจ์ด์
- ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ ์ฐ์ ์์๋ฅผ ์ค๋ค
const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);
-
useActionState๋ฅผ ์ปดํฌ๋ํธ์ ์ต์์ ๋ ๋ฒจ์์ ํธ์ถํ์ฌ ํผ ์ก์ ์ด ์คํ๋ ๋ ์ ๋ฐ์ดํธ๋๋ ์ปดํฌ๋ํธ State๋ฅผ ์์ฑํ์ธ์. useActionState๋ ๊ธฐ์กด์ ํผ ์ก์ ํจ์์ ์ด๊ธฐ State๋ฅผ ์ ๋ฌ๋ฐ๊ณ , ํผ์์ ์ฌ์ฉํ ์๋ก์ด ์ก์ ์ ๋ฐํํฉ๋๋ค. ๋ํ ์ต์ ํผ State์ ์ก์ ์ด ๋๊ธฐ ์ค์ธ์ง ์ฌ๋ถisPending๋ ๋ฐํํฉ๋๋ค. ์ด๋ ์ต์ ํผ State๋ useActionState์ ์ ๋ฌํ ํจ์์๋ ํจ๊ป ์ ๋ฌ๋ฉ๋๋ค.
-
๋ฐํ๊ฐ: ์ธ๊ฐ์ง ๊ฐ์ ๋ด์ ๋ฐฐ์ด
-
1.ํ์ฌ State์ ๋๋ค. ์ฒซ ๋ ๋๋ง ์์๋ initialState์ ์ผ์นํ๋ฉฐ, ์ก์ ์ด ์คํ๋ ํ์๋ ํด๋น ์ก์ ์ด ๋ฐํํ ๊ฐ๊ณผ ์ผ์นํฉ๋๋ค.
-
- form ์ปดํฌ๋ํธ์ action Prop์ด๋, ํผ ๋ด๋ถ button ์ปดํฌ๋ํธ์ formAction Prop์ ์ ๋ฌํ ์ ์๋ ์ ์ก์ ์ ๋๋ค. ์ด ์ก์ ์ startTransition ๋ด์์ ์๋์ผ๋ก ํธ์ถํ ์๋ ์์ต๋๋ค.
-
- ํ์ฌ Transition์ด ๋๊ธฐ ์ค์ธ์ง ์๋ ค์ฃผ๋ isPending ํ๋๊ทธ์ ๋๋ค.
import { useActionState } from 'react';
import { action } from './actions.js';
function MyComponent() {
const [state, formAction] = useActionState(action, null);
// ...
return (
<form action={formAction}>
{/* ... */}
</form>
);
}
useActionState๊ฐ ๋ฐํํ๋ ๋ฐฐ์ด์ ๋ค์๊ณผ ๊ฐ์ ์์๋ฅผ ๊ฐ์ต๋๋ค.
- ํผ์ ํ์ฌ State๋, ์ฒ์์๋ ์ ๋ฌํ ์ด๊ธฐ State(null)๋ก ์ค์ ๋๋ฉฐ, ํผ์ด ์ ์ถ๋ ํ์๋ ์ ๋ฌํ ์ก์ ์ ๋ฐํ๊ฐ์ผ๋ก ์ค์ ๋ฉ๋๋ค.
-
<form>
์ action Prop์ ์ ๋ฌํ๊ฑฐ๋ startTransition ์์์ ์ง์ ํธ์ถํ ์ ์๋์๋ก์ด ์ก์ ์ ๋๋ค.(formAction) - ์ก์ ์ด ์ฒ๋ฆฌ๋๋ ๋์ ์ฌ์ฉํ ์ ์๋ ๋๊ธฐ Pending State์ ๋๋ค.
- ํผ์ด ์ ์ถ๋๋ฉด, ์ ๊ณตํ ์ก์ ํจ์๊ฐ ํธ์ถ๋๋ฉฐ, ํด๋น ํจ์์ ๋ฐํ๊ฐ์ด ์๋ก์ด ํ์ฌ State๋ก ์ค์ ๋ฉ๋๋ค.
- ์ด ์ก์ ํจ์๋ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ํ์ฌ State๋ฅผ ์ถ๊ฐ๋ก ์ ๋ฌ๋ฐ์ต๋๋ค. ์ฒ์ ์ ์ถ๋ ๋๋์ด๊ธฐ State๊ฐ ์ ๋ฌ๋๋ฉฐ, ์ดํ ์ ์ถ๋ถํฐ๋ ์ง์ ํธ์ถ ์ ๋ฐํ๋ ๊ฐ์ด ์ ๋ฌ๋ฉ๋๋ค. ๋๋จธ์ง ์ธ์๋ค์ useActionState๋ฅผ ์ฌ์ฉํ์ง ์์์ ๋์ ๋์ผํฉ๋๋ค.
์์)
//App.js
import { useActionState } from "react";
import { addToCart } from "./actions.js";
function AddToCartForm({itemID, itemTitle}) {
const [message, formAction, isPending] = useActionState(addToCart, null);
return (
<form action={formAction}>
<h2>{itemTitle}</h2>
<input type="hidden" name="itemID" value={itemID} />
<button type="submit">Add to Cart</button>
{isPending ? "Loading..." : message}
</form>
);
}
export default function App() {
return (
<>
<AddToCartForm itemID="1" itemTitle="JavaScript: The Definitive Guide" />
<AddToCartForm itemID="2" itemTitle="JavaScript: The Good Parts" />
</>
);
}
//actions.js
"use server";
export async function addToCart(prevState, queryData) {
const itemID = queryData.get('itemID');
if (itemID === "1") {
return "Added to cart";
} else {
// Add a fake delay to make waiting noticeable.
await new Promise(resolve => {
setTimeout(resolve, 2000);
});
return "Couldn't add to cart: the item is sold out.";
}
}
- useOptimistic ๋ UI๋ฅผ ๋๊ด์ ์ผ๋ก ์ ๋ฐ์ดํธํ ์ ์๊ฒ ํด์ฃผ๋ React Hook์ ๋๋ค.
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
- React Hook์ผ๋ก, ๋น๋๊ธฐ ์์ ์ด ์งํ ์ค์ผ ๋ ๋ค๋ฅธ ์ํ๋ฅผ ๋ณด์ฌ์ค ์ ์๊ฒ ํด์ค๋๋ค. ์ธ์๋ก ์ฃผ์ด์ง ์ผ๋ถ ์ํ๋ฅผ ๋ฐ์, ๋คํธ์ํฌ ์์ฒญ๊ณผ ๊ฐ์ ๋น๋๊ธฐ ์์ ๊ธฐ๊ฐ ๋์ ๋ฌ๋ผ์ง ์ ์๋ ๊ทธ ์ํ์ ๋ณต์ฌ๋ณธ์ ๋ฐํํฉ๋๋ค. ํ์ฌ ์ํ์ ์์ ์ ์ ๋ ฅ์ ์ทจํ๋ ํจ์๋ฅผ ์ ๊ณตํ๊ณ , ์์ ์ด ๋๊ธฐ ์ค์ผ ๋ ์ฌ์ฉํ ๋๊ด์ ์ธ ์ํ๋ฅผ ๋ฐํํฉ๋๋ค.
- ์ด ์ํ๋ โ๋๊ด์ โ ์ํ๋ผ๊ณ ๋ถ๋ฆฌ๋๋ฐ, ์ค์ ๋ก ์์ ์ ์๋ฃํ๋ ๋ฐ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋๋ผ๋ ์ฌ์ฉ์์๊ฒ ์ฆ์ ์์ ์ ๊ฒฐ๊ณผ๋ฅผ ํ์ํ๊ธฐ ์ํด ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
ex) ์ข์์ user๊ฐ ์ข์์ ๋๋ ์๋ ? : frontend > backend > db๊น์ง๊ฐ์ง ์๊ณ useOptimistic์ ์จ์ ๋๊ด์ ์ํ๋ก ํ์ํ๊ณ ์ฌ์ฉํ ์ ์๋ค.
-
๋งค๊ฐ๋ณ์
- state: ์์ ์ด ๋๊ธฐ ์ค์ด์ง ์์ ๋ ์ด๊ธฐ์ ๋ฐํ๋ ๊ฐ์ ๋๋ค.
- updateFn(currentState, optimisticValue): ํ์ฌ ์ํ์ addOptimistic์ ์ ๋ฌ๋ ๋๊ด์ ์ธ ๊ฐ์ ์ทจํ๋ ํจ์๋ก, ๊ฒฐ๊ณผ์ ์ธ ๋๊ด์ ์ธ ์ํ๋ฅผ ๋ฐํํฉ๋๋ค. ์์ ํจ์์ฌ์ผ ํฉ๋๋ค. updateFn์ ๋ ๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ์ทจํฉ๋๋ค. currentState์ optimisticValue. ๋ฐํ ๊ฐ์ currentState์ optimisticValue์ ๋ณํฉ๋ ๊ฐ์ ๋๋ค.
-
๋ฐํ๊ฐ
- optimisticState: ๊ฒฐ๊ณผ์ ์ธ ๋๊ด์ ์ธ ์ํ์ ๋๋ค. ์์ ์ด ๋๊ธฐ ์ค์ด์ง ์์ ๋๋ state์ ๋์ผํ๋ฉฐ, ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ updateFn์์ ๋ฐํ๋ ๊ฐ๊ณผ ๋์ผํฉ๋๋ค.
- addOptimistic: addOptimistic๋ ๋๊ด์ ์ธ ์ ๋ฐ์ดํธ๊ฐ ์์ ๋ ํธ์ถํ๋ dispatch ํจ์์ ๋๋ค. ์ด๋ ํ ํ์ ์ optimisticValue๋ผ๋ ํ๋์ ์ธ์๋ฅผ ์ทจํ๋ฉฐ, state์ optimisticValue๋ก updateFn์ ํธ์ถํฉ๋๋ค.
- useActionState๋ ํผ ์ก์ ์ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก State๋ฅผ ์ ๋ฐ์ดํธํ ์ ์๋๋ก ์ ๊ณตํ๋ Hook์ ๋๋ค.
import { useFormStatus } from "react-dom";
import action from './actions';
function Submit() {
const status = useFormStatus();
return <button disabled={status.pending}>Submit</button>
}
export default function App() {
return (
<form action={action}>
<Submit />
</form>
);
}
-
์ํ ์ ๋ณด๋ฅผ ์ ๊ณต๋ฐ๊ธฐ ์ํด Submit ์ปดํฌ๋ํธ๋ฅผ
<form>
๋ด๋ถ์ ๋ ๋๋งํด์ผ ํฉ๋๋ค. ์ด Hook์ ํผ์ด ํ์ฌ ์ ์ถํ๊ณ ์๋ ์ํ์ธ์ง๋ฅผ ์๋ฏธํ๋ pending ํ๋กํผํฐ์ ๊ฐ์ ์ํ ์ ๋ณด๋ฅผ ๋ฐํํฉ๋๋ค. -
useFormStatus Hook์ form ๋ด๋ถ์ ๋ ๋๋งํ ์ปดํฌ๋ํธ์์ ํธ์ถํด์ผ ํฉ๋๋ค.
-
useFormStatus๋ ์ค์ง ์์ form์ ๋ํ ์ํ ์ ๋ณด๋ง ๋ฐํํฉ๋๋ค. ๋์ผํ ์ปดํฌ๋ํธ๋ ์์ ์ปดํฌ๋ํธ์์ ๋ ๋๋งํ form์ ์ํ ์ ๋ณด๋ ๋ฐํํ์ง ์์ต๋๋ค.
-
๋ฐํ๊ฐ
-
pending: ๋ถ๋ฆฌ์ธ ๊ฐ์ ๋๋ค. true๋ผ๋ฉด ์์ form์ด ์์ง ์ ์ถ ์ค์ด๋ผ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด false์ ๋๋ค.
-
data: FormData ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฐ์ฒด๋ก, ์์ form์ด ์ ์ถํ๋ ๋ฐ์ดํฐ๋ฅผ ํฌํจํฉ๋๋ค. ํ์ฑํ๋ ์ ์ถ์ด ์๊ฑฐ๋ ์์์ form์ด ์๋ ๊ฒฝ์ฐ์๋ null์ ๋๋ค.
-
method: 'get' ๋๋ 'post' ์ค ํ๋์ ๋ฌธ์์ด ๊ฐ์ ๋๋ค. ์ด ํ๋กํผํฐ๋ ์์ form์ด GET ๋๋ POST HTTP ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ถ๋๋์ง๋ฅผ ๋ํ๋ ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก form์ GET ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉฐ method ํ๋กํผํฐ๋ฅผ ํตํด ์ง์ ํ ์ ์์ต๋๋ค.
-
action: ์์ form์ action Prop์ ์ ๋ฌํ ํจ์์ ๋ ํผ๋ฐ์ค์ ๋๋ค. ์์
์ด ์๋ ๊ฒฝ์ฐ์๋ ์ด ํ๋กํผํฐ๋ null์ ๋๋ค. action Prop์ URI ๊ฐ์ด ์ ๊ณต๋์๊ฑฐ๋ action prop๋ฅผ ์ง์ ํ์ง ์์์ ๊ฒฝ์ฐ์๋ status.action์ null์ ๋๋ค.
-
import { useFormStatus } from "react-dom";
import { submitForm } from "./actions.js";
function Submit() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Submitting..." : "Submit"}
</button>
);
}
function Form({ action }) {
return (
<form action={action}>
<Submit />
</form>
);
}
export default function App() {
return <Form action={submitForm} />;
}
- React 19์์ ์๋กญ๊ฒ ์ ๊ณต๋๋ cache() API๋ฅผ ํ์ฉํด ์ด๋ฅผ ์ต์ํํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํ๋ค. ์ค๋ณต ์์ฒญ์ ๋ฐฉ์งํ๊ณ ์บ์ฑ์ ํ์ฉํด ๋ถํ์ํ ๋๊ธฐ ์๊ฐ์ ์ค์์ผ๋ก์จ, ์๋ฒ ์ปดํฌ๋ํธ์ ์ฑ๋ฅ์ ํ์ธต ๊ฐ์ ํ ์ ์๋ค๋ ์ ์ด ํต์ฌ์ด๋ค.
- ์๋ฒ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ ์์ฒญ์ด ๋ถ๋ชจโ์์์ผ๋ก ์์ฐจ์ ์ผ๋ก ์ด๋ค์ ธ ์ ์ฒด ๋ ๋๋ง์ ์ง์ฐ์ํค๋ ๋ฌธ์ ๊ฐ ์๋๋ฐ, ์ด๋ฅผ Waterfall Fetching์ด๋ผ ๋ถ๋ฅธ๋ค.
- React 19 cache()API๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ๋๋ ๊ณ์ฐ ๊ฒฐ๊ณผ๋ฅผ ์บ์ํ ์ ์์ต๋๋ค. ์ด API๋ React Server Components์ ํจ๊ป ์ฌ์ฉํ๋๋ก ์ค๊ณ๋์์ต๋๋ค.
const getUser = cache(async (userId: string) => {
return db.getUser(userId);
})
- Next.js์ ํ๋ฆฌ๋ก๋ํจํด์ ์ฌ์ฉํด ๋ฐ์ดํฐ ๋ฏธ๋ฆฌ ๋ก๋ํ๋ ๊ฒ ๋ฐฉ์ง
Reference :