[번역] 리액트 19 베타

Saetbyeol·2024년 5월 8일
73

translations.zip

목록 보기
14/16
post-thumbnail

공식 문서 React 19 Beta를 한국어로 번역한 글입니다.

이제 npm에서 리액트 19 베타를 사용할 수 있습니다! 이 글에서는 리액트 19의 새로운 기능과 사용하는 방법을 간단히 설명합니다.

📝 주의: 이번 베타 릴리즈는 라이브러리들이 리액트 19를 대비하도록 제공됩니다. 애플리케이션 개발자들은 18.3.0으로 버전을 업그레이드하고, 라이브러리와 작업하며 피드백에 따라 수정되는 동안 리액트 19가 안정될 때까지 기다려주세요.

리액트 19 베타를 npm에서 만나 볼 수 있습니다!

리액트 19 베타 업그레이드 가이드에서 리액트 19 베타를 여러분의 애플리케이션에 적용하기 위한 단계별 지침을 제공했습니다. 이 글에서는 리액트 19의 새로운 기능과 사용 방법에 대해 간단히 설명하고자 합니다.

중요한 변경 사항에 대한 목록은 업그레이드 가이드에서 확인하실 수 있습니다.

리액트 19에서 새롭게 등장하는 것은 무엇인가요?

액션(Actions)

리액트 애플리케이션에서는 데이터를 변경하고 이에 대한 응답을 기반으로 상태를 업데이트하는 것이 일반적입니다. 예를 들어, 사용자가 이름을 변경하기 위해 양식(form)을 제출하면, API 요청을 보낸 다음 응답을 처리합니다. 이전에는 대기 상태(pending state), 에러, 낙관적 업데이트, 순차적 요청을 직접 처리해야만 했습니다.

예를 들어 useState로 대기와 에러 상태를 다룰 수 있었습니다.

// 액션(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>
  );
}

리액트 19에서는 트랜지션에서 비동기 함수를 사용하여 대기 상태, 에러, 양식, 낙관적 업데이트를 자동으로 처리하는 기능이 추가됩니다.

다음과 같이 useTransition을 사용하여 대기 상태를 다룰 수 있습니다.

// 액션에서 대기 상태 사용하기
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)"라고 부릅니다.

액션은 데이터 제출을 자동으로 관리합니다.

  • 대기 상태: 액션은 요청이 시작될 때 함께 시작하며, 최종 상태 업데이트가 커밋되면 자동으로 재설정되는 대기 상태를 제공합니다.
  • 낙관적 업데이트: 액션은 새로운 훅인 useOptimistic을 지원합니다. 사용자들이 요청을 제출했을 때 즉각적인 응답을 제공할 수 있습니다.
  • 에러 처리: 액션은 에러 처리 기능을 제공하므로 요청이 실패했을 때 에러 바운더리를 표시할 수 있습니다. 또한 낙관적 업데이트로 수정된 값을 자동으로 원래의 값으로 복구할 수 있습니다.
  • 양식(form): 이제 <form> 요소에 actionformAction 프로퍼티에 함수를 전달할 수 있습니다. action 프로퍼티에 함수를 전달하면 기본적으로 액션을 사용하고 제출 후에 자동으로 양식을 재설정합니다.

액션을 기반으로 구축된 리액트 19는 낙관적 업데이트의 처리를 위한 useOptimistic, 액션의 일반적인 사용을 다루기 위한 새로운 훅 React.useActionstate을 도입합니다. react-dom에서는 양식을 자동으로 관리하는 form 액션, 양식에서 액션을 지원하는 useFormStatus 추가하고 있습니다.

리액트 19에서는 위의 예시를 아래와 같이 단순하게 작성할 수 있습니다.

// <form> 액션과 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>
  );
}

다음 섹션에서는 리액트 19의 새로운 액션 기능들을 하나씩 살펴보겠습니다.

새로운 훅: useActionState

액션의 일반적인 경우를 더 쉽게 처리하기 위해 useActionState 훅을 추가했습니다.

