2024년 12월 React 19의 stable 버전이 출시됐다. 리액트 공식 블로그를 참고해서 React 19의 주요 변경 사항을 정리해봤다. 새롭게 선보인 훅은 각종 문서와 예제를 참고해서 이해하기 쉽도록 부연 설명을 덧붙였다.
공식문서에 기존 사용자를 위한 마이그레이션 가이드도 자세하게 나와있으니 참고하자.
useTransition은 주로 무거운 작업의 상태 업데이트를 낮은 우선순위로 지정하여 UI 블로킹을 방지할 때 사용한다. React 18 버전까지 startTransition
콜백은 항상 동기적이어야 하는 제약이 있었다. 때문에 콜백 안에서 비동기 호출 같은 작업을 수행할 수 없었다.
React 19부턴 startTransition
콜백 안에서 비동기 처리도 가능해졌다. 덕분에 별도 상태를 정의하지 않고도 useTransition
훅이 반환하는 isPending
을 이용해 로딩 상태까지 처리할 수 있게됐다.
// 코드 출처: 리액트 공식 문서
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const error = await updateName(name);
// ...
});
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
{/* updateName 비동기 호출이 시작되면 isPending = true */}
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" />
useFormState 훅은 deprecated 됐다.
</aside>
useActionState는 비동기 상태 관리를 단순화하기 위해 설계된 훅으로, 여러 상태 관리 단계를 하나의 통합된 액션으로 묶어준다. 이를 통해 비동기 로직을 명확하게 분리하고, 상태 관리의 복잡성을 줄여서 코드를 더욱 간결하게 작성할 수 있다.
useActionState(action, initialState, permalink?)
// 폼 제출 혹은 버튼을 눌렀을 때 호출할 함수(액션)
async function increment(previousState, formData) {
return previousState + 1;
}
function StatefulForm({}) {
// state: 첫 렌더링에선 initialState, 액션 실행 후부턴 액션(increment 함수) 반환값
// formAction: form.action 혹은 button.formAction에 전달할 수 있는 래핑된 액션
const [state, formAction] = useActionState(increment, 0);
return (
<form>
{state}
<button formAction={formAction}>Increment</button>
</form>
)
}
아래 예시에서 폼을 제출하면 useActionState
훅 첫 번째 인자로 전달한 함수(액션)가 실행되고, 해당 함수의 2번째 인자를 통해 FormData
객체가 전달된다. 이때 액션이 처리중임을 알리는 isPending
상태는 true
로 변경된다.
function ChangeName({ name, setName }) {
// 초기 상태를 null로 설정했으므로 error의 초기값 역시 null
const [error, submitAction, isPending] = useActionState(
// 비동기 로직을 처리하는 액션 (useActionState 훅 첫번째 파라미터)
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) return error; // 요청 실패 시 에러 메시지 반환
// 성공 처리
redirect("/path");
return null;
},
null // 초기 상태 (useActionState 훅 두번째 파라미터)
);
return (
// useActionState 훅이 반환하는 래핑된 액션을 action 속성에 할당
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</form>
);
}
<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" />
액션(함수)은 actions/
같은 폴더를 만들어서 별도로 관리하는 것이 좋다.
</aside>
한편 리액트 <form>
컴포넌트의 action
속성은 URL 혹은 함수(액션)를 모두 지원한다. URL을 전달하면 브라우저 기본 폼 제출 동작에 따라 해당 URL로 요청이 전송된다.
함수를 전달했을 땐 브라우저 기본 폼 제출 동작을 생략하고 해당 함수를 호출한다. 이때 함수는 FormData
객체를 첫 번째 파라미터로 받고, HTTP 메서드는 method 프로퍼티와 관계없이 항상 POST 메서드로 처리된다. 참고로 <form>
태그의 method
속성 기본값은 get
이다(MDN).