새로운 아키텍처 소개
2018년부터 React Native 팀은 개발자들이 더 높은 품질의 경험을 만들 수 있도록 React Native의 핵심 내부 구조를 재설계해 왔습니다. 2024년 기준으로, 이 버전의 React Native는 대규모로 검증되었으며 Meta의 프로덕션 앱에서 사용되고 있습니다.
_새로운 아키텍처_라는 용어는 새로운 프레임워크 아키텍처와 이를 오픈소스로 제공하기 위한 작업을 모두 의미합니다.
새로운 아키텍처는 React Native 0.68부터 실험적으로 선택할 수 있게 되었으며, 이후 모든 릴리스에서 지속적으로 개선되고 있습니다. 현재 팀은 이를 React Native 오픈소스 생태계의 기본 경험으로 만들기 위해 노력하고 있습니다.
새로운 아키텍처가 필요한 이유
React Native로 수년간 개발을 진행하면서, 팀은 개발자들이 고품질의 경험을 구현하는 데 방해가 되는 몇 가지 한계점을 발견했습니다. 이러한 한계점은 프레임워크의 기존 설계에 근본적으로 내재된 문제였기 때문에, 새로운 아키텍처는 React Native의 미래를 위한 투자로 시작되었습니다.
새로운 아키텍처는 기존 아키텍처에서는 불가능했던 기능과 개선 사항을 가능하게 합니다.
동기식 레이아웃과 이펙트
적응형 UI를 구축할 때는 종종 뷰의 크기와 위치를 측정하고 레이아웃을 조정해야 합니다.
현재는 onLayout
이벤트를 사용해 뷰의 레이아웃 정보를 얻고 필요한 조정을 합니다. 하지만 onLayout
콜백 내에서 상태를 업데이트하면 이전 렌더링이 페인팅된 후에 적용될 수 있습니다. 이는 사용자가 초기 레이아웃을 렌더링하고 레이아웃 측정에 응답하는 사이에 중간 상태나 시각적인 점프를 볼 수 있음을 의미합니다.
새로운 아키텍처에서는 레이아웃 정보에 동기적으로 접근하고 적절하게 스케줄링된 업데이트를 통해 이 문제를 완전히 피할 수 있습니다. 이를 통해 사용자에게 중간 상태가 보이지 않게 됩니다.
예제: 툴팁 렌더링
뷰 위에 툴팁을 측정하고 배치하는 것은 동기식 렌더링이 가능하게 하는 것을 보여줄 수 있는 좋은 예입니다. 툴팁은 자신이 렌더링될 위치를 결정하기 위해 대상 뷰의 위치를 알아야 합니다.
현재 아키텍처에서는 onLayout
을 사용해 뷰의 측정값을 얻고, 뷰의 위치에 따라 툴팁의 위치를 업데이트합니다.
function ViewWithTooltip() {
// ...
// 레이아웃 정보를 얻어 ToolTip에 전달해 위치를 조정
const onLayout = React.useCallback(event => {
targetRef.current?.measureInWindow((x, y, width, height) => {
// 이 상태 업데이트는 동일한 커밋에서 실행된다는 보장이 없음
// 이로 인해 ToolTip이 위치를 재조정할 때 시각적인 "점프"가 발생
setTargetRect({x, y, width, height});
});
}, []);
return (
<>
<View ref={targetRef} onLayout={onLayout}>
<Text>툴팁이 위에 렌더링될 콘텐츠</Text>
</View>
<Tooltip targetRect={targetRect} />
</>
);
}
새로운 아키텍처에서는 useLayoutEffect
를 사용해 동기적으로 측정하고 레이아웃 업데이트를 단일 커밋에서 적용할 수 있습니다. 이로 인해 시각적인 "점프"를 피할 수 있습니다.
function ViewWithTooltip() {
// ...
useLayoutEffect(() => {
// `targetRect`의 측정과 상태 업데이트가 단일 커밋에서 이루어짐
// 이를 통해 ToolTip이 중간 페인트 없이 위치를 조정할 수 있음
targetRef.current?.measureInWindow((x, y, width, height) => {
setTargetRect({x, y, width, height});
});
}, [setTargetRect]);
return (
<>
<View ref={targetRef}>
<Text>툴팁이 위에 렌더링될 콘텐츠</Text>
</View>
<Tooltip targetRect={targetRect} />
</>
);
}
동시성 렌더러 및 기능 지원
새로운 아키텍처는 React 18 이후 출시된 동시성 렌더링 및 기능을 지원합니다. 이제 React Native 코드에서 데이터 페칭을 위한 Suspense, Transitions 및 기타 새로운 React API와 같은 기능을 사용할 수 있어 웹과 네이티브 React 개발 간의 코드베이스와 개념을 더욱 일치시킬 수 있습니다.
동시성 렌더러는 또한 자동 배칭과 같은 즉시 사용 가능한 개선 사항을 제공하여 React에서 리렌더링을 줄입니다.
예제: 자동 배칭
새로운 아키텍처를 사용하면 React 18 렌더러와 함께 자동 배칭을 얻을 수 있습니다.
이 예제에서 슬라이더는 렌더링할 타일 수를 지정합니다. 슬라이더를 0에서 1000으로 드래그하면 빠르게 연속적인 상태 업데이트와 리렌더링이 발생합니다.
동일한 코드에 대한 렌더러를 비교하면, 렌더러가 더 부드러운 UI를 제공하고 중간 UI 업데이트가 적다는 것을 시각적으로 확인할 수 있습니다. 이 네이티브 슬라이더 컴포넌트와 같은 네이티브 이벤트 핸들러의 상태 업데이트는 이제 배칭됩니다.