const [error, submitAction, isPending] = useActionState(
  async (previousState, newName) => {
    const error = await updateName(newName);
    if (error) {
      // 액션의 어떤 결과든 반환할 수 있습니다.
      // 이 예제에서는 에러만 반환합니다.
      return error;
    }

    // 요청이 성공한 경우
    return null;
  },
  null,
);

useActionState는 함수("액션")를 받아 호출할 래핑된 액션을 반환합니다. 이는 각 액션이 조합되므로 가능합니다. 래핑된 액션이 호출되면, useActionState는 액션의 마지막 결과를 data로, 액션의 대기 상태를 pending으로 반환합니다.


📝 참고

이전 카나리 릴리즈에서는 React.useActionStateReactDOM.useFormState라고 불렀습니다. 그러나 이번 버전에서는 useFormState를 폐기하고 이름을 수정했습니다.

더 자세한 내용은 #28491 PR을 참고하시길 바랍니다.


useActionState를 더 자세히 알고 싶으시다면, 해당 문서를 참고하세요.

리액트 DOM: <form> 액션

액션은 또한 react-dom을 위한 리액트 19의 새로운 <form> 기능과도 통합됩니다. 액션으로 양식을 자동으로 제출할 수 있도록 <form>, <input>, <button> 요소에 actionformAction 프로퍼티로 함수를 전달하는 기능을 추가했습니다.

<form action={actionFunction}>

<form> 액션이 성공적으로 수행되면 리액트는 자동으로 양식을 제어되지 않은(uncontrolled) 컴포넌트로 재설정합니다. <form>을 직접 재설정해야 한다면 새로운 리액트 DOM API인 requestFormRest을 호출할 수 있습니다.

더 자세히 알고 싶으시다면, <form>, <input>, <button>에 대한 react-dom 문서를 참고하세요.

리액트 DOM 새로운 훅: useFormStatus

디자인 시스템에서 컴포넌트가 속한 <form>에 대한 정보를 접근해야 할 때가 많습니다. 컴포넌트에 프로퍼티를 드릴링하지 않고 접근하려면 컨텍스트를 사용할 수도 있지만, 이를 좀 더 쉽게 다루도록 useFormStatus 훅을 새롭게 추가했습니다.

import { useFormStatus } from 'react-dom';

function DesignButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending} />
}

useFormStatus 훅은 컨텍스트 프로바이더처럼 부모의 <form>의 상태를 읽습니다.

더 자세한 정보는 useFormStatus에 대한 react-dom 문서를 참고하세요.

새로운 훅: useOptimistic

데이터 변경을 수행할 때 비동기 요청이 진행되는 동안 최종 상태를 낙관적으로 표시하는 것도 흔한 UI 패턴입니다. 리액트 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>
  );
}

useOptimistic 훅은 updateName 요청이 진행되면 optimisticName 값을 바로 보여줍니다. 업데이트가 종료되거나 실패하면 리액트는 자동으로 currentName 값으로 다시 전환합니다.

더 자세한 내용은 useOptimistic 문서를 참고하세요.

새로운 훅: use

리액트 19에서는 렌더링에서 리소스를 읽는 새로운 API인 use를 도입합니다.

예를 들어 use로 프로미스를 읽는 경우, 프로미스가 해결(resolve)될 때까지 리액트는 일시 중단됩니다.

import {use} from 'react';

function Comments({commentsPromise}) {
  // `use`는 프로미스가 해결될 때까지 일시 중단됩니다.
  const comments = use(commentsPromise);
  return comments.map(comment => <p key={comment.id}>{comment}</p>);
}

function Page({commentsPromise}) {
  // Comments에서 `use`가 일시 중단되면,
  // 이 컴포넌트의 서스펜스 바운더리가 보여집니다.
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}

📝 참고

use는 렌더링 중 생성된 프로미스를 지원하지 않습니다.

