[번역] 리액트 컴파일러와 리액트 19 - 이제 메모이제이션을 신경쓰지 않아도 되나요?

eunbinn·2024년 4월 3일
30

FrontEnd 번역

목록 보기
30/38
post-thumbnail

출처: https://www.developerway.com/posts/react-compiler-soon

리액트 컴파일러가 리액트 19에 포함되는 내용이 아니란 사실을 알고 계셨나요? 그렇다면 언제쯤 출시 예정이며 리액트에서 메모를 신경쓰지 않을 수 있을까요? 그러고 컴파일러가 출시되면 정확히 무엇이 바뀌는걸까요?

지난 한 달간 리액트 19와 React Forget으로 알려진 리액트 컴파일러에 대한 이야기로 리액트 커뮤니티가 뜨거웠습니다. 우리 모두는 머지 않아 리액트에서 메모에 대해 생각할 필요가 없어질 수 있다는 것에 대해 (좋은 의미로) 마음을 빼앗겼습니다. 하지만 정말로 그럴까요? 앞으로 몇 달 안에 memo, useMemo 그리고 useCallback을 잊어야 할까요? 그리고 리액트 컴파일러가 출시되면 실제로 무엇이 바뀌며, 그 이후에는 리액트에 대해 무엇을 배우고 가르쳐야 할까요?

한 번 살펴봅시다.

리액트 19는 리액트 컴파일러가 아닙니다

가장 중요한 것을 먼저 말씀드리자면, 메모이제이션은 곧바로 사라지지 않을 것이기 때문에 아직은 잊어서는 안됩니다. 리액트 19는 리액트 컴파일러가 아닙니다. 리액트 팀은 리액트 19가 곧 출시됨을 발표하는 하나의 블로그 게시물에서 컴파일러에 대해서도 발표했고, 모두가 흥분하여 이 둘이 함께 한다는 섣부른 결론을 내렸습니다.

리액트 팀 멤버의 트윗이 이러한 혼란을 명확히 해줍니다.

image

리액트 19에서는 여러 가지 새로운 기능이 추가될 예정이지만 컴파일러는 조금 더 기다려야 합니다. 현재로서는 얼마나 걸릴지 확실하지 않지만, 다른 리액트 핵심 멤버의 트윗에 따르면 올해 말쯤에 출시될 수도 있다고 합니다.

image

개인적으로 저는 이 타임라인에 회의적입니다. 컴파일러와 그 타임라인을 소개한 리액트 팀원의 강연을 보면, 우리는 아직 컴파일러 여정의 한가운데에 있습니다.

image

이 여정은 2년 전인 2021년에 시작되었습니다. Meta 처럼 규모가 큰 코드베이스에서 이렇게 근간이 되는 기능을 출시하는 것은 매우 복잡한 일일 것입니다. 그렇기 때문에 타임라인의 중간에서 끝으로 넘어가는 데에도 또다시 2년이 걸릴 수도 있습니다.

하지만 또 모르죠, 리액트 팀은 올해에 출시되도록 작업할 수도 있습니다. 그렇다면 좋은 소식일 것입니다. 비디오에서 언급한 바에 따르면 리액트 팀은 코드를 변경하지 않고도 새로운 컴파일러를 적용할 수 있음을 약속합니다. 그냥 작동할 거에요. 실제로 연말까지 출시된다면 정말로 그냥 동작할 것이라는 좋은 신호이며 사용자들은 빠르고 쉽게 전환할 수 있을 것입니다.

하지만 올해 컴파일러가 출시되고 실제로 단점 없이 매우 쉽게 도입할 수 있다고 해도, useCallbackmemo를 바로 잊어도 된다는 의미는 아닙니다. "이미 컴파일러가 활성화된 경우"에 대해 먼저 이야기한 다음 "아직 컴파일러로 마이그레이션하지 않은 드문 경우"의 시나리오로 천천히 전환하는 "전환" 기간이 반드시 존재할 것입니다.

클래스 컴포넌트에서 훅이 있는 함수형 컴포넌트로의 전환은 심적으로 적어도 3년(2018년부터)은 걸린 것 같습니다. 모든 강좌, 문서, 블로그가 따라잡으면서 대부분의 사람들이 훅이 있는 리액트 버전으로 마이그레이션했고, 우리는 함수형 컴포넌트와 훅을 기본으로 이야기하기 시작했습니다. 하지만 6년이 지난 지금도 여전히 여기저기에 많은 클래스 컴포넌트가 숨어 있습니다.

