JIGGAG

10월 한달동안 로그

2024년 11월 14일

(번역) 리액트 컴파일러 이해하기

  • 리액트 컴파일러
    • 리액트의 핵심 아키텍처는 사용자가 제공한 함수(즉, 컴포넌트)를 반복해서 호출합니다
    • 이 함수의 비용이 큰 경우 성능 문제를 겪게 된다
    • 이러한 함수 실행을 최적화 하여 성능을 끌어올린다
    • 이것을 리액트 컴파일러 로 자동으로 처리해주겠다는 것이다 🥳
  • 우선 아주 좋아보인다
    • 자동으로 최적화를 해준다니 내가 할 일을 거의 다 해주겠다는 것 아닌가
    • (갑자기 AI 보다 컴파일러에게 먼저 밀려 책상을 정리하게 되는건 아닐까)
  • 컴파일러, 트랜스파일러, 옵티마이저
    • 컴파일러, 트랜스파일러, 옵티마이저는 코드가 포함된 텍스트 파일을 가져와서 분석하고 기능적으로는 동일하지만 다른 코드를 생성하는 프로그램입니다.
    • 트랜스파일하면 바벨을 먼저 떠올린다
      • 현재 환경에서 지원하지 않는 코드가 동작할 수 있도록 하위 호환된 코드로 변환해주는데
      • 이는 결국 컴파일러가 하는 역할과 비슷하다
    • 여기서는 트랜스파일러를 소스 간 컴파일러 , 옵티마이저는 최적화 컴파일러 라고 표현한다
    • 그렇다면 리액트 컴파일러는?
      • 작성된 리액트 코드를 읽고 분석하여 최적화된 리액트로 다시 작성해주는 역할이다
  • 리액트 그리고 메모이제이션
    • 함수를 실행한 결과값을 현재의 값과 비교하여 DOM 에 반영하는 재조정을 거친다
    • UI 가 바뀌는 것은 함수의 실행 결과인데 이 함수의 실행 비용이 클수록 더디게 반영되고 이는 성능 문제로 이어진다
    • 이러한 비용이 큰 함수를 메모이제이션하고 캐싱된 값을 사용하여 성능을 개선한다
    • 메모이제이션된 함수가 의존하고 있는 무언가가 변경되었음을 알려주어야 새로 계산된 값을 저장하는데
    • 이때 정확한 의존 상태를 전달해야한다
  • 리액트 컴파일러
    • 메모이제이션을 직접 작성하지 않고 (아무것도 메모이제이션 하지 않는다)
    • 컴파일러가 읽고 이 함수가 의존하고 있는 것들을 분석하여 새로 메모이제이션된 함수로 재작성 한다
  • 여기서 걱정하는 부분이 생긴다
    • 우리는 다른 사람의 코드가 우리의 원래 의도와 일치하는 것을 만들어낼 것이라고 신뢰하고 있습니다.
    • 직접 의존성을 작성하던 것과는 다르게 자동으로 의존성이 추가되었다
    • 만약 의도적으로 이 의존성은 빼고 싶었다면 이건 컴파일러가 어떻게 알 수 있을까?
    • 결국 이러한 의도를 전달할 수 있는 무언가가 새로이 추가될 것으로 생각된다
  • 메모이제이션은 메모리를 사용한다
    • 메모이제이션 및 캐싱은 일반적으로 처리량을 메모리로 교환하는 것을 의미합니다.
    • 한 순간의 연산 비용을 줄이기 위해 메모리에 계속 저장해서 들고 있는 것이다
    • 메모리 최적화 작업으로 이어질 것이고 책상은 여기로 옮겨야겠다
  • 하지만 디버깅해야 하는 코드는 내 코드뿐만 아니라 도구가 생성하는 코드도 포함한다는 점을 명심해야 합니다.
    • 😯

