React 19 뒤늦게 톺아보기

육기준·2024년 6월 12일
0
post-thumbnail

React 19 베타 버전이 4월 25일에 출시됬습니다.
사실 최근까지 모르고 있다가, 어떤 프론트 친구가 리액트 19 베타를 보고 발광하는 걸 보고 알았습니다.
원래는 그냥 넘기려고 그랬는데, 어차피 써야 할 거 한번 미리 정리해봤습니다.

액션

Pending 상태를 감지하여 자동으로 값을 변화시켜주는 useTransition, 기본적인 Action 처리에 사용되는 useActionState가 추가되었습니다.

useTransition

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로 변경됩니다.
};

useActionState

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가 추가되었습니다.

useFormStatus

const {pending} = useFormStatus();

return <button type="submit" disabled={pending} /> 
// 버튼을 누르면 바로 비활성화됩니다.

useOptimistic

폼의 상태에 따라 낙관적으로 값을 설정하는 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>

use

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에서 지원된 경우가 많습니다.

1. Ref의 속성화

forWardRef 속성 대신 ref속성으로 직접 ref를 전달해줄 수 있게 됩니다.

function MyInput({placeholder, ref}) {
  return <input placeholder={placeholder} ref={ref} />
}
  
<MyInput ref={ref} />

2. 개선된 에러 로그

에러 로그가 두 번씩 출력되던 문제가 해결되었습니다.

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.

2-1. 개선된 하이드레이션 에러 로그

하이드레이션 에러 로그가 아래와 같이 변경됩니다.

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
  …

3. <Context>의 Provider화

이제 <Context.Provider><Context>로 바로 사용할 수 있습니다.

const ThemeContext = createContext('');

<ThemeContext value="dark">
  {children}
</ThemeContext>

4. Ref 클린업 함수 추가

컴포넌트가 unMount되면 ref가 제거되게 할 수 있는 클린업 함수가 추가됩니다.
만약 타입스크립트와 함꼐 사용할 경우, 코드를 <div ref={current => {instance = current}} />의 형태로 작성하는 것을 추천드립니다.

<input
  ref={(ref) => {
	// ...
    return () => {
      // 컴포넌트 unMount시 작동
    };
  }}
/>

5. useDeferredValue 기본값 옵션

useDeferredValue에 기본값 옵션이 추가되었습니다.
아래와 같이 사용하며, 첫 렌더시 해당 값을 반환합니다.
렌더 후에는, deferredValue가 반환된 상태로 백그라운드에서 렌더링을 다시 예약합니다.

const value = useDeferredValue(deferredValue, '');

6. 문서 메타데이터 지원

컴포넌트에서 메타데이터 태그를 작성하면, 컴포넌트가 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>
);

7. 스타일시트 지원

우선 순위를 제대로 명시하고, 위치를 잘 잡아야 한다는 단점과 함께 스타일시트가 지원됩니다.

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>
  )
}

8. 비동기 스크립트 지원

코드의 위치 등을 관리할 필요 없이 어디서든 비동기 스크립트를 렌더링하는, 더 나은 비동기 스크립트 지원이 추가됩니다.

function MyComponent() {
  return (
    <div>
      <script async={true} src="..." />
      Hello World
    </div>
  )
}

function App() {
  <html>
    <body>
      <MyComponent>
      ...
      <MyComponent> // 따로 비동기 코드가 겹치지 않게 됩니다.
    </body>
  </html>
}

9. 프리로드 자원 지원

효율적으로 자원을 불러오기 위해, 프리로드 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
}

10. 커스텀 요소 지원

알 수 없는 prop을 property가 아닌 attribute으로 취급했던 이전 버전들과는 다르게, 아래 전략과 함꼐 property가 완벽하게 지원됩니다.

  • SSR: 컴포넌트에 넘어온 prop들 중 값이 true거나, 타입이 string, number 등의 기본 타입인 prop은 property으로 인식합니다.

  • CSR: 커스텀 요소 인스턴스와 부합하는 prop은 property로, 아닌 prop은 attribute로 인식합니다.

11. 서드파티 호환성 증가

<head><body>에 들어있는 예상치 못한 태그들은 이제 불일치 오류를 피하기 위해 무시합니다.

만약 hydration 불일치 오류로 인해 리렌더링이 필요한 상황에서는, 서드파티와 확장 프로그램들의 스타일시트를 그대로 두고 리렌더링됩니다.

💭 결론

이번 React 19에서도 많은게 바뀌었습니다.
특히 성능 개선과 함께 비동기와 관련된 내용들이 많이 추가되고 수정됬고, 원래 HTML에서 지원하던 부분들을 더 지원하게 된 걸로 보입니다.

저는 메타데이터가 head로 호이스팅되게 됬다는 점이 매우 마음에 드는 것 같습니다.
(지금까지 페이스북 메신저나 디스코드에서 알림이 왔을 때 웹 버전이면 타이틀이 바뀌는 방식으로 알려줘왔는데, 이 부분이 구현하기 편해져서 좋네요)

🚀 번외

번역기 없이 직접 번역하고 내용을 잘라낸 거라서 중요한 내용이 잘렸거나, 틀렸을 수도 있습니다.
만약 틀린게 있다면 댓글로 알려주세요.

profile
Front-End 전문가가 되기 위해 꾸준히 공부하고 있습니다

0개의 댓글