글목록 보기

React 19 살펴보기

[작성일] 2024년 12월 8일

2024_웹프레임워크와_기술_선호도_조사_이미지

출처 https://survey.stackoverflow.co/2024/technology

들어가며

프론트엔드 개발 분야는 끊임 없이 새로운 기술이 쏟아진다. 이러한 변화속에서도, React는 2022년 초 이후로 눈에 띄는 대규모 업데이트 없이 꾸준히 프론트엔드 생태계의 중심을 지켜왔다.

그러던 중, 2024년 4월에 React 19 베타 버전이 공개되었고, 이어서 2024년 12월 5일, 드디어 React 19의 정식 버전이 릴리즈되었다.

이 글에서는 React 가 어떤 문제를 해결했고 어떻게 진화하고 있는지, 그리고 새롭게 추가 된 기능들이 어떤 이점을 가져다 줄 수 있는지를 알아보겠다.

React 19의 새로운 부분

useTransition()

모던 웹에서 비동기적으로 데이터를 요청하고, 응답이 오면 해당 값을 사용하여 화면을 처리하는 작업은 매우 흔하고 반복적인 작업이다.

React 19에서는 useTransition 훅을 사용하여, 비동기 상태 관리를 더 효과적으로 수행할 수 있다.

useTransition에서는 크게 두가지 이점을 가져다 준다.

  1. 긴 작업을 수행하는 동안 다른 상태 업데이트가 먼저 처리되도록 하여, 끊기지 않는 UI를 제공한다.
  2. 비동기 데이터가 처리 되는 동안의 pending 상태 처리를 도와준다.

아래의 코드로 직접 알아보도록 하겠다.

React 19 이전 방식에서 이름 변경 기능을 구현한다면, 아래와 같이 구현할 것이다.

