먼저, 메모이제이션을 자동으로 해주는 새로운 리액트 컴파일러는 리액트 19에 포함되지 않습니다. 리액트 팀은 리액트 19가 곧 출시됨을 발표하는 하나의 블로그 게시물에서 컴파일러를 함께 발표해서 생긴 오해입니다.
앞서 리액트 19에서의 변경사항을 간단히 요약하자면, 서버 컴포넌트를 위한 변경사항과 편의성을 위한 hook이 추가되었습니다.
⚠️ 해당 글에서는 주요 변경사항을 담았으며, 모든 변경사항을 담지는 않았음을 알려드립니다.
// Using <form> Actions and useActionState
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>
);
}
관례에 따라 비동기 트랜지션을 사용하는 함수를 Action이라 칭하며, useActionState를 사용하면 서버 컴포넌트에서도 Hydration 과정 없이도 즉각적으로 form을 사용할 수 있습니다. RSC를 사용하지 않는다면 일반적인 state와 동일하게 동작합니다.
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useFormStatus는 하위 컴포넌트에서도 상위 컴포넌트의 form을 읽을 수 있게 해줍니다.
const [optimisticState, toggleOptimisticIsLike] = useOptimistic<State, Value>(
state,
(currentState: State, optimisticValue: Value): State => {
return {
isLike: optimisticValue,
count: optimisticValue ? currentState.count + 1 : currentState.count - 1
}
})
const handleClick = () => {
startTransition(async () => {
const nextIsLike = !optimisticState.isLike
// 낙관적 업데이트
toggleOptimisticIsLike(nextIsLike)
try {
const response = nextIsLike ? await addLike() : await removeLike()
// 서버 응답 결과로 UI를 업데이트
setState(response)
setError('')
} catch (error) {
if (error instanceof Error) {
setError(error.message)
}
}
})
}
// 출처: <https://seungwoo.dev/posts/react-use-optimistic>
낙관적 업데이트를 쉽게 해줍니다.
리액트 18에서 추가된 useTransition에서도 비동기 함수를 지원할 수 있도록 변경되면서, 동일하게 낙관적 업데이트를 쉽게 다룰 수 있게 되었습니다.
// 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={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
import {use} from 'react';
function Comments({commentsPromise}) {
// `use` 는 promise가 완료될 때 까지 suspend 상태입니다.
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는 가장 상위의 컨텍스트 제공자를 찾아 읽을 수 있습니다. 최상위에서 호출할 필요가 없으며 조건문에서도 사용이 가능합니다.
import {use} from 'react';
import ThemeContext from './ThemeContext'
function Heading({children}) {
if (children == null) { // <- 조건문
return null;
}
// useContext를 사용할 때에는 할 수 없던 조건문 내 context 사용
const theme = use(ThemeContext);
return (
<h1 style={{color: theme.color}}>
{children}
</h1>
);
}
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' },
});
}
정적 HTML 생성을 위해 데이터를 로드하는 과정을 기다린 후 결과를 반환하며, 기존의 renderToString보다 향상된 기능을 제공합니다. 특히, Node.js 스트림이나 Web Streams와 같은 스트리밍 환경에서 동작하도록 설계되었습니다.
const getUser = cache(async (userId: string) => {
return db.getUser(userId);
});
cache() API는 데이터 페칭이나 연산 결과를 렌더링할 때마다 캐싱할 수 있게 할 수 있도록 합니다.
정적 컴포넌트 혹은 정적 렌더링 환경에서 meta 태그를 사용 가능합니다.
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>
);
}
업그레이드를 돕기 위해, codemod.com 팀과 협력하여 리액트 19의 새로운 API와 패턴으로 코드를 자동으로 업데이트할 수 있는 codemod(AST를 분석해서 코드구조를 바꿈)를 배포했습니다. codemod 명령어를 사용하여 코드 마이그레이션을 할 수 있습니다.
contextTypes와 getChildContext를 사용하는 레거시 Context (2018년 10월 v16.6.0 지원중단)React.createFactory (2020년 2월 v16.13.0 지원중단)react-test-renderer/shallow 대신 react-shallow-renderer 사용ReactDOM.render 대신 ReactDom.createRoot 사용ReactDOM.hydrate 대신 ReactDOM.hydrateRoot 사용unmountComponentAtNode(document.getElementById("root")) 대신 root.unmount() 사용ref를 prop으로 제공 )react-test-renderer 지원 중단이전 버전에서는 컴포넌트가 suspense 상태가 되면, 일시 중단된 형제 컴포넌트(같은 레벨의 컴포넌트)들이 먼저 렌더링된 후에 fallback UI가 적용되었습니다. 리액트 19에서는 컴포넌트가 suspense 상태가 되면, 먼저 fallback UI가 적용되고 그 후에 일시 중단된 형제 컴포넌트들이 렌더링됩니다.
ref를 기본적으로 forwardRef는 삭제될 예정입니다. forwardRef는 더 이상 권장되지 않지만 리액트 19에서는 호환성을 위해 계속 동작합니다.
useRef 사용시 인자 필요useRef(); -> 타입스크립트 에러발생, useRef(undefined); -> 통과
const ref = useRef<number>(null);
// 'current'는 읽기 전용 속성이므로 할당할 수 없음
ref.current = 1; // undefiend를 넣을 수 있기 때문에 더이상 이런 문제가 발생하지 않음
JSX 네임스페이스를 React.JSX로 대체참고:
https://react.dev/blog/2024/12/05/react-19
https://react.dev/reference/react/cache
https://www.intelligencelabs.tech/062b17fe-7b82-4bae-ba7f-0c17baca1bab#a520a537-7c18-49fe-8730-a200274514f8https://velog.io/@eunbinn/react-19-upgrade-guide
https://velog.io/@eunbinn/react-compiler-soon#리액트-19는-리액트-컴파일러가-아닙니다
https://siosio3103.medium.com/번역-리액트-19-forwardref-지원-중단-앞으로-ref를-전달하기-위한-표준-가이드-13c02855efd8