렌더링 중 생성된 프로미스를 use에 전달하면 리액트는 아래와 같은 경고를 표시합니다.

캐시되지 않은 프로미스에 의해 컴포넌트가 중단되었습니다. 서스펜스와 호환되는 라이브러리 또는 프레임워크를 제외하고는 클라이언트 컴포넌트나 훅에서 생성된 프로미스는 아직 지원되지 않습니다.

이를 해결하려면 프로미스 캐싱을 지원하는 서스펜스 기반의 라이브러리 또는 프레임워크에서 프로미스를 전달해야 합니다. 향후 렌더링에서 프로미스를 더 쉽게 캐싱하는 기능을 제공할 계획입니다.


use로 컨텍스트를 읽을 수도 있으며, 얼리 리턴과 같이 조건부로 컨텍스트를 읽는 것도 가능합니다.

import {use} from 'react';
import ThemeContext from './ThemeContext'

function Heading({children}) {
  if (children == null) {
    return null;
  }

  // 얼리 리턴된 값이 있으므로
  // 여기서는 useContext가 동작하지 않습니다.
  const theme = use(ThemeContext);
  return (
    <h1 style={{color: theme.color}}>
      {children}
    </h1>
  );
}

use API는 훅과 비슷하게 렌더에서만 호출될 수 있습니다. 훅과 다른 점은 조건부 호출이 가능하다는 것입니다. 향후에는 렌더링에서 리소스를 사용하는 use의 더 많은 방법을 지원할 계획입니다.

더 자세한 내용은 use 문서를 참고하세요.

리액트 서버 컴포넌트

서버 컴포넌트

서버 컴포넌트는 번들링 전에 클라이언트 애플리케이션 또는 SSR 서버와 분리된 환경에서 컴포넌트를 미리 렌더링할 수 있는 새로운 방법입니다. 여기서 분리된 환경은 리액트 서버 컴포넌트의 "서버"가 됩니다. 서버 컴포넌트는 CI 서버에서 빌드 시 한번 실행되거나 웹 서버를 사용하여 각 요청에 대해 실행될 수 있습니다.

카나리에서 제공된 리액트 서버 컴포넌트의 모든 기능이 리액트 19에도 포함됩니다. 즉, 서버 컴포넌트와 함께 제공되는 라이브러리는 이제 풀스택 리액트 아키텍처를 지원하는 프레임워크에서 react-server 내보내기 조건(export condition)을 갖춘 리액트 19를 피어 종속성으로 설정할 수 있다는 뜻입니다.


📝 참고

서버 컴포넌트를 지원하려면 어떻게 구축해야 하나요?

리액트 19의 서버 컴포넌트는 안정적이며 메이저 버전 간에 충돌되는 지점은 없지만, 리액트 서버 컴포넌트 번들러 또는 프레임워크를 구현하는 데 사용되는 기본 API는 유의적 버전(semver)을 따르지 않으므로 19.x 마이너 버전 간에 충돌 지점이 있을 수 있습니다.

번들러 또는 프레임워크로서 리액트 서버 컴포넌트를 지원하려면 특정 리액트 버전으로 고정하거나 카나리 릴리즈를 사용하는 것을 권장합니다. 향후에도 번들러 및 프레임워크와 협력하여 리액트 서버 컴포넌트를 구현하는 데 사용되는 API를 안정화할 예정입니다.


더 자세한 내용은 리액트 서버 컴포넌트 문서를 참고하세요.

서버 액션

서버 액션은 클라이언트 컴포넌트가 서버에서 실행되는 비동기 함수를 호출할 수 있도록 합니다.

서버 액션이 "use server" 지시어와 함께 정의되면 프레임워크는 자동으로 서버 함수에 대한 참조를 생성하여 클라이언트 컴포넌트에 전달합니다. 함수가 클라이언트에서 호출될 때 리액트는 함수를 호출하는 서버에 요청을 보내고 결과를 반환합니다.


📝 참고

서버 컴포넌트에 대한 지시어는 없습니다.