Transitions과 같은 새로운 동시성 기능은 UI 업데이트의 우선순위를 표현할 수 있는 힘을 제공합니다. 업데이트를 낮은 우선순위로 표시하면 React가 업데이트 렌더링을 "중단"하고 더 높은 우선순위의 업데이트를 처리하여 중요한 부분에서 반응성이 뛰어난 사용자 경험을 보장할 수 있습니다.
예제: startTransition
사용
이전 예제를 기반으로 트랜지션이 진행 중인 렌더링을 중단하고 새로운 상태 업데이트를 처리하는 방법을 보여줄 수 있습니다.
타일 수 상태 업데이트를 startTransition
으로 감싸서 타일 렌더링이 중단될 수 있음을 나타냅니다. startTransition
은 또한 트랜지션이 완료되었을 때 알려주는 isPending
플래그를 제공합니다.
function TileSlider({value, onValueChange}) {
const [isPending, startTransition] = useTransition();
return (
<>
<View>
<Text>
Render {value} Tiles
</Text>
<ActivityIndicator animating={isPending} />
</View>
<Slider
value={1}
minimumValue={1}
maximumValue={1000}
step={1}
onValueChange={newValue => {
startTransition(() => {
onValueChange(newValue);
});
}}
/>
</>
);
}
function ManyTiles() {
const [value, setValue] = useState(1);
const tiles = generateTileViews(value);
return (
<TileSlider onValueChange={setValue} value={value} />
<View>
{tiles}
</View>
)
}
트랜지션에서 빈번한 업데이트가 발생하면 React는 상태가 오래되자마자 렌더링을 중단하므로 중간 상태가 적게 렌더링됩니다. 트랜지션 없이는 더 많은 중간 상태가 렌더링됩니다. 두 예제 모두 자동 배칭을 사용하지만, 트랜지션은 개발자에게 진행 중인 렌더링을 배칭할 수 있는 더 많은 힘을 제공합니다.
빠른 JavaScript/네이티브 인터페이싱
새로운 아키텍처는 JavaScript와 네이티브 사이의 비동기 브릿지를 제거하고 JavaScript 인터페이스(JSI)로 대체합니다. JSI는 JavaScript가 C++ 객체에 대한 참조를 보유할 수 있게 해주는 인터페이스이며, 그 반대도 가능합니다. 메모리 참조를 통해 직렬화 비용 없이 직접 메서드를 호출할 수 있습니다.
JSI는 React Native의 인기 있는 카메라 라이브러리인 VisionCamera가 실시간으로 프레임을 처리할 수 있게 합니다. 일반적인 프레임 버퍼는 10MB 정도이며, 프레임 속도에 따라 초당 약 1GB의 데이터를 처리합니다. 브릿지의 직렬화 비용과 비교할 때, JSI는 이러한 양의 인터페이싱 데이터를 쉽게 처리합니다. JSI는 데이터베이스, 이미지, 오디오 샘플 등과 같은 복잡한 인스턴스 기반 타입도 노출할 수 있습니다.
새로운 아키텍처에서 JSI를 도입함으로써 모든 네이티브-JavaScript 상호작용에서 이러한 직렬화 작업을 제거했습니다. 여기에는 View
와 Text
와 같은 네이티브 코어 컴포넌트의 초기화 및 리렌더링도 포함됩니다. 새로운 아키텍처에서의 렌더링 성능 조사와 측정된 개선된 벤치마크에 대해 더 자세히 읽어볼 수 있습니다.
새로운 아키텍처를 활성화하면 어떤 점을 기대할 수 있나요?
새로운 아키텍처는 여러 기능과 개선 사항을 제공하지만, 앱이나 라이브러리에 새로운 아키텍처를 활성화한다고 해서 바로 성능이나 사용자 경험이 개선되지는 않습니다.
예를 들어, 동기식 레이아웃 효과나 동시성 기능과 같은 새로운 기능을 활용하려면 코드를 리팩토링해야 할 수 있습니다. JSI가 자바스크립트와 네이티브 메모리 간의 오버헤드를 최소화하지만, 데이터 직렬화가 앱 성능의 병목 현상이 아니었을 수도 있습니다.
앱이나 라이브러리에서 새로운 아키텍처를 활성화하는 것은 React Native의 미래를 선택하는 것입니다.
팀은 새로운 아키텍처가 제공하는 새로운 기능을 적극적으로 연구하고 개발 중입니다. 예를 들어, 웹 정렬은 Meta에서 활발히 탐구 중인 영역이며, React Native 오픈소스 생태계에 공개될 예정입니다.
전용 토론 및 제안 저장소에서 진행 상황을 확인하고 기여할 수 있습니다.
새로운 아키텍처를 지금 사용해야 할까요?
0.76 버전부터 모든 React Native 프로젝트에서 새로운 아키텍처가 기본적으로 활성화됩니다.
만약 제대로 동작하지 않는 부분을 발견한다면, 이 템플릿을 사용해 이슈를 열어주세요.
어떤 이유로든 새로운 아키텍처를 사용할 수 없다면, 여전히 이를 사용하지 않도록 선택할 수 있습니다:
Android
android/gradle.properties
파일을 엽니다.newArchEnabled
플래그를true
에서false
로 변경합니다.
# 이 속성을 사용하여 새로운 아키텍처 지원을 활성화합니다.
# 이를 통해 애플리케이션에서 TurboModules와 Fabric 렌더링을 사용할 수 있습니다.
# 커스텀 TurboModules/Fabric 컴포넌트를 작성하거나 이를 제공하는 라이브러리를 사용하려면
# 이 플래그를 활성화해야 합니다.
-newArchEnabled=true
+newArchEnabled=false
iOS
ios/Podfile
파일을 엽니다.- Podfile의 메인 스코프에
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
를 추가합니다. (템플릿의 참조 Podfile)
+ ENV['RCT_NEW_ARCH_ENABLED'] = '0'
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
- 다음 커맨드로 CocoaPods 의존성을 설치합니다:
bundle exec pod install