React 19 버전이 출시되었다. 변경 점에 대해 알아보자.
1. Actions
1.1 개념
- Actions는 React 앱에서 비동기로 데이터를 전송하고, 이에 따른 상태 업데이트를 단순화하기 위한 새 기능이다.
- 요청 대기 상태(loading), 에러 처리, 낙관적 업데이트 등을 자동으로 관리해준다.
1.2 예시 코드
기존 방식(대기 상태와 에러를 직접 관리)
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={(e) => setName(e.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</div>
);
}
React 19 - useTransition로 대기 상태 자동 관리
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={(e) => setName(e.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</div>
);
}
2. useActionState 훅
- useActionState는 액션(서버 요청) 로직을 한 곳에 모아, 결과 상태(에러, 응답 등)와 대기 상태를 간편하게 제어하는 훅이다.
- 반환값으로 [actionResult, submitAction, isPending] 형식(또는 [에러, 액션함수, 대기상태])을 제공한다.
const [error, submitAction, isPending] = useActionState(
async (prevState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
- 위 코드에서 submitAction은 폼 제출에 사용되고, error와 isPending은 자동으로 업데이트된다.
3. <form>와 useFormStatus
3.1 <form> action={함수} 사용
- React 19에서는 <form> 태그의 action, formAction에 함수를 직접 전달할 수 있다.
- 제출 시 자동으로 대기 상태를 관리해주고, 성공 시 폼이 초기화된다.
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
3.2 useFormStatus 훅
- useFormStatus를 통해 부모 폼의 상태를 쉽게 조회할 수 있다.
- 디자인 시스템 같은 곳에서 별도 Context를 정의하지 않고도, 현재 폼의 대기 상태 등을 가져올 수 있다.
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>Submit</button>;
}
4. useOptimistic 훅
- 낙관적 업데이트(Optimistic Update)를 손쉽게 구현하기 위한 훅이다.
- 서버 응답 전이라도 UI 상태를 미리 갱신하고, 오류 발생 시 되돌리는 로직을 간단히 처리한다.
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" />
</p>
</form>
);
}
5. 새 API: use
- 렌더링 중에 프로미스나 Context를 읽고, 자동으로 서스펜스(Suspense) 처리할 수 있도록 해주는 API다.
- 예: use(commentsPromise)로 호출 시, 프로미스가 완료될 때까지 UI를 서스펜스 처리한다.
import { use } from 'react';
function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // 프로미스 완료 전까지 Suspense
return comments.map((c) => <p key={c.id}>{c.text}</p>);
}
- 또, use(ThemeContext)처럼 조건부로 Context를 사용하는 것도 지원한다.
- 다만, 렌더링 도중 새로 만든 프로미스는 지원하지 않는다.
6. React DOM Static API
- react-dom/static에 prerender, prerenderToNodeStream가 추가되어, 데이터 로딩이 완료된 정적 HTML을 생성한다.
- 기존 renderToString와 달리, 모든 비동기 로딩이 끝날 때까지 기다린 후 HTML을 반환하는 방식이다.
import { prerender } from 'react-dom/static';
async function handler(request) {
const { prelude } = await prerender(<App />, {
bootstrapScripts: ['/main.js'],
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
7. React Server Components
- 서버 컴포넌트(Server Components)는 빌드 시점이나 웹 서버에서 미리 컴포넌트를 렌더링하는 방식을 제공한다.
- 클라이언트 측의 번들 크기를 줄이고, 서버 자원을 활용해 렌더링 성능을 높일 수 있다.
8. Server Actions
- "use server" 지시어를 통해 클라이언트 컴포넌트가 서버 함수를 직접 호출할 수 있게 해준다.
- 함수가 클라이언트에서 실행되는 대신, React가 자동으로 서버로 요청을 보내고 결과를 가져온다.
// server.js
"use server";
export async function saveDataOnServer(data) {
// 서버 로직
return await someDBCall(data);
}
// client.js
import { saveDataOnServer } from './server.js';
function ClientComponent() {
const handleClick = async () => {
const result = await saveDataOnServer({ foo: 'bar' });
console.log(result);
};
return <button onClick={handleClick}>Save</button>;
}
9. ref를 prop으로 사용
- 함수형 컴포넌트에서 ref를 직접 prop으로 받을 수 있게 되었다.
- 기존 forwardRef 없이도 DOM 요소나 컴포넌트 인스턴스에 접근 가능하다.
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// 사용 예시
<MyInput ref={inputRef} placeholder="Enter text" />
10. 하이드레이션 오류 개선
- 서버 렌더링된 내용과 클라이언트 렌더링이 불일치할 때, 기존에는 여러 번 경고가 쏟아졌으나 이제 단 한 번에 명확히 알려준다.
- 콘솔에 mismatch 부분을 diff 형태로 표기해 디버깅을 간단히 한다.
11. <Context> 자체를 Provider로 사용
- <Context.Provider> 대신 <Context> 태그를 직접 사용할 수 있다.
const ThemeContext = createContext('');
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
12. Ref 콜백의 정리(cleanup) 함수
- Ref 콜백에서 반환값으로 정리 함수를 반환하면, 언마운트 시 자동으로 호출된다.
- 기존에는 언마운트 시 ref 콜백이 null로 재호출되는 방식이었으나, 이제 더 명확하게 cleanup 로직을 작성할 수 있다.
<input
ref={(el) => {
// 생성 로직
return () => {
// cleanup 로직
};
}}
/>
13. useDeferredValue 초기값
- useDeferredValue(value, initialValue) 형태로 초기값을 지정할 수 있다.
- 첫 렌더에서 initialValue를 사용하고, 이후에 비동기로 value를 반영한다.
function Search({ deferredInput }) {
const value = useDeferredValue(deferredInput, '');
return <Results query={value} />;
}
14. 문서 메타데이터 지원
- 컴포넌트에서 <title>, <meta>, <link> 태그를 직접 작성하면, 자동으로 <head> 영역으로 호이스팅된다.
- 서버 렌더링, 스트리밍 환경에서도 일관된 메타데이터 관리가 가능하다.
function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="author" content="Author Name" />
<link rel="canonical" href={`/posts/${post.id}`} />
<p>...</p>
</article>
);
}
15. 스타일시트 관리
- <link rel="stylesheet">나 <style> 요소를 컴포넌트 단위로 작성해도, React가 중복 로드 없이 알맞은 순서로 DOM에 삽입한다.
- precedence 속성을 이용해 스타일 우선순위를 지정할 수 있다.
function ComponentOne() {
return (
<Suspense fallback="loading...">
<link rel="stylesheet" href="foo.css" precedence="default" />
<link rel="stylesheet" href="bar.css" precedence="high" />
<div className="foo-class bar-class">...</div>
</Suspense>
);
}
16. 비동기 스크립트(async)
- <script async>를 여러 컴포넌트에서 렌더링해도 한 번만 로드되고 실행된다.
- 서버 렌더링 시 <head>로 모아서, 중요 자원(스타일, 폰트 등)보다 뒤에 로드하도록 우선순위를 조정한다.
function MyComponent() {
return (
<div>
<script async src="myScript.js" />
Content...
</div>
);
}
17. 리소스 사전 로드
- prefetchDNS, preconnect, preload, preinit 등을 사용해 브라우저가 필요한 자원을 미리 가져오도록 지시할 수 있다.
- 초기 페이지 로딩 성능 개선이나 예상되는 네비게이션 시 미리 로딩하는 데 유용하다.
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';
function MyComponent() {
preconnect('https://cdn.example.com');
preload('https://cdn.example.com/font.woff', { as: 'font' });
preinit('https://cdn.example.com/main.js', { as: 'script' });
return <div>Content...</div>;
}
18. 커스텀 엘리먼트 지원
- 커스텀 엘리먼트를 사용할 때, 서버 렌더링 시 원시 타입(props)만 속성으로 렌더링하고, 클라이언트에서는 프로퍼티로 할당한다.
- CustomElement 관련 호환성이 크게 향상되었다.
<my-custom-element someProp={123} onChange={handleChange} />
'React' 카테고리의 다른 글
Transient Props를 사용해 styled-component 에러 해결하기 (0) | 2024.09.28 |
---|---|
React + MSW(Mock Service Worker)로 백엔드 API 없이 개발하기 (0) | 2024.09.24 |
react-datepicker로 생년월일 값 받기 (0) | 2024.09.18 |
[React] 제어 컴포넌트와 비제어 컴포넌트 (2) | 2024.09.11 |
CRA없이 Webpack(웹팩) + Babel(바벨) 기본 설정하기 (0) | 2024.07.29 |