일반적으로 "use server" 지시어가 서버 컴포넌트를 나타낸다고 오해하는 경우가 많지만, 서버 컴포넌트를 위한 지시어는 존재하지 않습니다. "use server" 지시어는 서버 액션에서 사용됩니다.

이에 대한 더 자세한 정보는 지시어 문서를 참고하세요.


서버 액션은 서버 컴포넌트에서 생성될 수 있으며 클라이언트 컴포넌트에 프로퍼티로 전달될 수 있습니다. 또는 클라이언트 컴포넌트에 가져와 사용할 수 있습니다.

더 자세한 내용은 리액트 서버 액션 문서를 참고하세요.

리액트 19에서 무엇이 개선되었나요?

프로퍼티가 된 ref

리액트 19부터 함수 컴포넌트에서 ref를 프로퍼티로 접근할 수 있습니다.

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

//...
<MyInput ref={ref} />

리액트 19의 새로운 함수 컴포넌트에서는 더 이상 forwardRef를 사용하지 않아도 됩니다. ref 프로퍼티를 사용하도록 자동으로 수정하는 codemod를 출시할 예정입니다. 향후 버전에서는 forwardRef를 사용하지 않고 제거할 계획입니다.


📝 주의

클래스 컴포넌트에 전달된 refs는 컴포넌트 인스턴스를 참조하므로 프로퍼티로 전달되지 않습니다.


하이드레이션 에러 개선

react-dom에서의 하이드레이션 에러를 보고하는 방식 또한 개선되었습니다. 예를 들어, 개발 모드에서 하이드레이션 불일치가 발생했을 때 구체적인 정보 없이 여러 에러를 보여주고 있었습니다.

이제 리액트 19에서는 어떻게 불일치가 발생했는지 하나의 에러 메시지로만 기록합니다.

프로바이더가 된 <Context>

리액트 19에서는 <Context.Provider> 대신 <Context>를 프로바이더로서 렌더링할 수 있습니다.

const ThemeContext = createContext('');

function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );
}

이제 컨텍스트 프로바이더는 <Context>를 사용할 수 있으며, 이미 사용중인 프로바이더를 변환하는 codemod 또한 출시될 예정입니다. 향후 버전에서는 <Context.Provider>가 폐기됩니다.

ref를 위한 클린업 함수

ref 콜백에서 클린업 함수를 반환하는 것을 지원합니다.

<input
  ref={(ref) => {
    // ref가 생성됨

    // 추가된 사항: 요소가 DOM에서 제거되었을 때
    // ref를 재설정하는 클린업 함수를 반환합니다
    return () => {
      // ref 클린업
    };
  }}
/>

컴포넌트가 언마운트 되었을 때, 리액트는 ref 콜백에서 반환된 클린업 함수를 호출합니다. DOM ref뿐만 아니라 클래스 컴포넌트의 ref, useImperativeHandle에서도 동작합니다.


📝 참고

이전 리액트는 컴포넌트가 언마운트 될 때 ref 함수를 null과 함께 호출했습니다. ref가 클린업 함수를 반환한다면 이제 리액트는 이 단계를 건너 뜁니다.

향후 버전에서는 컴포넌트를 언마운트할 때 null과 ref를 호출하는 것을 폐기할 예정입니다.


클린업 함수의 도입으로 인해 ref 콜백에서 다른 것을 반환하면 타입스크립트에서 거부됩니다. 이를 수정하려면, 다음과 같이 암시적 반환을 지양해야 합니다.

- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />

원래 코드에선 HTMLDivElement 인스턴스를 반환했기 때문에 타입스크립트는 클린업 함수를 반환하려고 하는 것인지 아닌지 정확히 알 수 없었습니다.

no-implicit-ref-callback-return 옵션과 함께 codemod를 실행하면 위 패턴을 적용할 수 있습니다.

useDeferredValue 초깃값

useDeferredValueinitialValue 옵션을 추가했습니다.

