리액트 19버전 출시를 앞두고 있는 지금, 어떤 기능들이 추가되는지 알아보기 위해 개인적으로 번역하며 읽어보았습니다.
RC?
Release Candidate의 약자로, 소프트웨어 개발 주기에서 최종 출시 전에 공개되는 버전을 말한다. 안정성과 기능이 최종 버전에 가깝지만 사용자로부터 피드백을 받고 버그를 수정할 가능성이 있는 상태를 의미한다.
React App들의 일반적인 사용 케이스는 데이터를 변형하고 그 응답에 따라 상태를 업데이트하는 것입니다. 예를 들어, 사용자가 이름을 변경하여 폼을 제출할 때, API 요청을 생성한 다음, 응답을 처리합니다. 과거에는 Pending 상태, 에러, 낙관적 업데이트, 그리고 순차적인 요청을 수동적으로 처리해야 했습니다.
예를 useState를 통해 Pending과 Error 상태를 다룰 수 있었습니다.
// Before Actions
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={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
React 19에서는 Pending 상태, Error, 폼, 낙관적 업데이트를 자동으로 처리하기 위해 transition에서 비동기 함수를 통해 지원하는 방식을 추가하였습니다.
예를 들어, pending state를 다루기 위해 useTransition 훅을 아래와 같이 사용할 수 있습니다.
// Using pending state from Actions
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>
);
}
비동기 트랜지선은 isPending
상태를 즉시 true로 바꾸고, 트랜지션이 끝난 후에 비동기 요청를 만들고, isPending을 False로 바꿉니다. 이것을 통해서 데이터가 바뀌는 동안 현재 UI를 반응성있고, 상호작용적으로 유지할 수 있습니다.
Actions를 기반으로, React 19에서는 낙관적 업데이트를 관리하는 useOptimistic
과 Actions의 일반적인 케이스를 다루는 React.useActionState
라는 새로운 훅을 소개합니다.
react-dom
에서는 form을 자동으로 관리하는 <form>
Actions와 form에서의 Actions의 일반적인 케이스를 지원하는 useFormStatus를 추가했습니다.
React 19에서는 위 예시를 다음과 같이 단순화 할 수 있습니다.
// 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>
);
}
다음 섹션에서는 React 19의 새로운 Action의 기능들을 하나씩 알아봅니다.
useActionState
Actions의 일반적인 케이스를 쉽게 하기 위해 useActionState
라고 불리는 새로운 훅을 추가하였습니다.
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// You can return any result of the action.
// Here, we return only the error.
return error;
}
// handle success
return null;
},
null,
);
useActionState는 함수를 받아서, 호출할 wrapping된 Action을 반환합니다. Actions가 구성되기 때문에 이것은 동작합니다. 래핑된 Action이 호출될 때, useActionState는 Action의 마지막 결과를 data
로 반환하고 Actions의 pending 상태를 pending
으로 반환합니다.
더 많은 정보를 알고 싶으면 useActionState의 문서를 확인하세요.
<form>
ActionsActions는 또한 React 19에서 <form>
의 새로운 기능과도 통합되었습니다. Actions을 통해 자동으로 폼을 제출하기 위해 <form>
, <input>
, <button>
엘리먼트의 formAction props로 함수를 전달하는 기능을 추가했습니다.
<form action={actionFunction} />
<form>
액션이 성공되었을 때, 리액트는 제어되지 않은 컴포넌트에 대해서 폼을 초기화 하게 됩니다. 만약 <form>
의 초기화를 수동으로 해야한다면 새로운 React DOM API의 requestFormReset을호출할 수 있습니다.
더 자세한 내용은 react-dom의 <form>
, <input>
, <button>
문서를 참조하세요.
useFormStatus
디자인 시스템에서, 컴포넌트까지 props를 drilling하지 않고, 컴포넌트에 대한 정보에 접근하는 디자인 컴포넌트를 만드는 것이 일반적입니다. 이것을 Context를 통해서 가능하지만 더 쉽게 만들기 위해서 useFormStatus라는 새로운 훅을 추가하였습니다.
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useFormStatus
는
자세한 내용은 react-dom
의 useFormStatus
문서를 참조하세요 .
useOptimistic
데이터 변형(Mutation)을 수행할때 또 다른 일반적인 패턴은 비동기 요청이 수행되는 동안에 낙관적으로 최종 상태를 보여주는 것입니다. React 19에서는 useOptimistic이라고 불리는 새로운 훅을 호출하여 더욱 쉽게 했습니다.
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"
disabled={currentName !== optimisticName}
/>
</p>
</form>
);
}
updateName 요청이 진행되는 동안에, useOptimistic
훅은 즉시 optimisticName을 랜더할 것입니다. 업데이트가 완료 되거나, 에러가 발생하면 리액트는 자동으로 currentName
값으로 바꾸게 됩니다.
자세한 내용은 useOptimistic
문서를 참조하세요.
use
React 19에서 render
에서 리소스를 읽는 새로운 API인 use를 소개합니다.
예를 들어 use를 통해 promise를 읽을 수 있고, 그 동안 React는 promise가 완료될 때 까지 중단합니다.
import {use} from 'react';
function Comments({commentsPromise}) {
// `use` will suspend until the promise resolves.
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({commentsPromise}) {
// When `use` suspends in Comments,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
use
를 활용하여 Context를 읽을 수 있으며, early return 후와 같이 context를 조건부로 읽을 수 있습니다.
import {use} from 'react';
import ThemeContext from './ThemeContext'
function Heading({children}) {
if (children == null) {
return null;
}
// This would not work with useContext
// because of the early return.
const theme = use(ThemeContext);
return (
<h1 style={{color: theme.color}}>
{children}
</h1>
);
}
use
API는 훅과 비슷하게 render
에서 부를 수 있습니다. 훅과는 다르게 use
API는 조건부로 호출이 가능합니다. 미래에는 use
를 활용하여 리소스를 활용하는 더 많은 방식을 지원할 계획에 있습니다.
자세한 내용은 use
문서를 참조하세요.
Server Component
서버 컴포넌트는 번들링하기 이전에 클라이언트 애플리케이션이나 SSR 서버와 별도의 환경에서 구성 요소를 미리 랜더링할 수 있는 방식이다. 이 분리된 환경은 “React Server Component”의 ‘서버’입니다. 서버 컴포넌트는 CI 서버에서 빌드 시간에 한 번 실행되거나 웹 서버를 사용해서 각 요청에 대해 실행될 수 있습니다.
React 19에는 Canary 채널에서 포함된 모든 React Server Component의 기능이 포함되어 있습니다. 즉, Server Component와 함께 제공되는 라이브러리는 이제 Full-Stack React Architecture를 지원하기위한 프레임워크를 사용할 때 React 19를 react-server export 조건과 함께 피어 종속성으로 타겟으로 할 수 있습니다.
자세한 내용은 React Server Components
문서를 참조하세요.
Server Actions
Server Actions는 클라이언트 컴ㅍ노너트가 서버에서 실행되는 비동기 함수를 호출할 수 있게 해줍니다.
Server Actions가 use server
로 직접 정의되어 있을 때, 당신의 프레임워키에서는 자동으로 서버 함수의 참조를 생성하고 클라이언트 컴포넌트에게 참조를 전달합니다. 함수가 클라이언트에서 호출되었을 때, React는 함수를 실행하고 결과를 반환하기 위해서 서버에 요청을 보냅니다.
서버 액션은 서버 컴포넌트에서 생성되어 클라이언트 컴포넌트에 Props로 전달될 수 도 있고, 클라이언트 컴포넌트에서 import해서 사용할 수 도 있습니다.
자세한 내용은 React Server Actions
문서를 참조하세요.
ref
React 19를 시작하면서 함수형 컴포넌트에서 prop으로 ref에 접근할 수 있습니다.
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
새로운 함수형 컴포넌트는 더이상 forwardRef가 필요하지 않습니다. 그리고 새로운 ref prop을 사용하여 자동으로 당신의 컴포넌트를 업데이트하는 codemod도 배포할 예정힙니다. 미래의 버전에서는 forwardRef를 deprecate하고 삭제할 예정입니다.
우리는 또한 react-dom에서 hrdration error를 보고하는 에러 보고를 개선하였습니다. 예를 들어 DEV에서 불일치에 대한 어떠한 정보도 없이 기록하는 대신
이제 불일치의 diff에 대한 메세지를 포함하여 기록합니다.
React 19에서 Context.Provider 대신 를 Provider로 활용할 수 있습니다.
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
새로운 Context Provider는 <Context>
를 활용할 수 있으며, 기존 Provider를 변환하기 위한 codemod를 게시할 예정입니다. 향후 버전에서는 <Context.Provider>
을 사용하지 않을 예정입니다.
이제 ref의 콜백에서 정리 함수를 반환하는 기능을 지원합니다.
<input
ref={(ref) => {
// ref created
// NEW: return a cleanup function to reset
// the ref when element is removed from DOM.
return () => {
// ref cleanup
};
}}
/>
컴포넌트가 언마운트 되었을 때, 리액트는 ref 콜백함수로부터 반환되는 cleanup 함수를 호출합니다. 이것은 DOM refs, 클래스 컴포넌트의 refs, useImperativeHandle에 유효합니다.
ref 정리함수가 도입되었기 때문에 ref 콜백함수에서 다른 것을 반환하는 것은 이제 TS에서 거부됩니다. 아래와 같이 return을 사용하지 않음으로서 문제를 해결할 수 있습니다.
- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />
원래 코드는 HTMLDivElement의 인스턴스를 반환했고, TypeScript는 이것이 cleanup 함수인지 cleanup 함수를 반환하고 싶지 않아하는지 알 수 없었습니다.
당신은 no-implicit-ref-callback-return
을 통해서 해당 패턴을 codemod 할 수 있습니다.
useDeferredValue
inital Value
우리는 useDeferredValue
에 initial Option을 추가하였습니다.
function Search({deferredValue}) {
// On initial render the value is ''.
// Then a re-render is scheduled with the deferredValue.
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
initialValue가 제공되면, useDeferedValue는 이것을 컴포넌트의 초기 랜더 값으로서 반환합니다. 그리고 반환되는 defferedValue와 함께 백그라운드에서 리랜더를 예약합니다.
더 많은 내용은 useDefferedValue
를 참조하세요.
HTML에서 , <link>, <meta>와 같은 document metadata tag들은 document의 <head> 부분에 배치되기 위해 예약되어 있습니다. React에서 앱에 적합한 메타데이터를 결정하는 컴포넌트는 <head>와 멀리 떨어져 있거나, React에서 <head>를 랜더링하지 않을 수 있습니다. 과거에는 이런 엘리먼트를 수동으로 삽입해야하거나 react-helmet 과 같은 외부 라이브러릴 활용해야했습니다. 그리고 리액트 앱을 서버에서 랜더링할 때 신중하게 처리해야 했습니다.
React 19에서는 컴포넌트에서 기본적으로 메타데이터 태그를 랜더링하는 기능을 추가합니다.
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>
);
}
리액트가 이 컴포넌트를 랜더링 할 때. , <link>, <meta> 데이터 태그를 보고 자동으로 document의<head> 영역으로 끌어올립니다. 메타데이터 태크를 기본적으로 지원함으로써, 클라이언트 전용 앱, 스트리밍 SSR, 서버 컴포넌트와 함께 작동할 수 있도록 합니다.
외부 링크( <link rel="stylesheet" href="...">
)와 인라인( <style>...</style>
) 스타일시트는 스타일 우선순위 규칙으로 인해 DOM에서 신중하게 배치해야 합니다. 컴포넌트 내에서 구성 가능성을 허용하는 스타일시트 기능을 구축하는 것은 어렵기 때문에 사용자들은 컴포넌트 자신에게 종속될 수 있는 구성 요소에서 멀리 떨어진 모든 스타일을 로드하거나 복잡성을 캡슐화하는 스타일 라이브러릴 사용합니다.
React 19에서 우리는 이런 복자섭ㅇ을 해결하고 클라이언트에서 도이성 랜더링과 서버에서 스트리밍 랜더링에 대해 깊은 통합을 제공하고, 스타일 시트에 대한 기본 지원을 제공합니다. 만약 React에 스타일시트의 precedence
를 알려주면, DOM에서 스타일시트의 삽입 순서를 관리하고, 스타일시트가 로드된 이후 스타일 규칙에 따라 달라지는 컨텐츠를 표시합니다.
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" /> <-- will be inserted between foo & bar
</div>
)
}
서버 사이드 랜더링 동안 React는 에 스타일 시트를 포함시키고, 로드될 때까지 그리지 않도록합니다. 만약 스타일시트가 스트리밍이 시작되고 늦게 발견되면 Reactsms 에 해당 스타일시트에 의존하는 Suspense 바운더리의 내용을 공개하기 전에 클라이언트에서 스타일시트가 삽입되도록 합니다.
클라이언트 사이드 랜더링 동안 React는 랜더링을 커밋하기 전에 새롭게 랜더링된 스타일시트가 로드될 때까지 기다립니다. 애플리케이션 내 여러 위치에서 이 구성 요소를 랜더링하는 경우 React는 문서에 스타일시트를 한 번만 포함합니다.
function App() {
return <>
<ComponentOne />
...
<ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM
</>
}
스타일시트를 수동적으로 로드하는데 익숙한 사용자에게 이것은 해당 스타일시트와 그 종속성을 찾을 수 있는 기회이고, 더 나은 지역적 추론과 실제 종속된 스타일시트만 로드하도록 보장하는 데 도움이 됩니다.
스타일 라이브라리와 번들러를 통한 스타일 통합도 이런 새로운 사용성을 채택할 수 있기 때문에, 스타일 시트를 직접 랜더링하지 않더라도 도구가 이 기능을 사용하도록 업르게이 됨에 따라 해당 장점을 활용할 수 있습니다.
더 많은 정보가 필요하면 <link>
와 <style>
문서를 확인하세요.
HTML에서 일반 스크립트( <script src="...">
)와 지연 스크립트( <script defer="" src="...">
)는 문서 순서대로 로드되므로 구성 요소 트리에서 이러한 종류의 스크립트를 깊숙이 렌더링하는 것이 어렵습니다.
하지만 비동기 스크립트(<script async="" src="...">
)는 임의의 순서로 로드됩니다.
React 19에서는 스크립트에 실제적으로 의존하는 구성요소 내에서 구성 요소 트리의 어느 곳에서나 스크립트 인스턴스를 다시 배치하거나 중복을 제거할 필요없이 비동기 스크립트를 랜더링할 수 있도록 하여 비동기 스크립트를 위한 지원을 포함하고 있습니다.
function MyComponent() {
return (
<div>
<script async={true} src="..." />
Hello World
</div>
)
}
function App() {
<html>
<body>
<MyComponent>
...
<MyComponent> // won't lead to duplicate script in the DOM
</body>
</html>
}
모든 랜더링 환경 속에서 비동기 스크립트는 중복이 제거되므로 React는 만약 여러 개의 서로 다른 구성 요소에 의해 랜더링되더라도 스크립트를 한번만 수행합니다.
서버 사이드 랜더링에서는 비동기 스크립트가 태그에 포함되고, 스타일시트, 글꼴, 이미지 프리로드와 같은 페인트 차단보다 낮은 우선순위를 가집니다.
더 많은 정보가 필요하면 <script>
문서를 확인하세요.
초기 document를 로드하고 클라이언트 사이트에서 업데이트가 일어나는 동안, 브라우저에 가능한 한 빨리 필요한 리소스에 대해서 말해주면 페이지 성능에 극적인 영향을 줄 수 잇다.
React 19에서는 브라우저 리소스를 로드하고, 프리로딩을 위한 여러가지 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
}
<!-- the above would result in the following DOM/HTML -->
<html>
<head>
<!-- links/scripts are prioritized by their utility to early loading, not call order -->
<link rel="prefetch-dns" href="https://...">
<link rel="preconnect" href="https://...">
<link rel="preload" as="font" href="https://.../path/to/font.woff">
<link rel="preload" as="style" href="https://.../path/to/stylesheet.css">
<script async="" src="https://.../path/to/some/script.js"></script>
</head>
<body>
...
</body>
</html>
이러한 API는 글꼴과 같은 추가 리소스의 발견을 스타일시트 로딩을 제외해서 초기 페이지 로드를 최적화하는 데활용할 수 있습니다. 또한 예상 탐색에서 사용되는 리소스 목록을 미리 페치한 다음 클릭 또는 호버 시 해당 리소스를 적극적으로 미리 로드하여 클라이언트 업데이트를 더 빠르게 만들 수도 있습니다.
자세한 내용은 Resource Preloading API를 확인하세요.
우리는 third-party 스크립트와 브라우저 익스텐션을 고려해서 hydration을 개선하였습니다.
Hydrating 될 때, 클라이언트에서 랜더링되는 엘리먼트가 서버에서 온 HTML 내에서 찾은 요소와 일치하지 않을 때, ㄷ클라이언트가 다시 수정하여 리랜더링 되도록 강제합니다. 이전에는 만약 엘리먼트가 third-party 스크립트나 브라우저 익스텐션에 의해 요소가 삽입되면 불일치 오류가 발생하고 클라이언트가 랜더링 되었습니다.
React 19에서는 와 내에 있는 불분명한 태그를 건너뛰어 불일치 오류를 방지합니다. 만약 리액트와 관계없는 hydration 불일치로 인해서 전체 document를 다시 랜더링해야하는 경우 third-party 스크립트나 브라우저 익스텐션으로 인해 삽입된 스타일 시트는 내버려둡니다.
우리는 React 19에서 중복을제거하고, 잡힌 오류와 잡히지 않은 에러를 처리하기 위한 옵션을 제공하기 위해 오류 처리를 개선하였습니다. 예를 들어, Error Boundary에 의해 잡은 랜더링 오류가 있는 경우에 이전 React는 오류를 두 번 발생시킨 다음(원래 오류에 대해 한 번, 자동 복구에 실패한 후 다시 한 번) 에러가 발생한 위치에 대한 정보에 대해서 console.error를 호출합니다.
이로 인해 1개의 오류에 대해서 3개의 오류가 검출되었습니다.
React 19에서는 모든 정보를 포함한 1개의 에러만 기록합니다.
추가적으로, onRecoverableError를 보완하기 위한 새로운 루트 옵션들을 추가했습니다.
더 많은 정보와 예시를 보고 싶으면, creatRoot
와 hydrateRoot
를 참조하세요.
React 19는 Custom Elements(사용자 정의 요소)에 대한 완전한 지원을 추가했으며, 이제 모든 Custom Elements Everywhere 테스트를 통과합니다.
이전 버전에서는 React에서 Custom Elements를 사용하는 것이 어려웠습니다. 왜냐하면 React가 인식하지 못하는 props(속성)를 속성(attributes)으로 처리했기 때문입니다. React 19에서는 클라이언트(Client)와 서버 사이드 렌더링(SSR) 모두에서 동작하는 방식으로 properties(프로퍼티)를 지원하도록 개선했습니다. 다음과 같은 전략이 적용됩니다:
props
는 아래와 같은 조건에 따라 속성(attributes)으로 렌더링됩니다:true
와 같은 원시 값(primitive value)인 경우.false
인 경우, 해당 props는 생략됩니다.Custom Element 인스턴스의 프로퍼티(property)와 일치하는 props는 프로퍼티로 설정됩니다. 그 외의 props는 속성(attribute)으로 설정됩니다.
잘 보고 갑니다.