비슷한 타임라인을 컴파일러에도 적용해보면 적어도 향후 3년 동안은 memo, useMemo, useCallback이 무엇인지에 대한 지식을 유지해야 한다는 뜻이 됩니다. 운이 좋게도 컴파일러가 출시되는 즉시 마이그레이션 할 수 있는 최신 코드베이스에서 작업하는 경우라면 그보다 짧을 수 있습니다. 레거시 코드가 많아 마이그레이션 속도가 느린 대규모 코드베이스에서 작업하거나 리액트를 가르치는 선생님이라면 더 오래 걸릴 수 있습니다.

리액트 컴파일러로 변경되는 것들

그래서 정확히 무엇이 바뀌는건가요? 간단히 말하자면, 이제 모든 것이 메모화된다는 것입니다. 리액트 컴파일러는 일반적인 리액트 코드를 모든 훅의 종속성, 컴포넌트의 프로퍼티 및 컴포넌트 자체가 메모화된 코드로 변환하는 Babel 플러그인이 될 것입니다. 예를 들어 아래와 같은 코드가 있다고 하겠습니다.

const Component = () => {
  const onSubmit = () => {};
  const onMount = () => {};

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

  return <Form onSubmit={onSubmit} />;
};

내부적으로 이 코드는 onSubmitonMount는 모두 useCallback으로 감싸지고 FormReact.memo로 감싸진 것처럼 동작할 것입니다.

const FormMemo = React.memo(Form);

const Component = () => {
  const onSubmit = useCallback(() => {}, []);
  const onMount = useCallback(() => {}, []);

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

  return <FormMemo onSubmit={onSubmit} />;
};

물론 컴파일러가 정확히 이 코드로 변환하는 것은 아니며 이보다 훨씬 더 복잡하고 발전된 형태일 것입니다. 하지만 간단히 이해하기에는 좋은 예시입니다. 정확한 세부 사항이 궁금하다면 컴파일러를 도입한 리액트 핵심 팀원들의 설명 영상을 추천합니다. 또한 여기서 왜 useCallbackmemo를 사용하는지 모르겠다면 유튜브의 고급 리액트 시리즈의 첫 여섯개의 동영상을 시청해 볼 것을 추천합니다. 리렌더링과 메모에 대한 모든 것을 다루고 있습니다. 만약 영상보다 글이 더 좋으시다면 이 글의 내용을 읽어보세요.

리액트를 가르치고 배우는 방식에 있어서 이러한 전환은 몇 가지 의미를 가집니다.

부모가 리렌더링되면 자식도 리렌더링되는 속성

현재 부모 컴포넌트가 리렌더링되면 내부에 렌더링된 모든 컴포넌트가 리렌더링됩니다.

// 부모 컴포넌트가 리렌더링되면
const Parent = () => {
  // 자식 컴포넌트도 리렌더링됩니다
  return <Child />;
};

현재 많은 사람들은 Child 컴포넌트는 props가 변경될 때만 리렌더링된다고 생각합니다. 저는 이를 "리렌더링 속설"이라고 부르고 싶습니다. 이는 사실이 아닙니다. 표준 리액트 동작에서 props는 중요하지 않습니다.

재밌게도 이는 컴파일러에선 사실이 됩니다. 모든 것이 메모화되어 있기 때문에 현재의 속설은 실제로 표준 리액트 동작이 됩니다. 몇 년 안에는 리액트 컴포넌트는 상태나 props가 변경될 때만 리렌더링되며, 부모가 리렌더링되는지 여부는 중요하지 않다고 가르쳐야 합니다. 때때로 삶은 참 기묘합니다.

더 이상 성능을 위한 합성은 필요 없습니다

현재 리렌더링을 줄일 수 있는 "상태를 아래로 이동시키기""컴포넌트를 자식으로 전달"과 같은 몇 가지 합성 기법이 있습니다. 리액트에서 메모이제이션을 제대로 하는 것은 매우 어렵기 때문에 저는 보통 useCallbackmemo를 사용하기 전에 이 기법들을 사용하기를 권장합니다.

예를 들어 아래와 같은 코드가 있다고 하겠습니다.

const Component = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setIsOpen(true)}>open dialog</Button>
      {isOpen && <ModalDialog />}
      <VerySlowComponent />
    </>
  );
};

VerySlowComponent는 매번 대화 상자가 열릴 때마다 리렌더링되어 대화 상자가 열리는 것을 지연시킵니다. 대화 상자를 여는 상태를 캡슐화하면 다음과 같이 작성할 수 있습니다.

const ButtonWithDialog = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setIsOpen(true)}>open dialog</Button>
      {isOpen && <ModalDialog />}
    </>
  );
};

const Component = () => {
  return (
    <>
      <ButtonWithDialog />
      <VerySlowComponent />
    </>
  );
};