function Search({deferredValue}) {
  // 초기 렌더 값은 ''입니다.
  // 이후 deferredValue로 리렌더링 됩니다.
  const value = useDeferredValue(deferredValue, '');

  return (
    <Results query={value} />
  );
}

initialValue가 존재하면, useDeferredValue는 컴포넌트 초기 렌더 시 value를 반환하고, 이후 백그라운드에서 다시 렌더링할 때 반환된 deferredValue를 사용합니다.

더 자세한 내용은 useDeferredValue 문서를 참고하세요.

문서 메타데이터 지원

HTML에서 <title>, <link>, <meta>와 같은 메타데이터 태그들은 문서의 <head> 섹션 내에 배치되어 있습니다. 리액트에서는 앱에 적합한 메타데이터를 결정하는 컴포넌트가 <head>를 렌더링하는 위치에서 매우 멀거나 리액트가 <head>를 전혀 렌더링하지 않을 수 있습니다. 이전에는 이러한 요소들을 효과적으로 삽입하고자 react-helmet과 같은 라이브러리를 사용하거나 수동으로 삽입해야 했으며, 서버에서 리액트 앱을 렌더링할 때 세심한 처리가 필요했습니다.

리액트 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>
  );
}

리액트가 위 컴포넌트를 렌더링할 때, <title>, <link>, <meta> 태그는 문서 <head> 섹션에 위치하도록 자동으로 호이스팅 처리합니다. 메타데이터 태그를 기본적으로 지원함에 따라 클라이언트 전용 앱, 스트리밍 SSR 및 서버 컴포넌트와 함께 동작하는 것도 가능합니다.


📝 참고

그럼에도 불구하고 메타데이터 라이브러리가 필요할 수 있습니다.

간단한 경우 문서 메타데이터 태그를 렌더링하는 것이 적합할 수 있으나, 라이브러리는 일반 메타데이터를 현재 라우트에 따라 특정 메타데이터로 재정의하는 등 더 우수한 기능을 제공할 수 있습니다. 추가된 기능을 통해 react-helmet과 같은 프레임워크와 라이브러리가 메타데이터 태그를 지원하기 쉽게 만들며, 대체가 아닌 보완의 역할을 수행합니다.


더 자세한 내용은 <title>, <link>, <meta>의 각 문서를 참고하세요.

스타일시트 지원

외부에서 연결되거나(<link rel="stylesheet" href="...">) 인라인으로 추가된(<style>...</style>) 스타일시트는 스타일 우선순위 규칙으로 인해 DOM에서 신중하게 위치를 지정해야 합니다. 컴포넌트 내에서 합성 가능한 스타일시트 기능을 구축하는 것은 어렵기 때문에 사용자들은 종종 컴포넌트가 사용하는 스타일을 모두 로드하거나 이러한 복잡성을 캡슐화하는 스타일 라이브러리를 사용하게 됩니다.

리액트 19에서는 이러한 복잡성을 다루고 동시 렌더링을 위한 더 깊은 통합을 제공하며, 내장된 스타일시트 지원을 통해 클라이언트의 동시 렌더링과 서버의 스트리밍 렌더링에 대한 지원을 확장합니다. 리액트에 스타일시트의 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" />  <-- foo와 bar 사이에 삽입됩니다.
    </div>
  )
}

서버 사이드 렌더링 중 리액트는 <head>에 스타일시트를 포함시킵니다. 이로써 브라우저가 로드될 때까지 화면이 그려지지 않도록 합니다. 이미 스트리밍을 시작한 후에 스타일시트가 늦게 발견되더라도, 리액트는 해당 스타일시트에 의존하는 서스펜스 바운더리의 콘텐츠를 공개하기 전에 스타일시트가 클라이언트의 <head>에 삽입되도록 보장합니다.

클라이언트 사이드 렌더링 도중 리액트는 새로 렌더링 된 스타일시트가 로드될 때까지 대기한 후 렌더링을 커밋합니다. 이 컴포넌트를 애플리케이션 내 여러 위치에서 렌더링하는 경우 리액트는 스타일시트를 문서에 한 번만 포함시킵니다.

