React 19 베타 버전이 4월 25일에 출시됬습니다.
사실 최근까지 모르고 있다가, 어떤 프론트 친구가 리액트 19 베타를 보고 발광하는 걸 보고 알았습니다.
원래는 그냥 넘기려고 그랬는데, 어차피 써야 할 거 한번 미리 정리해봤습니다.
Pending 상태를 감지하여 자동으로 값을 변화시켜주는 useTransition
, 기본적인 Action 처리에 사용되는 useActionState
가 추가되었습니다.
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => { // isPending이 true로 변경됩니다.
const error = await updateName(name);
if (error) {
setError(error);
return; // isPending이 false로, error가 true로 변경됩니다.
}
redirect("/path");
}) // isPending이 false로 변경됩니다.
};
const [error, submitAction, isPending] = useActionState(
// 각각 에러 여부, 함수, 대기 여부를 나타냅니다.
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error; // error가 true로, isPending이 false로 변경됩니다.
}
redirect("/path");
return null;
},
null, // isPending이 false로 변경됩니다.
);
return <button onClick={submitAction}>test<button>
Form에서도 새롭게 추가된 액션을 지원합니다.
React 19부터 <Form>
, <Button>
, <Input>
태그에서 바로 Action을 사용하도록 하는 속성을 추가할 수 있게 됩니다.
또한 Form의 Action 상태를 감지하는 useFormStatus
가 추가되었습니다.
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
// 버튼을 누르면 바로 비활성화됩니다.
폼의 상태에 따라 낙관적으로 값을 설정하는 useOptimistic
이 추가되었습니다.
const [optimisticName, setOptimisticName] = useOptimistic("test"); //기본 이름이 test로 설정됩니다.
const submitAction = async formData => {
const newName = formData.get("name");
setOptimisticName(newName); // optimisticName을 새로 받아온 이름으로 변경합니다.
const updatedName = await updateName(newName);
onUpdateName(updatedName);
// 실패할 시, optimisticName이 원래대로 돌아갑니다.
};
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>
promise가 종료되기 전까지 코드를 중지하는 use
가 추가되었습니다.
참고로 렌더링시 생성된 promise를 지원하지 않기 떄문에, 해당 promise는 프레임워크에서 직접 보내주거나 promise 캐싱이 가능한 suspend 라이브러리를 사용해야 합니다.
const Comments = ({commentsPromise}) => {
// commentsPromise가 종료될 때까지 코드가 중지됩니다.
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
// commentsPromise가 종료될 떄까지 Suspens가 출력됩니다.
return <Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
또한 context를 읽는 데도 사용할 수 있습니다.
const theme = use(ThemeContext);
console.log(theme.color);
클라이언트나 SSR 서버 환경과 분리된 환경에서 번들 전에 컴포넌트를 미리 렌더링할 수 있습니다.
이를 CI 서버에서 빌드시에만 실행되게 하거나, 웹 서버에서 요청마다 실행되게 하게 설정할 수 있습니다.
Server Action은 클라이언트가 서버에서 실행된 비동기 함수를 호출할 수 있게 합니다.
만약 Server Action이 "use strict"
지시문과 함꼐 설정되면, 프레임워크가 자동으로 서버 함수의 레퍼런스를 생성하여 클라이언트로 넘겨줍니다.
이 함수가 실행되면, React가 서버에 실행 요청을 보낸 후 데이터를 반환합니다.
참고로 "use server"
지시문은 서버 액션에서만 쓰이는 지시문이며, 서버 컴포넌트에선 사용할 수 없습니다.
버전 자체가 올라간 만큼 개선된 점도 많습니다.
특히 원래 HTML과 JS에서 지원하던 요소들이 추가로 React에서 지원된 경우가 많습니다.
forWardRef
속성 대신 ref
속성으로 직접 ref를 전달해줄 수 있게 됩니다.
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
<MyInput ref={ref} />
에러 로그가 두 번씩 출력되던 문제가 해결되었습니다.
Uncaught Error: hit
at Throws
at renderWithHooks
…
--------------------------------
Uncaught Error: hit // 이 항목이 더 이상 출력되지 않습니다.
at Throws
at renderWithHooks
…
--------------------------------
The above error occurred in the Throws component:
at Throws
at ErrorBoundary
at App
React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.
하이드레이션 에러 로그가 아래와 같이 변경됩니다.
Uncaught Error: Hydration failed because the server rendered HTML didn’t match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component used:
- A server/client branch if (typeof window !== 'undefined').
- Variable input such as Date.now() or Math.random() which changes each time it’s called.
- Date formatting in a user’s locale which doesn’t match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
<App>
<span>
+ Client
- Server
at throwOnHydrationMismatch
…
이제 <Context.Provider>
를 <Context>
로 바로 사용할 수 있습니다.
const ThemeContext = createContext('');
<ThemeContext value="dark">
{children}
</ThemeContext>
컴포넌트가 unMount되면 ref가 제거되게 할 수 있는 클린업 함수가 추가됩니다.
만약 타입스크립트와 함꼐 사용할 경우, 코드를 <div ref={current => {instance = current}} />
의 형태로 작성하는 것을 추천드립니다.
<input
ref={(ref) => {
// ...
return () => {
// 컴포넌트 unMount시 작동
};
}}
/>
useDeferredValue
에 기본값 옵션이 추가되었습니다.
아래와 같이 사용하며, 첫 렌더시 해당 값을 반환합니다.
렌더 후에는, deferredValue가 반환된 상태로 백그라운드에서 렌더링을 다시 예약합니다.
const value = useDeferredValue(deferredValue, '');
컴포넌트에서 메타데이터 태그를 작성하면, 컴포넌트가 Mount됬을 때 해당 메타데이터가 head 태그로 호이스팅됩니다.
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} />
// 여기까지 자동으로 head 태그에 호이스팅됩니다.
<p>
Eee equals em-see-squared...
</p>
</article>
);
우선 순위를 제대로 명시하고, 위치를 잘 잡아야 한다는 단점과 함께 스타일시트가 지원됩니다.
function ComponentOne() {
return (
<Suspense fallback="loading...">
<link rel="stylesheet" href="foo" precedence="default" />
<link rel="stylesheet" href="bar" precedence="high" />
<article class="foo-class bar-class">
{...}
</article>
</Suspense>
)
}
function ComponentTwo() {
return (
<div>
<p>{...}</p>
<link rel="stylesheet" href="baz" precedence="default" />
// foo와 bar 사이에 삽입됩니다.
</div>
)
}
코드의 위치 등을 관리할 필요 없이 어디서든 비동기 스크립트를 렌더링하는, 더 나은 비동기 스크립트 지원이 추가됩니다.
function MyComponent() {
return (
<div>
<script async={true} src="..." />
Hello World
</div>
)
}
function App() {
<html>
<body>
<MyComponent>
...
<MyComponent> // 따로 비동기 코드가 겹치지 않게 됩니다.
</body>
</html>
}
효율적으로 자원을 불러오기 위해, 프리로드 API가 지원됩니다.
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly
preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font
preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet
prefetchDNS('https://...') // when you may not actually request anything from this host
preconnect('https://...') // when you will request something but aren't sure what
}
알 수 없는 prop을 property가 아닌 attribute으로 취급했던 이전 버전들과는 다르게, 아래 전략과 함꼐 property가 완벽하게 지원됩니다.
SSR: 컴포넌트에 넘어온 prop들 중 값이 true거나, 타입이 string, number 등의 기본 타입인 prop은 property으로 인식합니다.
CSR: 커스텀 요소 인스턴스와 부합하는 prop은 property로, 아닌 prop은 attribute로 인식합니다.
<head>
와 <body>
에 들어있는 예상치 못한 태그들은 이제 불일치 오류를 피하기 위해 무시합니다.
만약 hydration 불일치 오류로 인해 리렌더링이 필요한 상황에서는, 서드파티와 확장 프로그램들의 스타일시트를 그대로 두고 리렌더링됩니다.
이번 React 19에서도 많은게 바뀌었습니다.
특히 성능 개선과 함께 비동기와 관련된 내용들이 많이 추가되고 수정됬고, 원래 HTML에서 지원하던 부분들을 더 지원하게 된 걸로 보입니다.
저는 메타데이터가 head로 호이스팅되게 됬다는 점이 매우 마음에 드는 것 같습니다.
(지금까지 페이스북 메신저나 디스코드에서 알림이 왔을 때 웹 버전이면 타이틀이 바뀌는 방식으로 알려줘왔는데, 이 부분이 구현하기 편해져서 좋네요)
번역기 없이 직접 번역하고 내용을 잘라낸 거라서 중요한 내용이 잘렸거나, 틀렸을 수도 있습니다.
만약 틀린게 있다면 댓글로 알려주세요.