(번역) 리액트 컴파일러 이해하기
- 리액트 컴파일러
리액트의 핵심 아키텍처는 사용자가 제공한 함수(즉, 컴포넌트)를 반복해서 호출합니다
- 이 함수의 비용이 큰 경우 성능 문제를 겪게 된다
- 이러한 함수 실행을 최적화 하여 성능을 끌어올린다
- 이것을
리액트 컴파일러
로 자동으로 처리해주겠다는 것이다 🥳
- 우선 아주 좋아보인다
- 자동으로 최적화를 해준다니 내가 할 일을 거의 다 해주겠다는 것 아닌가
- (갑자기 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 에서 HoC 를 구현하는 방법
- 다른 메서드 처럼 정의된 mixins 을 추가하는 것으로 사용하는데
- 상의 컴포넌트에서 정의된 메서드를 재정의 할 수 없어 항상 새로운 메서드를 추가해야한다는 단점이 있다
- 이해한게 맞다면… HoC 형태로 확장했는데 이를 확장 아닌 확장이라고 봐야하나
- 리액트 클래스 컴포넌트
- 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로 전달하고 이에 접근할 수 있다
- 서버 컴포넌트가 서버에서 작성되는 것을 기다리기 위해 비동기 형태 지원