(번역) 리액트 컴파일러 사용법 — 완벽 가이드

  • 새로 나온 리액트19 컴파일러 이야기
  • 사람이 작성한 리액트 코드를 읽고 분석하여 최적화된 코드로 변환
    • 이때 최적화 해주는 작업이 위에서도 언급했던 메모이제이션을 알아서 해줄게! 하는 작업이다
  • 리액트 컴파일러 없이 최적화
    • memo, useMemo, useCallback….
    • 우리는 이러한 최적화 기술을 과도하게 사용하기 시작했습니다. 이러한 과도한 최적화는 애플리케이션의 성능에 부정적인 영향을 미칠 수 있습니다.
  • 리액트 컴파일러 사용한 최적화
    • 우선 컴파일러가 최적화를 자동으로 진행하려면 작성된 코드가 자동 컴파일을 할 수 있는 코드의 형태를 가지고 있는지 체크해야한다
      • react-compiler-healthcheck
      • eslint-plugin-react-compiler
    • 그리고나서 새로운 컴파일러를 사용하겠다고 설정
      • 자동 최적화가 우려된다면 이 설정을 하지 않으면 되겠다 🤔
      • babel-plugin-react-compiler
    • 컴파일러 내부 동작
      • 어떤 컴포넌트나 훅을 작성하면 (메모이제이션 되지 않은)
      • 컴파일러가 최적화된 코드를 반환하는데
      • 이 코드를 보면 캐시에 저장하여 상태를 구분하여(1) 변경되지 않았다면 이전과 동일하게 반환한다(2)
        • (1) 컴포넌트 props나 state를 캐시해야하고
        • (2) 여기서 반환하는 JSX 도 캐시되어있어야한다
    • 기대대로 동작하지 않을 가능성이 존재한다
      • 나의 로직은 언제나 그렇듯 복잡하기에 컴파일러가 의도치 않게 최적화를 진행할 수 있다
      • 이러한 우려에 깔끔한 해결법은 use no memo 로 해당 파일을 컴파일러에게 최적화 하지 않도록 예외 처리하는 것이다

(번역) 리액트 컴포넌트의 유형 돌아보기

  • 리액트 createClass
    • 이 함수를 처음 보았다
    • 리액트를 클래스/함수 컴포넌트 과도기 이후 접했는데
    • 클래스 컴포넌트와 유사한 형태를 띄고 있어 따로 사용해보지는 않을듯
  • 리액트 믹스인 mixins (패턴)
  • 리액트 클래스 컴포넌트
    • createClass 를 대체하는 클래스 컴포넌트
    • 메서드 안에서 바인딩이 필요하기 때문에 ES6의 화살표 함수를 통해 이를 간단히 해결
  • 리액트 고차 컴포넌트 (패턴)
    • 컴포넌트 재사용을 위해 컴포넌트가 컴포넌트를 받아 확장된 컴포넌트를 반환하는 형태
    • 리액트 고차 컴포넌트와 Render Prop 컴포넌트 모두 현재의 리액트 애플리케이션에서는 많이 사용되지 않습니다. 오늘날에는 리액트 훅과 함께 사용하는 함수 컴포넌트가 컴포넌트 간 로직을 공유하는 표준입니다.
      • 훅을 이용하는 것을 권장
  • 리액트 함수 컴포넌트
    • 훅을 이용해 상태와 부수 효과 사용
    • 클래스 컴포넌트 대체
  • 리액트 서버 컴포넌트
    • RSC

    • 컴포넌트를 서버에서 작성하여 HTML만을 전송하는 것

    • 컴포넌트가 직접 서버 리소스에 접근할 수 있다는 장점

      const ReactServerComponent = async () => {
        const posts = await db.query('SELECT * FROM posts');
      
        return (
          <div>
            <ul>
              {posts?.map(post => (
                <li key={post.id}>{post.title}</li>
              ))}
            </ul>
          </div>
        );
      };
      
      export default ReactServerComponent;
      
    • 서버 컴포넌트가 생겨나면서 이전에 나왔던 클래스/함수 컴포넌트들을 클라이언트 컴포넌트로 분류

    • 서버에서 작성되기 때문에 리액트 훅이나 이벤트 핸들러를 통한 효과를 누릴 수 없다

  • 비동기 컴포넌트
    • 서버 컴포넌트가 서버에서 작성되는 것을 기다리기 위해 비동기 형태 지원
      • Promise를 컴포넌트에 props로 전달하고 이에 접근할 수 있다