function App() {
  return <>
    <ComponentOne />
    ...
    <ComponentOne /> // DOM에 중복된 스타일시트 링크를 생성하지 않습니다.
  </>
}

스타일시트를 수동으로 로드하는 데 익숙한 사용자는 특정 컴포넌트에 의존하는 스타일시트를 찾을 수 있어 지역적 추론이 용이하고 실제로 필요한 스타일시트만 로드하는 것이 더 쉬워집니다.

스타일 라이브러리 및 번들러와의 스타일 통합도 이 새로운 기능을 채택할 수 있으므로 직접 스타일시트를 렌더링하지 않더라도, 도구가 이 기능을 사용하도록 업그레이드 된다면 이점을 함께 누릴 수 있습니다.

더 자세한 내용은 <link>, <style>의 각 문서를 참고하세요.

비동기 스크립트 지원

HTML에서 일반 스크립트(<script src="...">)와 지연 스크립트(<script defer="" src="...">)는 문서 순서대로 로드되므로, 이러한 종류의 스크립트를 컴포넌트 트리의 깊은 곳에 렌더링하기 어렵습니다. 그러나 비동기 스크립트(<script async="" src="...">)는 임의의 순서로 로드됩니다.

리액트 19에서는 실제로 스크립트에 의존하는 컴포넌트 내 어디든지 스크립트를 렌더링할 수 있도록 비동기 스크립트에 대한 지원을 개선했습니다. 따라서 스크립트 인스턴스를 재배치하거나 중복을 제거하는 작업을 수행할 필요가 없어졌습니다.

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

function App() {
  <html>
    <body>
      <MyComponent>
      ...
      <MyComponent> // DOM에 중복된 스크립트를 생성하지 않습니다.
    </body>
  </html>
}

모든 렌더링 환경에서 여러 다른 컴포넌트에 의해 비동기 스크립트가 렌더링되더라도 리액트가 스크립트를 한 번만 로드하고 실행하므로 중복이 제거됩니다.

서버 사이드 렌더링에서 비동기 스크립트는 <head>에 포함되며 스타일시트, 폰트 및 이미지 프리로드와 같이 화면 그리기를 차단하는 더 중요한 리소스 다음으로 우선순위가 지정됩니다.

더 자세한 내용은 <script> 문서를 참고하세요.

리소스 프리로딩 지원

초기 문서를 로드하거나 클라이언트 쪽에서 업데이트가 발생할 때, 브라우저에 최대한 빨리 로드해야 할 리소스를 알려주면 페이지 성능에 아주 큰 영향을 미칠 수 있습니다.

리액트 19는 브라우저 리소스를 로드하고 프리로드하는 새로운 API가 여럿 포함되어 있어 비효율적인 리소스 로드로 방해받지 않는 훌륭한 경험을 쉽게 만들 수 있습니다.

import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
  preinit('https://.../path/to/some/script.js', {as: 'script' }) // 스크립트를 즉시 로드하고 실행
  preload('https://.../path/to/font.woff', { as: 'font' }) // 폰트 프리로드
  preload('https://.../path/to/stylesheet.css', { as: 'style' }) // 스타일시트 프리로드
  prefetchDNS('https://...') // 이 호스트에게 아무것도 요청하지 않을 수도 있는 경우
  preconnect('https://...') // 요청은 하지만 무엇을 요청할지 확실하지 않은 경우
}
<!-- 위 컴포넌트를 사용하면 다음과 같은 DOM/HTML이 생성됨 -->
<html>
  <head>
    <!-- 호출 순서가 아니라, 미리 로드했을 때의 유용성에 따라 우선순위를 지정함 -->
    <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를 사용하면 폰트와 같은 추가 리소스의 탐색을 스타일시트 로딩에서 제외하여 초기 페이지 로드를 최적화할 수 있습니다. 또한 탐색이 예상되는 리소스 목록을 미리 가져온 후 클릭 또는 마우스 오버 시 해당 리소스를 프리로드하여 클라이언트 업데이트 속도를 높일 수 있습니다.

