[React] 19 ๋ฒ์ ์ ํต์ฌ ์ ๋ฐ์ดํธ ์ฌํญ ์ดํด๋ณด๊ธฐ
๐ ๋ค์ด๊ฐ๋ฉฐ
ํ์ฌ React 19 ๋ฒ์ ์ ์ ๋ฐ์ดํธ์ ๋ํด์ ๋ง์ ๊ด์ฌ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
์ฌ๋ฌ ๋ณ๊ฒฝ์ฌํญ๋ค์ด ๋ง์ง๋ง ์ด๋ฒ ํฌ์คํ ์์๋ ํต์ฌ ๋ด์ฉ์ด๋ผ๊ณ ์๊ฐํ๋ ๋ถ๋ถ์ ํฌ์คํ ํ๋ ค ํฉ๋๋ค.
์ฆ, ๊ธฐ์กด์๋ ํ์ํ๊ณ ์ฌ์ฉํด์ผ ํ์ง๋ง 19 ๋ฒ์ ์ด ๋์ ๋๋ฉด์ ์ฌ์ฉํ์ง ์์๋ ๋๋ ๊ฒ๋ค๊ณผ ์ถ๊ฐ๋ก ๋ช ๊ฐ์ง ์๋ก์ด ๊ธฐ๋ฅ๋ค์ ์๊ฐํ๋ ค๊ณ ํฉ๋๋ค.
ํ์ฌ 2024๋ 5์ 15์ผ ๊ธฐ์ค์ผ๋ก ๋ฆฌ์กํธ 19๋ฒ์ ์ ์ฌ์ฉํด๋ณด๊ณ ์ถ๋ค๋ฉด ์นด๋๋ฆฌ ๋ฒ์ ์ ์ค์นํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
npm i react@canary react-dom@canary
or
yarn add react@canary react-dom@canary
React Compoiler
19 ๋ฒ์ ์ ๊ฐ์ฅ ํฐ ๋ถ๋ถ์ ๋ฆฌ์กํธ ์ปดํ์ผ๋ฌ์ ๋๋ค.
๋ฆฌ์กํธ ์ปดํ์ผ๋ฌ๋ ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ์ผ๋ฐ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ก ๋ณํํฉ๋๋ค.
์ฃผ์ ์ด์ ์ผ๋ก ์ ๋ฐ์ ์ธ ์ฑ ์ฑ๋ฅ์ ํฅ์ ์ํค๋ ๊ฒ์ด์ง๋ง ๋ ์ข์ ์ ์ ๊ฐ๋ฐ์๊ฐ ์ฑ๋ฅ์ ๋ํด ๋ง์ ๊ณ ๋ฏผ์ ํ ํ์๊ฐ ์ค์ด๋ค์์ต๋๋ค.
๊ธฐ์กด์๋ ๋ถํ์ํ ๋ ๋๋ง์ ์ค์ด๊ธฐ ์ํด ๋ฉ๋ชจ์ด์ ์ด์ (Memoization)์ ์ํ useCallback, useMemo, memo์ ๊ฐ์ ๊ฒ๋ค์ ์ฌ์ฉํด์ผ ํ์ต๋๋ค.
์ด ๋ฉ๋ชจ์ด์ ์ด์ ์์ฒด๋ ๋จ๋ฐํ๋ฉด ์คํ๋ ค ์ฑ๋ฅ์ด ์ ํ๋์ด์ ๊ฐ๋ฐํ๋ ์ ์ฅ์์ ์ด ๊ธฐ์ค์ ๋ช ํํ ์ก๋๋ฐ ๊ณ ๋ฏผ์ด ํ์ํ์ต๋๋ค.
์ด๋ฐ ๋ฌธ์ ๋ฅผ ๋ฆฌ์กํธ ์ปดํ์ผ๋ฌ๊ฐ ์๋์ผ๋ก ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ์ต์ ํํ์ฌ ์ด๋ฐ ๋ฌธ์ ๋ก ๊ณ ๋ฏผํ ํ์๊ฐ ์ค์ด๋ค์์ต๋๋ค.
์ฆ ์์์ ์ธํผํ ๋ฉ๋ชจ์ด์ ์ด์ ์ ์ํ ํ ๋ค์ ์ฌ์ฉํ์ง ์์๋ ๋ฉ๋๋ค.
forwardRef
์์ผ๋ก forwardRef๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์ด์ก์ต๋๋ค.
// 19 ์ด์
function App() {
const buttonRef = useRef();
const onButtonClick = () => console.log(buttonRef.current);
return (
<button ref={buttonRef} onClick={onButtonClick}>
ํด๋ฆญ
</button>
);
}
const Button = forwardRef((props, ref) => {
return <button ref={ref} {...props} />;
});
๊ธฐ์กด์๋ Ref๋ฅผ ์์ ์ปดํฌ๋ํธ์๊ฒ ์ ๋ฌํ๊ธฐ ์ํด์๋ ๋จผ์ Ref๋ฅผ ๋ง๋ ๋ค์ ๋ฐ๋ ์ปดํฌ๋ํธ์์๋ forwardRef๋ฅผ ๊ฐ์ผ ํ ref props๋ฅผ ๋ฐ์ ์ ์์์ต๋๋ค.
// 19 ์ดํ
function App() {
const buttonRef = useRef();
const onButtonClick = () => console.log(buttonRef.current);
return (
<button ref={buttonRef} onClick={onButtonClick}>
ํด๋ฆญ
</button>
);
}
const Button = ({ ref, ...props }) => {
return <button ref={ref} {...props} />;
};
์ ๋ฐ์ดํธ ์ดํ์๋ ์ ์ฝ๋์ฒ๋ผ forwadRef๋ก ๊ฐ์ธ์ง ์์๋ props๋ก ref๋ฅผ ์ ๋ฌ๋ฐ์ ์ ์๊ฒ ๋์์ต๋๋ค.
use() hook
๋ค์ํ ๋ฐ์ดํฐ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ํธ์ถํ ์ ์๋ use() hook์ด ์ถ๊ฐ ๋์์ต๋๋ค.
use() hoook์ ์ฌ์ฉํ๋ฉด promise์ context๋ฅผ ์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์ฆ, ์ฌ๋ฌ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉ๋ ์ ์๋ hook์ ๋๋ค.
๊ธฐ์กด์ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ fetching ํ๊ธฐ ์ํด useEffect๋ฅผ ์ฌ์ฉํ๊ณ ,
React context์ ์ ๊ทผํ๊ธฐ ์ํด useContext()๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ถ์ ๋์ฒดํ ์ ์์ต๋๋ค.
Fetch data - useEffect()
function Person() {
const [person, setPerson] = useState(null);
useEffect(() => {
fetchPerson().then((data) => setPerson(data));
}, []);
if (!person) return <h1>๋ถ๋ฌ์ค๋ ์ค...</h1>;
return <h1>{person.name}</h1>;
}
useEffect() ๋ด๋ถ์์ API ์์ฒญ์ ํ๊ณ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ useState๋ฅผ ํตํด์ ์ํ๋ฅผ ์ ๋ฐ์ดํธํด์ค ์ ์์์ต๋๋ค.
๋ํ, ๋ก๋ฉ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ๋ ํ ์ ์๊ณ ๋ง์ง๋ง์ผ๋ก UI๋ฅผ ์ ๋ฐ์ดํธํ ์ ์์์ฃ .
Fetch data - use()
function Person() {
const person = use(fetchPerson());
return <h1>{person.name}</h1>;
}
function App() {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Person />
</Suspense>
);
}
use() hook์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ promise๋ฅผ ๋ฐํํ๋ ํจ์์ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
๋ฆฌ์กํธ์ Suspense ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ธ์ค๋ ๋์์ UI๋ฅผ ํ์ํ๊ณ , promise๊ฐ resolve ๋๋ฉด ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.
Read context - useContext()
const UserContext = createContext();
function User() {
const user = useContext(UserContext);
return <h1>์๋
{user.name}!</h1>;
}
function App() {
return (
<UserContext.Provider value={{ name: "Yongveloper" }}>
<User />
</UserContext.Provider>
);
}
context์ ์ ๊ทผํ๊ธฐ ์ํด์ useContext()๋ฅผ ์ฌ์ฉํ์ฌ ์ ๊ทผํ๋ ค๋ conetext๋ฅผ ์์ฑํ ํ ๋ฃ์ด์ ์ ๊ทผํ ์ ์์์ต๋๋ค.
๋ํ, ์ ๊ทผํ๊ธฐ ์ํด์๋ ํด๋น Conext Provider๋ก wrapping์ด ๋์ด์์ด์ผ ํฉ๋๋ค.
Read context - use()
const UserContext = createContext();
function User() {
const user = use(UserContext);
return <h1>์๋
{user.name}!</h1>;
}
function App() {
return (
<UserContext value={{ name: "Yongveloper" }}>
<User />
</UserContext>
);
}
useConext() ๋์ ์ use() hook์ผ๋ก ๋์ฒดํ ์ ์์ต๋๋ค.
๋ํ, Context.Provider ๋์ Context๋ฅผ ํ๋ก๋ฐ์ด๋๋ก์ ๋ ๋๋ง ํ ์ ์์ต๋๋ค.
์ง์๋ฌธ(Directives)
19 ๋ฒ์ ์ ์ง์๋ฌธ์ ์ถ๊ฐํ ์ ์๊ฒ ๋์์ต๋๋ค.
์ต๊ทผ์ Next.js๋ฅผ ์ฌ์ฉํด ๋ณด์ จ๋ค๋ฉด ์ด๋ฏธ ๋ณธ ์ ์ด ์์ ๊ฑฐ์์.
์ง์๋ฌธ์ ์ปดํฌ๋ํธ ํ์ผ์ ์๋จ์ ์ถ๊ฐํ ์ ์๋ ๋ฌธ์์ด์ผ ๋ฟ์ ๋๋ค.
ํด๋ผ์ด์ธํธ์์ ํด๋ผ์ด์ธํธ๋ฅผ ํตํด์ ๋ฆฌ์กํธ๋ฅผ ์คํํ ๊ฑด์ง('use clinet'), ์๋ฒ๋ฅผ ์ฌ์ฉํด์ ๋ฆฌ์กํธ๋ฅผ ์คํํ ๊ฑด์ง ์๋ ค์ค ์ ์์ต๋๋ค.('use server')
Actions
form์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ์ฝ๊ฒ ํ ์ ์๋ ์์ฃผ ์ ์ฉํ ์๋ก์ด ๊ธฐ๋ฅ์ ๋๋ค.
function formAction(formData) {}
<form action={formAction}>
<form>, <input>, <button> ์์์ action๊ณผ formAction ํ๋กํผํฐ๋ก ํจ์๋ฅผ ์ ๋ฌํ๋ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์์ต๋๋ค.
๋ฆฌ์กํธ 19์์๋ ์๋ฒ๋ ํด๋ผ์ด์ธํธ์์ ์คํํ ์ ์์ต๋๋ค.
Clinet Actions
'use client';
function App() {
function formAction(formData) {
alert('type:' + formData.get('name'));
}
return (
<form action={formAction}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
);
}
ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์์ ์ฌ์ฉ ์์ ์ ๋๋ค.
- ์๋จ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ธ์ง ์๋ ค์ค๋๋ค.
- form์ action์ action ์์ฑํ์ฌ ํจ์๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
- formData๋ก๋ถํฐ input์ value๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
useActionState()
import {useActionState} from 'react'
useActionState() hook์ ์ก์ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์๋ก์ด ์ํ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
const [state, formAction] = useActionState(action, null);
// const [state, formAction] = useActionState(fn, initialState, permalink?);
import { useActionState } from "react"; // not react-dom
function Form({ formAction }) {
const [state, action, isPending] = useActionState(formAction);
return (
<form action={action}>
<input type="email" name="email" disabled={isPending} />
<button type="submit" disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</form>
);
}
useActionState์์ ํ์ฌ ์ํ, ์ํํ ์ก์ ํจ์, ์ฒ๋ฆฌ ์ค ์ฌ๋ถ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
useActionState()๋ฅผ ์ฌ์ฉํ์ฌ ๋ ํธ๋ฆฌํ๊ฒ form์ ์ฒ๋ฆฌํ ์ ์๊ฒ ๋์์ต๋๋ค.
useOptimistic()
์์ ์ฒ๋ฆฌ์ ๋ํด์ ์ฑ๊ณตํ๋ ์๋๋ ์ฌ์ฉ์๊ฐ ์์ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ฒ ํ๋ ๊ฒ์ ์ข์ ์ฌ์ฉ์ ๊ฒฝํ์ด ์๋๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ์ํํ ์ ์์ต๋๋ค.
import { useOptimistic } from 'react';
function AppContainer() {
const [optimisticState, addOptimistic] = useOptimistic(
state,
// updateFn
(currentState, optimisticValue) => {
// merge and return new state
// with optimistic value
}
);
}
์ด๋ฒ ์ ๋ฐ์ดํธ์์ useOptimistic() hook์ผ๋ก ํธ๋ฆฌํ๊ฒ ์ฒ๋ฆฌํ ์ ์๊ฒ ๋์์ต๋๋ค.
function ChatApp() {
const [messages, setMessages] = useState([]);
const [optimisticMessage, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { text: newMessage, sending: true }]
);
async function formAction(formData) {
const message = formData.get('message');
addOptimisticMessage(message);
const createMessage = await createMessage(message);
setMessages((messages) => [...messages, { text: createMessage }]);
}
//...
}
์ด๋ ๊ฒ ํ๊ฒ ๋๋ฉด ์๋ฒ์ ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ์ํ์ ์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ ์๋ฃ๊ฐ ๋๋ฉด ์์ ์ํ๋ฅผ ์ค์ ์๋ฒ์์ ๋ฐ์์จ ์ํ๋ก ๋ณ๊ฒฝํ ์ ์๋๋ก ๊ฐ๋ฅํฉ๋๋ค.
๐ ๋ง๋ฌด๋ฆฌ
์ด๋ฒ ๋ฆฌ์กํธ 19 ๋ฒ์ ์ ์ ์ ๋ฐ์ดํธ๊ฐ ์ ๋ง ๊ธฐ๋๋๋ ์ํฉ์ ๋๋ค.
๊ฐ์ธ์ ์ผ๋ก ์์ผ๋ก ๊ฐ๋ฐํ๋ฉด์ ์์ฃผ ์ฌ์ฉ๋ ๊ฒ ๊ฐ์ ํต์ฌ์ด๋ผ๊ณ ๋๋ผ๋ ๊ฒ์ ๋ํด์๋ง ์ ๋ฆฌ๋ฅผ ํด๋ดค์ต๋๋ค.
์์์ ์๊ฐํ ๊ฒ๋ค ์ธ์๋ ์ ์ฉํ๊ณ ๊ฐ์ ๋๋ ๊ฒ๋ค์ด ์ ๋ง ๋ง์์ ๊ณต์ ๋ฌธ์๋ฅผ ๊ผญ ํ์ธํด ๋ณด์๊ธธ ๊ถ์ฅ๋๋ฆฝ๋๋ค.
์ฐธ๊ณ :
- https://19.react.dev/reference/react
- https://www.youtube.com/watch?v=2NPIYnY3ilo&ab_channel=CodeBootcamp
- https://velog.io/@typo/react-19-beta