React 생태계는 Hook의 등장으로 한 번의 큰 변화를 거치고 근래에는 Server Component와 Concurrent Mode을 통해 한번 더 큰 변화를 거쳐가고 있다. 그리고 이제 React 19의 stable release도 그리 멀지 않아 보인다. React Official Blog에 소개된 React 19 RC를 통해 어떤 새로운 개념과 기능들이 추가될 예정인지 살펴보자
React Compiler
React19부터 React Compiler를 사용할 수 있게된다. React Compiler는 application build시 우리가 작성한 application code 중 필요한 부분에 자동으로 memorization 작업을 해준다. 어떤 컴포넌트를 memo를 통해 memorize할 것인지, 어떤 값과 함수를 useMemo, useCallback을 통해 memorization할 것인지, 이때까지 React 개발자는 직접 memorization을 수행해왔지만 이제 React Compiler의 도입을 통해 이런 수고를 덜 수 있게 되었다.
Application build시 React Compiler가 정상적으로 memorization 작업을 수행하기 위해선 다음 세 가지 조건이 만족되어야 한다.
Javascript 문법에 위반되지 않는 유효한 Javascript 코드여야 한다
만약 nullable / optional인 값을 참조할 때는 값이 해당 값이 null이나 undefined이 아닌 것을 검증 후 참조한다 ( object.nullableProperty?.foo 처럼 optional chaning을 사용하거나 if (object.nullableProperty)와 같이 null 또는 undefined이 아닌지 우선 확인 후 값에 접근하는 과정 필요 )
Rules of React를 지키며 작성된 코드여야 한다
만약 작성된 코드 중 위의 사항을 만족하지 못하는 부분은 build시 compiler가 memorization 작업을 하지않고 넘어간다.
개발 환경에 따라 Compiler를 세팅하는 방법이 조금씩 다르므로 자세한 사항은 Documentation을 참고하자
Server Component, Server Action
NextJS의 App route를 사용해봤다면 익숙한 개념일 것이다. 그리고 React 19버전에서 Server Component와 Server Action 모두 stable 상태가 된다. client side에서 render되는 Client Component와는 달리 Server Component는 server side에서 render된다
그리고 Server Action을 통해 특정 코드를 client side가 아닌 server side에서 실행하여 그 결과를 client side로 전달할 수 있다. Server side에서만 실행되어야 하는 Server Action을 사용하기 위해선 함수가 시작하는 부분에 "use server"를 명시하거나 server actions을 따로 파일로 정리하여 사용한다면 파일 상단에 "use server"를 명시해주어야 한다
const getDataList = () => {
"use server"
...
}
...
// 혹은 server action용 파일을 따로 관리한다면
// 파일 상단에 "use server"를 추가
"use server"
const getServerDataList = () => {
...
}
...
React에서 제공하는 Server API를 통해 여러 테스트를 해볼 수 있겠지만 실제 application을 개발하는데 server component나 server action을 사용하고자 한다면 NextJS와 같은 framework를 통해 개발하는 것이 일반적이다. 그리고 NextJS에선 page route와 app route라는 두 가지 다른 route system을 제공하는데 server component와 server action은 app route기반에서 사용할 수 있다.
USE
새로 추가되는 use api에 Promise를 전달하여 Promise에서 resolve된 값을 바로 사용할 수 있다. 다음 예제를 살펴보자
import {use} from 'react';
const getOrderList = new Promise((resolve, reject) => {
fetch("http://localhost:4000/orders")
.then((result) => resolve(result.json()))
.catch((err) => reject(err));
});
const OrderList = () => {
const orderList = use(getOrderList );
...
};
export default OrderList;
위의 예제에서 볼 수 있듯이 Promise를 use의 인자로 넘기면 Promise가 resolve될 때 까지 위의 component는 suspend 상태가 되며 resolve될 때 까지 상단의 Suspense fallback UI로 대체된다. 그리고 Promise가 resolve되면 resolve의 인자로 넘긴 값이 orderList 변수에 담기고 reject처리가 되면 상단의 Error Boundary로 error가 넘어간다 ( 상단에 Error Boundary로 처리를 했다면 )
Async function in Transition
React 19에선 startTransition에 async function을 바로 사용할 수 있다.
const [dataList, setDataList] = useState();
const [isPening, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async()=>{
const data = await getDataList();
startTransition(()=>{
setDataList(data)
})
})
}
여기서 주의할 점은 startTransition 내부에서 await 다음에 오는 state update는 transition으로 취급되지 않는다는 한계점이 있다. 추후에는 해결될 수 있겠지만 현재로서 위의 예제와 같이 startTransition에 async await statement를 사용한다면 transition ( task 우선순위가 낮은 )으로 취급되어야 하는 부분은 또 다른 startTransition을 추가하여 그 안에서 state update를 진행해준다.
useActionState
위의 예제와 같이 transition을 발생시키는 함수를 'Action' 이라고 한다. ( 여기서 말하는 transition은 React가 urgent, non-urgent update를 구분하기 위해 사용되는 transition을 말한다 ) 그리고 React 19에서는 action의 상태와 관련된 state 관리를 보다 쉽게 해주는 useActionState hook이 추가된다.
간단한 데이터 업데이트 과정을 생각해보자. Input box를 통해 데이터를 입력하고 submit을 누르면 input에 담긴 데이터를 api를 통해 서버로 전달한다. 서버로 요청을 보내면 frontend 측에선 요청이 처리 중이라는 걸 알리기 위해 pending 상태의 UI를 보여주고 만약 요청이 실패하면 오류 상태의 UI를 보여줘야 한다
위의 작업을 useActionState hook을 통해 처리하면 다음과 같이 처리할 수 있다
import { useActionState } from "react";
...
const [state, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const result = await updateName({
name: formData.get("name"),
address: formData.get("address"),
});
if (result.error) {
return;
}
return result.data;
},
{ name: "", address: "" }
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<input type="text" name="address" />
<button type="submit" disabled={isPending}>
Update
</button>
</form>
);
...
위의 예제에서 볼 수 있듯이 useActionState은 action 함수를 첫 번째 인자로 받고 initial state를 두 번째 인자로 받는다. 그리고 현재 state, action함수 그리고 현재 pending상태 인지 아닌지를 나타내는 isPending을 return한다.
위의 예제에서 버튼을 눌러 form을 submit하면 useActionState의 action 함수가 실행된다. action 함수가 실행되면 transition이 시작되고 isPending 상태는 true가 되고 action 함수로 인해 시작된 transition이 종료되면pending 상태는 다시 false로 돌아온다. 그리고 action 함수에서 return하는 값이 새로운 state로 세팅된다.
React 19부터 form element의 action property 또는 button element의 formAction property를 통해 action 함수를 바로 전달하여 사용할 수 있다. action 또는 formAction에서 어떠한 함수도 전달할 수 있으며 전달된 함수는 action 함수로 취급된다 ( 즉, transition을 발생시킨다 ) 그리고 form 하위의 input이 uncontrolled component라면 action 함수의 실행이 끝나면 input 값은 초기화 된다.
useFormStatus
Child component에서 Parent component에 있는 form의 pending 상태 및 input data 정보들을 확인할 수 있다
다음 예제를 살펴보자
// OrderItem.tsx
const OrderItem() => {
const { action, data, method, pending } = useFormStatus();
console.log(data?.get("name"));
...
}
// App.tsx
const App = () => {
const [state, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const result = await updateName({
name: formData.get("name"),
address: formData.get("address"),
});
if (result.error) {
return;
}
return result.data;
},
{ name: "", address: "" }
);
return (
<form action={submitAction}>
...
<OrderItem />
</form>
);
}
useFormStatus을 통해 form의 정보를 알고 싶다면 위의 예제처럼 component가 form element 안쪽에 있어야 한다.
현재 form이 submit되어 pending 상태인지 그리고 pending 상태라면 form에 submit된 input의 값과 같은 정보를 확인할 수 있다.
Ref as a prop
상위 컴포넌트에서 하위 컴포넌트로 ref를 전달하고자 한다면 React 19부터 더 이상 forwardRef로 하위 컴포넌트를 감쌀 필요없이 바로 ref를 prop으로 전달할 수 있다.
const App = () => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<form>
<input type="text" name="name" ref={inputRef} />
<Suspense fallback={<div>Loading ...</div>}>
<OrderList ref={inputRef} />
</Suspense>
</form>
);
}
const OrderList = ({ ref }: Props) => {
...
}
Context as a Provider
React 19에서 Context 관련 setup에도 조금 변경사항이 있다. 기존에 Context를 사용하려면 createContext를 통해 Context를 생성한 다음 Context.Provider를 통해 하위 컴포넌트를 감싸줘야 했다
const SettingsContext = createContext(initialState);
const SettingProvider = () => {
return (
<SettingsContext.Provider setting={...}>
...
<SettingsContext.Provider />
)
}
React 19부터는 Provider없이 Context로 바로 감싸줘도 된다
const SettingsContext = createContext(initialState);
const SettingProvider = () => {
return (
<SettingsContext setting={...}>
...
<SettingsContext />
)
}
Cleanup function for ref property
React19부터 ref property에 cleanup 함수가 추가된다.
<input
type="text"
name="address"
ref={(ref) => {
return () => {
console.log(" ::: address input clean up ::: ");
};
}}
/>
위의 예제에서 해당 input component가 unmount될 때 cleanup 함수가 실행된다
Document Metadata in a component
React 19부터는 Component내에 바로 <title> 또는 <meta>와 같인 document metadata tags를 사용할 수 있다. React가 component를 render할 때 해당 metadata tags를 만나면 자동으로 <head> section으로 옮겨서 실행해준다
const OrderList = () => {
return (
<div>
<title>test 123</title>
<meta name="author" content="James" />
</div>
);
};
export default OrderList;
React 팀은 위의 기능이 React-Helmet과 같은 library를 대체하기 위함이 아닌 React-Helmet과 같은 library가 metadata와 관련된 작업의 처리를 보다 쉽게 처리할 수 있게 하기 위함이라고 말하고 있다. 간단한 상황이 아니라면 React-Helmet와 같은 library가 제공하는 추가 기능을 적극 활용하는 것이 좋다
Support for stylesheets
React 19에선 특정 component 내에서 <link> tag를 통해 외부 스타일을 불러올 때 해당 style 파일이 먼저 load되고 나서 component가 display되어야 하는 순서를 내부적으로 처리해준다.
또한 React 19에선 link tag의 precedence option을 설정하면 특정 style을 우선시 적용될 수 있도록 삽입 순서 또한 내부적으로 처리해준다.
const OrderList = () => {
return (
<div>
<p>{...}</p>
<link rel="stylesheet" href="styles/order.css" precedence="high" />
<link rel="stylesheet" href="styles/order-item.css" precedence="default" />
</div>
)
}
Browser resource hints
React19부터 preconnect, dns-prefetch, preload와 같은 resouce hint를 사용해 resource를 load할 수 있는 api를 제공한다
import { prefetchDNS, preconnect, preload } from 'react-dom'
const OrderListResource = () => {
preload('https://exaple.com/font.woff', { as: 'font' })
prefetchDNS('https://example.com')
preconnect('https://example.com')
}
위의 예제처럼 prefetchDNS, preconnect등의 api로 load하는 resource들은 React에 의해 자동으로 head tag로 옮겨진다.
<html>
<head>
<link rel="prefetch-dns" href="...">
<link rel="preconnect" href="...">
<link rel="preload" as="font" href="...">
</head>