const NameForm = () => {
  const [name, setName] = useState<string>("");
  const [isPending, setIsPending] = useState<boolean>(false);
  const [error, setError] = useState<boolean>(false);

  const handleSubmit = async () => {
    setIsPending(true);

    const result = await updateNameSlowly(name);

    setIsPending(false);
    setError(result.isError);
  };

  return (
    <>
      <input value={name} onChange={(event) => setName(event.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        이름 변경
      </button>
      {error && <strong>이름 변경 중 에러가 발생했습니다.</strong>}
    </>
  );
};

아래는 React 19의 useTransition 훅을 사용할 때의 예시이다.

const NameForm = () => {
  const [name, setName] = useState<string>("");
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState<boolean>(false);

  const handleSubmit = () => {
    startTransition(async () => {
      const result = await updateNameSlowly(name);
      setError(result.isError);
    });
  };

  return (
    <>
      <input value={name} onChange={(event) => setName(event.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        이름 변경
      </button>
      {error && <strong>이름 변경 중 에러가 발생했습니다.</strong>}
    </>
  );
};

startTransition의로 감싼 비동기 작업은 덜 중요한 작업으로 간주되어 UI를 블로킹하지 않으며, 다른 중요 작업이 우선 처리 될 수 있게 해준다. 또한, 비동기 작업이 시작 되고 완료 될 때, isPending 값은 자동으로 변경 되어, 로딩 상태를 사용자에게 알릴 수 있다.

React의 병렬 렌더링 엔진은 이러한 우선순위에 따라 고우선 작업을 먼저 처리하고, 남는 시간에 저우선 작업을 진행한다. 덕분에 사용자에게 UI가 블로킹되지 않고, 작업이 자연스럽게 이어지는 경험을 제공한다.

useActionState()

useActionState(action, initialState, permalink?)

Canary 버전에서 제시 되었던 useFormStateuseActionState로 대체되었다. React 19에서는 useActionState 훅을 사용하여 <form>의 비동기 작업 결과를 기반으로 상태를 업데이트할 수 있다.

또한, form태그의 action 속성에 비동기함수를 넘겨주면, form 내부의 입력 필드들은 자동으로 초기화 된다.

const MyForm = () => {
  const [state, action, isPending] = useActionState(updateUserName, {
    username: "", // 초기 상태
  });

  return (
    <form action={action}>
      <input
        id="username"
        name="username"
        type="text"
        defaultValue={state.username}
      />
      <button type="submit" disabled={isPending}>
        업데이트
      </button>

      {isPending && <p>업데이트 중입니다 ...</p>}
      {state.username && <p>업데이트 된 사용자 이름: {state.username}</p>}
    </form>
  );
};

useFormStatus()

useFormStatus 훅을 사용하여, 하위 컴포넌트에서도 상위 form의 상태를 읽을 수 있다.

이 훅을 사용해 상위 form의 상태를 쉽게 읽을 수 있어, 기존처럼 prop이나 global state로 상위 form의 상태를 관리할 수고가 사라졌다.

function MyButton() {
  const { pending, data, method, action } = useFormStatus();
  return <button disabled={pending}>저장</button>;
}

export default function App() {
  return (
    <form action={action}>
      <MyButton />
    </form>
  );
}

useOptimistic()

React 19의 useOptimistic 훅은 비동기 요청이 진행되는 동안 사용자의 입력값을 미리 UI에 반영하여, 더 직관적 사용자 경험을 제공한다. 이 훅을 사용하면 요청이 완료되지 않은 상태에서도 예상 값을 UI에 즉시 보여줄 수 있으며, 요청 완료 후에는 실제 응답 값으로 UI가 자동으로 업데이트된다.

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>당신의 이름은: {optimisticName}</p>
      <p>
        <input
          type="text"
          name="name"
          disabled={currentName !== optimisticName}
        />
      </p>
    </form>
  );
}

use() API

use() 는 Promise 객체나 context와 같은 값을 읽을 수 있게 해주는 API이다. use()는 반복문, 조건문처럼 중첩 된 함수 내에서 조건적으로 수행가능하다는 특징이 있다.

use(Promise) 형태로 사용하면, Promise 객체가 resolve 될 때 해당 값을 반환하고, 아직 반환되지 않았다면, 컴포넌트는 해당 작업이 완료 될 때까지 렌더링을 중단(suspend)하게 된다.

React 19 이전 방식에서 비동기 데이터를 읽어오려면, 아래와 같이 코드를 작성해야한다.

const MyComponent = () => {
  const [datas, setDatas] = useState<string[]>([]);

  const getList = async () => {
    const result = await getHeavyList(nam);
    setDatas(result.datas);
  };

  useEffect(() => {
    getList();
  }, []);

  return <div>{datas ? JSON.stringify(datas) : "데이터를 로드 중입니다."}</div>;
};

이제 use(Promise)로 리팩토링을 진행해보자

const MyComponent = () => {
  const datas = use(getHeavyList);

  return <div>{JSON.stringify(datas)}</div>;
};

const App = () => {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <MyComponent />
    </Suspense>
  );
};

코드가 눈에 띄게 간결해진 모습이다. 또한 Suspense 기반으로 에러 처리를 수행하고, 데이터 흐름도 단순화 된 것을 확인할 수 있다.


React 19의 개선된 부분

ref

기존에는 하위 컴포넌트로 ref를 전달할 때, forwardRef를 사용하여 전달해야 했지만, React 19부터 컴포넌트에서 ref를 prop으로 직접 전달 할 수 있게 됐다. (forwardRef는 삭제 예정)

Metadata 지원

React 19에서 아래와 같이 meta 태그들을 사용하면, 해당 태그들이 <head>로 자동으로 끌어 올려진다.

const BlogPost = ({ post }) => {
  return (
    <article>
      <h1>{post.title}</h1>
      <title>{post.title}</title>
      <meta name="author" content="Ronaldo" />
      <link rel="author" href="https://twitter.com/cronaldo/" />
      <meta name="keywords" content={post.keywords} />
      <p>어쩌구 저쩌구 ~~</p>
    </article>
  );
};

마치며

상반기에 공개되었던 React 19 RC와 비교했을 때, 이번 정식 버전은 기능적으로 큰 차이는 없지만 안정성과 완성도를 높여 출시된 것 같다.

React 18버전과 비교하자면 비동기 데이터를 처리하는 부분에서의 개발 편의성과 기능 향상에 집중한 릴리즈인 것 같다.

기대를 모으던 React Compiler는 이번 버전에 포함되지 않았다. 또한, Next.js나 TanStack Query 같은 도구들과의 호환성을 고려했을 때, 실무에 적용되기까지 시간이 조금 더 걸릴 것으로 보인다.

이번주에 출시 된 따끈따끈한 React 19의 공식 문서를 익혀보며 덜 숙련 된 상태로 작성하게 된 부족한 포스팅이기에, 기회가 될 때 직접 프로젝트에 적용 시켜보며 더 깊게 배워보도록 해야겠다.