아무것도 메모이제이션하지 않고도 VerySlowComponent의 불필요한 리렌더링을 제거했습니다.

컴파일러가 출시되면 이런한 패턴을 성능을 위해 적용하는 것은 불필요해질 것입니다. 관심사의 통합과 분리 목적으로는 계속해서 사용할 수 있습니다. 하지만 컴포넌트를 더 작은 컴포넌트로 분할해야 하는 자동화된 리렌더링 기능은 더 이상 없을 것입니다. 컴포넌트를 더 크게 작성해도 아무런 부정적 영향이 없을 수 있습니다.

더 이상 useMemo/useCallback을 모든 곳에 사용하지 않아도 됩니다

당연히 우리의 코드를 괴롭히던 useMemouseCallback이 모두 사라질 것입니다. 이 부분이 제일 마음에 듭니다. 이제 더이상 onSubmit props 콜백 하나를 기억하기 위해 여러 단계의 컴포넌트를 통해 props를 추적하지 않아도 됩니다. 서로 의존성이 얽혀있고 이해하기 불가능했던 읽기 어렵고 디버깅할 수 없는 useMemouseCallback 체인은 더 이상 없습니다. children이 메모이제이션 되지 않아 아무도 눈치채지 못한 채 메모이제이션이 깨지는 일도 이제 없습니다.

비교(diffing)와 재조정(reconciliation)

리액트에서 비교와 재조정을 설명하는 방식을 바꿔야할 수도 있습니다. 현재의 간단한 설명은 <Child />와 같은 컴포넌트를 "렌더링"한다고 하면 그 컴포넌트의 엘리먼트를 생성하기만 하면 된다는 것입니다. 이 엘리먼트 아래와 같은 객체 형태를 가집니다.

{
  "type": ...,
  "props": ...,
  // 다른 리액트 필드
}

여기서 "type"은 문자열 또는 컴포넌트에 대한 참조입니다.

아래와 같은 코드가 있다고 하겠습니다.

const Parent = () => {
  return <Child />;
};

Parent가 리렌더링되면 함수가 실행되고 <Child /> 객체가 재생성됩니다. 리액트는 해당 객체의 리렌더링 전과 후를 얕게 비교하고 만약 참조가 변경되었다면 하위 트리의 전체 비교를 수행해야 한다는 표시를 리액트에 전달합니다.

현재 <Child /> 컴포넌트가 props가 없더라도 항상 리렌더링되는 이유가 바로 이 때문입니다. <Child /> (React.createElement의 문법적 설탕(syntax sugar))의 결과는 항상 다시 생성되는 객체이기 때문에 얕은 비교 검사를 통과할 수 없습니다.

리액트 컴파일러가 있어도 엘리먼트, 비교, 재조정에 대한 개념은 변하지 않습니다. 하지만 <Child />는 props가 변경되지 않았다면 이제 메모화된 객체를 반환합니다. 따라서 컴파일러의 최종 결과는 엘리먼트를 포함한 모든 것이 useMemo로 래핑되는 것과 비슷합니다.

const Parent = () => {
  const child = useMemo(() => <Child />, []);
  return child;
};

하지만 이는 제한적으로 공개된 리소스를 바탕으로 제가 가정한 것이기 때문에 약간 틀릴 수도 있습니다. 어쨌든 이는 우리의 프로덕션 코드에서는 크게 중요하지 않은 구현 세부 사항일 뿐입니다.


다른 모든 것은 지금과 거의 동일하게 유지됩니다. 다른 컴포넌트 안에 컴포넌트를 생성하는 것은 여전히 대규모 안티 패턴이 될 것입니다. 엘리먼트를 식별하거나 상태를 재설정할 때는 여전히 "key" 속성을 사용할 것입니다. 컨텍스트는 여전히 다루기 어려운 문제일 것입니다. 심지어 데이터 페칭이나 에러 핸들링의 관한 것은 대화의 일부도 아닙니다.

어쨌든 저는 컴파일러의 출시가 정말 기대됩니다. 리액트 생활이 크게 개선될 것으로 기대하고 있습니다. 제가 지금까지 작성한 글들의 절반을 다시 쓰고 촬영한 유튜브 동영상의 절반을 다시 만들어야 한다고 할지라도 말이죠😅.


2개의 댓글

comment-user-thumbnail
2024년 4월 4일

감사합니다!!

답글 달기
comment-user-thumbnail
2024년 5월 17일

Nowadays, I receive a lot of information about students´ mental health during exams. Additionally, discussing the daily challenge with cookie clicker can lead to shared knowledge and improved performance over time.

답글 달기