더 자세한 내용은 리소스 프리로딩 API 문서를 참고하세요.

서드파티 스크립트와 확장 프로그램 호환성

서드파티 스크립트와 브라우저 확장 프로그램을 고려하여 하이드레이션을 개선했습니다.

하이드레이션 중에 클라이언트에서 렌더링하는 요소가 서버에서 찾은 HTML과 일치하지 않으면, 리액트는 콘텐츠를 수정하기 위해 강제로 클라이언트 렌더링을 다시 수행합니다. 이전에는 서드파티 스크립트나 브라우저 확장 프로그램에 의해 요소가 삽입되면 불일치 오류가 발생하고 클라이언트를 그대로 렌더링 했습니다.

리액트 19에서는 <head><body>에 예상치 못한 태그가 있으면 건너뛰어지므로 불일치 오류가 발생하지 않습니다. 리액트가 관련 없는 하이드레이션 불일치로 인해 전체 문서를 다시 렌더링해야 할 경우, 서드파티 스크립트 및 브라우저 확장 프로그램에 의해 삽입된 스타일시트를 그대로 유지합니다.

에러 보고의 개선

리액트 19에서는 에러 처리를 개선하여 중복을 제거하고 잡힌 에러와 잡히지 않은 에러를 처리하는 옵션을 제공합니다. 예를 들어, 이전에는 렌더링 중 에러 바운더리에 잡힌 에러가 있으면 리액트가 에러를 두 번(원래 오류와 자동 복구에 실패한 후) 던졌습니다. 그런 다음 에러가 발생한 위치에 대한 정보를 포함하여 console.error를 호출했습니다.

그 결과 에러가 발견될 때마다 3개의 에러가 발생했습니다.

그러나 리액트 19에서는 모든 에러 정보를 포함한 하나의 에러만을 보고합니다.

또한 두 가지 새로운 루트 옵션을 추가하여 onRecoverableError를 보완했습니다.

  • onCaughtError: 리액트가 에러 바운더리에서 에러를 잡은 경우 호출됨
  • onUncaughtError: 에러가 발생했으나 에러 바운더리에서 잡히지 않은 경우 호출됨
  • onRecoverableError: 에러가 발생하고 자동으로 복구된 경우 호출됨

더 자세한 내용과 예제는 createRoot, hydrateRoot의 문서를 참고하세요.

사용자 정의 요소(Custom Elements) 지원

리액트 19는 사용자 정의 요소를 완전히 지원하며 Custom Elements Everywhere에서 모든 테스트를 통과했습니다.

과거 버전에서는 리액트에서 인식되지 않는 프로퍼티를 속성(attribute)으로 처리하여 사용자 정의 요소의 사용이 어려웠습니다. 리액트 19에서는 아래와 같은 전략을 사용하여 각 렌더링 중 동작하는 속성을 지원하도록 추가했습니다.

  • 서버 사이드 렌더링: 사용자 정의 요소에 추가된 프로퍼티가 string, number, true와 같은 원시 타입인 경우 속성으로 렌더링됩니다. object, symbol, function, false와 같은 원시 타입이 아닌 경우 해당 프로퍼티는 생략됩니다.
  • 클라이언트 사이드 렌더링: 사용자 정의 요소의 인스턴스와 일치하는 프로퍼티인 경우 프로퍼티로 할당되고 일치하지 않으면 속성으로 할당됩니다.

리액트에서 사용자 정의 요소 지원의 설계와 구현을 주도한 Joey Arhar에게 감사의 말씀을 드립니다.

어떻게 업그레이드 할 수 있나요?

단계별 지침과 중요한 변경점 및 주목할 만한 모든 변경점의 전체 목록은 리액트 19 업그레이드 가이드 문서를 참고하세요.

0개의 댓글