React Native 문서 정독하기

제이밍·2024년 9월 3일
1
post-thumbnail
post-custom-banner

React Native 문서의 일부 내용을 번역하여 정리한 포스팅입니다.

RN 소개

React Native는 React와 플랫폼의 네이티브 기능을 사용하여 Android 및 iOS 애플리케이션을 구축하는 오픈 소스 프레임워크입니다

React.js + React Native
앱스토어에 실제 배포할 수 있는 앱을 만든다.

목차

Introduction
Core Components and Native Components
Platform-Specific Code
Fast Refresh
Metro
Using Libraries
Debugging
Security
Performance Overview

Introduction

먼저 React Native를 사용하려면 JavaScript의 기본 개념을 이해해야 합니다.

Android 개발에서는 Kotlin 또는 Java로 뷰를 작성하며,
iOS 개발에서는 Swift 또는 Objective-C를 사용함.

React Native를 사용하면, JavaScript로 이러한 뷰를 React 컴포넌트를 사용해 호출할 수 있습니다.
런타임 시 React Native는 해당 컴포넌트에 대한 Android 및 iOS 뷰를 생성합니다.

React Native 아키텍쳐

사진출처 - feconf

크롬에서 자바스크립트 엔진이 <div> 를 생성하는 것과 같은 원리
리액트 네이티브에서는 이를 Hermes 엔진 이라고 부릅니다.

Document.createElement('div')
-> <div></div>

Hermes 엔진의 특징

단 하나의 자바스크립트만 실행 시킬수 있습니다.

일반적으로 브라우저는 파일을 하나로 합쳐야 실행 가능한 경우가 많다!
이러한 딜레마를 해결해 준 것이 바로 번들러

React에 webpack이 있다면 React Native 에는 Metro가 있다!

Metro는 React Native의 파일들을 번들링 하여 개발한 파일들을 하나의 파일로 만드는 작업을 해줍니다.

Metro의 주요 역할

  • Resolution: 요청에 맞춰서 정확하게 파일의 경로를 찾아주는 일
    우리가 크게 신경쓰지 않아도 번들러에서 처리해준다.
resolveRequest: (context, moduleName, platform) => {
  return {
    type: 'sourceFile',
    filePath: /* 정확한 파일의 경로 문자열 */,
  }
}
  • Load: javascript가 아닌것을 javascript로 바꿔줘야 한다.
    (비표준 javascript를 babel, swc 같은 로더 사용)
    • transformPath를 통해서 load지정 가능하다.

React Native의 새 버전을 출시할 때마다 Hermes 버전을 빌드합니다.
이렇게 하면 사용 중인 React Native 버전과 완벽하게 호환되는 Hermes 버전을 사용할 수 있습니다.

과거에는 Hermes 버전과 React Native 버전을 일치시키는 데 문제가 있었습니다.
이를 통해 이 문제가 완전히 해결되고 사용자에게 특정 React Native 버전과 호환되는 JS 엔진이 제공됩니다.

Core Components and Native Components

React Native에는 즉시 사용할 수 있는 필수적인 네이티브 컴포넌트들이 포함되어 있어, 바로 앱을 구축할 수 있습니다. 이러한 컴포넌트들을 React Native의 핵심 컴포넌트(Core Components)라고 부릅니다.

핵심 컴포넌트

React Native에는 컨트롤에서 인터렉티브한 기능까지 다양한 핵심 컴포넌트가 있습니다.

React Native가 컴파일 하여 Native App으로 변환된 코드입니다!

UI 요소들과 다르게 javascript logic은 컴파일 되지 않고 구축한 네이티브앱 안에서 javascript가 스레드로 실행돼요.

Platform-Specific Code

크로스 플랫폼 앱을 개발할 때, 가능한 한 많은 코드를 재사용하고 싶을텐데요,
React Native는 코드를 플랫폼별로 분리하여 구성할 수 있는 방법을 제공합니다!

  1. Platform 모듈 사용

Platform.OS는 iOS에서 실행 중일 때는 ios로, Android에서 실행 중일 때는 android로 설정됩니다

import {Platform, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
  height: Platform.OS === 'ios' ? 200 : 100,
});
  1. Platform.select
    Platform.select 메서드를 사용하여 실행 중인 플랫폼에 가장 적합한 값을 반환할 수 있습니다.
import {Platform, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      ios: {
        backgroundColor: 'red',
      },
      android: {
        backgroundColor: 'green',
      },
      default: {
        // 다른 플랫폼, 예를 들어 웹
        backgroundColor: 'blue',
      },
    }),
  },
});

//플랫폼별 컴포넌트를 반환하도록 사용할 수도 있습니다.
const Component = Platform.select({
  ios: () => require('ComponentIOS'),
  android: () => require('ComponentAndroid'),
})();

<Component />;
  1. 플랫폼별 버전 감지
//Android
import {Platform} from 'react-native';

if (Platform.Version === 25) {
  console.log('Running on Nougat!');
}

// ios
import {Platform} from 'react-native';

const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS <= 9) {
  console.log('Work around a change in behavior');
}

네이티브 전용 확장자

모듈이 Android/iOS 차이점 없이 NodeJS/웹과 React Native 간에 공유되어야 할 경우 .native.js 확장자를 사용할 수 있습니다. 이는 React Native와 ReactJS 간의 공통 코드를 공유하는 프로젝트에 유용합니다.

예를 들어, 프로젝트에 다음과 같은 파일이 있는 경우:

  • Container.js # 웹 번들러에서 선택됨 (webpack)
  • Container.native.js # Android와 iOS 모두에서 React Native 번들러에 의해 선택됨 (metro)

Button.ios.tsx / Button.android.tsx

import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';

const Button = ({ onPress, title }) => {
  return (
    <TouchableOpacity style={styles.button} onPress={onPress}>
      <Text style={styles.text}>{title}</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF', // iOS 스타일
    padding: 10,
    borderRadius: 5,
  },
  text: {
    color: '#FFFFFF',
    fontSize: 18,
  },
});

export default Button;


import Button from './Button'; 
// 플랫폼에 따라 자동으로 .ios.js 또는 .android.js 파일을 가져옴

export default Button;

이 방법을 통해 코드의 재사용성을 높이면서도 플랫폼별로 다른 UI/UX를 제공할 수 있습니다.

Fast Refresh

Fast Refresh는 React Native의 기능으로, 코드 변경 시 거의 즉각적인 피드백을 제공합니다. 기본적으로 활성화되어 있으며, React 컴포넌트의 수정 사항이 1~2초 내에 화면에 반영됩니다.

이 기능은 React Native에서 먼저 도입되었으며, 나중에 React 웹 애플리케이션에서도 사용할 수 있도록 확장되었습니다.

Expo 프로젝트에서 Fast Refresh는 기본적으로 활성화되어 있으며, 코드를 저장할 때마다 앱이 자동으로 변경 사항을 반영합니다

  • 작동 방식

    • 특정 모듈이 수정되면 해당 모듈만 업데이트되고 컴포넌트가 다시 렌더링됩니다.
      만약 React 컴포넌트가 아닌 내용을 내보내는 모듈을 수정하면, 그 모듈과 이를 가져오는 다른 모듈들도 다시 실행됩니다.
  • 에러 처리

    • 구문 오류나 런타임 오류가 발생하면, 오류를 수정한 후 Fast Refresh가 계속해서 세션을 유지하고 업데이트된 코드를 적용합니다.
  • 제한 사항

    • 클래스 컴포넌트에서는 로컬 상태가 유지되지 않으며, 함수형 컴포넌트와 Hooks만 상태를 유지합니다. 고차 컴포넌트(HOC)를 사용하는 경우에도 상태가 리셋될 수 있습니다.

상태를 리셋하고 컴포넌트를 다시 마운트하려면 // @refresh reset 주석을 추가할 수 있습니다. 또한, Fast Refresh는 useState, useRef와 같은 Hooks의 상태를 유지하려고 하지만, useEffect와 같은 의존성을 가진 Hooks는 항상 업데이트됩니다.

  • Fast Refresh와 Hooks
    • 가능할 때마다 Fast Refresh는 편집 간에 컴포넌트의 상태를 유지하려고 합니다. 특히, useState와 useRef는 인수나 Hook 호출 순서를 변경하지 않는 한 이전 값을 유지합니다.
    • 의존성을 가진 Hooks (useEffect, useMemo, useCallback)는 Fast Refresh 동안 항상 업데이트됩니다. Fast Refresh가 실행되는 동안에는 의존성 목록이 무시됩니다.

예를 들어, useMemo(() => x * 2, [x])를 useMemo(() => x * 10, [x])로 수정하면, x가 변경되지 않았더라도 다시 실행됩니다.
만약 React가 그렇게 하지 않았다면, 수정 사항이 화면에 반영되지 않았을 것입니다!

때로는 예상치 못한 결과가 발생할 수 있습니다. 예를 들어, 의존성이 없는 useEffect도 Fast Refresh 중에 한 번 다시 실행될 수 있습니다.
그러나 useEffect가 가끔 다시 실행되는 상황에 견딜 수 있도록 코드를 작성하는 것은 Fast Refresh 없이도 좋은 습관입니다.

이는 나중에 새로운 의존성을 추가할 때 더 쉽게 만들어줍니다.

Metro

개발서버 부터 프로덕션에 배포 할때까지 사용하는 도구

Metro는 React Native에서 JavaScript 코드와 자산을 빌드하는 도구입니다.
프로젝트의 metro.config.js 파일에서 Metro 설정을 커스터마이징할 수 있습니다.

Hot Module Replacement (HMR): Metro는 코드의 특정 부분이 변경될 때, 전체 애플리케이션을 다시 로드하지 않고 변경된 부분만 즉시 업데이트하는 기능을 지원합니다.
앞서 언급한 Fast Refresh 기능은 Metro에 의해 지원됩니다.

Metro는 대규모 자바스크립트 프로젝트를 효율적으로 번들링하는 데 최적화되어 있습니다. 리액트 네이티브의 특성상 수백 개의 파일이 하나의 앱으로 묶이게 되는데, Metro는 이를 빠르게 처리합니다.

  1. Metro의 내부 기본 설정 위에 병합될 객체(권장).
  2. Metro의 내부 기본 설정을 인수로 받아 최종 설정 객체를 반환하는 함수.

Metro의 동작 원리

  • 모듈 해석 - Metro는 애플리케이션의 진입점(entry point) 파일(index.js 등)을 시작으로, 모든 파일과 의존성을 분석합니다.
  • 코드 변환 - 필요한 모든 파일을 찾은 후, Metro는 Babel과 같은 트랜스파일러를 사용해 최신 자바스크립트 문법과 JSX를 변환합니다.
  • 번들 생성 - 모든 의존성을 분석하고 변환이 완료된 파일들을 하나의 큰 번들 파일로 묶습니다. 이 번들 파일은 애플리케이션이 실제로 실행되는 시점에 로드됩니다.
  • 소스맵 생성 - 디버깅을 용이하게 하기 위해 Metro는 소스맵을 생성합니다. 소스맵은 번들된 코드와 원본 코드를 매핑해 주어 디버깅 시에 원본 코드를 쉽게 확인할 수 있게 해줍니다.

Using Libraries

React Native는 기본 제공되는 컴포넌트 외에도 커뮤니티에서 제공하는 다양한 라이브러리를 사용할 수 있습니다.

라이브러리는 npm이나 Yarn과 같은 패키지 관리자를 통해 설치할 수 있습니다.

  • iOS와 Android에서 네이티브 코드를 연결하려면 각각 CocoaPods와 Gradle을 사용해야 하며, 설치 후에는 앱 바이너리를 다시 빌드해야 합니다.
  • 라이브러리를 찾기 위해서는 React Native Directory나 npm 레지스트리를 사용할 수 있습니다. 호환성을 확인하는 것이 중요합니다.
  • 설치할 때는 라이브러리가 React Native에서 작동하는지, 지원하는 플랫폼과 버전이 맞는지 확인해야 합니다.

Debugging

React Native에서는 여러 디버깅 옵션을 제공하는 앱 내 개발자 메뉴(Dev Menu)를 사용할 수 있습니다. 이 메뉴에 접근하는 방법은 다음과 같습니다

iOS 시뮬레이터: Cmd ⌘ + D (또는 Device > Shake)
Android 에뮬레이터: Cmd ⌘ + M (macOS) 또는 Ctrl + M (Windo
ws 및 Linux)
Android 장치 및 에뮬레이터: 터미널에서 adb shell input keyevent 82 명령어 실행

Expo 디버거 사용하기

  1. In a Chrome browser window, navigate to chrome://inspect.
  2. Use the "Configure..." button to add the dev server address (typically localhost:8081).
  3. You should now see a "Hermes React Native" target with an "inspect" link. Click this to open the debugger.

React DevTools 사용

React DevTools를 사용해 React 요소 트리, props, state 등을 확인할 수 있습니다.

npx react-devtools

Performance Monitor

Android와 iOS에서는 개발자 메뉴에서 "Perf Monitor"를 선택해 성능 오버레이를 토글할 수 있습니다. 이 기능은 가이드용으로 사용되며, 정확한 성능 측정을 위해서는 Android Studio와 Xcode의 네이티브 도구를 사용하는 것을 권장합니다.

Security

애플리케이션을 여러가지 보안 사고로부터 보호하기 위해 얼마나 많은 노력을 기울이느냐에 따라 악의적인 공격의 피해를 입거나 보안 취약점이 드러날 가능성은 반비례합니다. 보통 자물쇠는 뚫릴 수 있지만, 캐비닛 훅보다는 훨씬 뚫기 어렵습니다!

이 가이드에서는 민감한 정보 저장, 인증, 네트워크 보안 및 애플리케이션 보안을 강화하는 데 도움이 되는 도구에 대해 배울 것입니다.

민감한 정보 저장

앱 코드에 민감한 API 키를 절대 저장하지 마세요.

앱에서 리소스에 접근하기 위해 API 키나 비밀이 반드시 필요하다면, 가장 안전한 방법은 앱과 리소스 사이에 조정 계층을 구축하는 것입니다.
ex) 서버리스 함수(AWS Lambda나 Google Cloud Functions를 사용)

Async Storage

Async Storage는 React Native에서 비동기적이고 암호화되지 않은 키-값 저장소를 제공하는 커뮤니티 유지 모듈입니다. Async Storage는 앱 간에 공유되지 않으며, 각 앱은 자체 샌드박스 환경을 가지고 있어 다른 앱의 데이터에 접근할 수 없습니다.

  • 사용해야 할 경우: 앱 실행 간 비민감 데이터를 유지하기 위해, Redux 상태 저장, GraphQL 상태 저장 등.
  • 사용하지 말아야 할 경우: 토큰 저장, 비밀 정보 저장.

보안 저장소

iOS - Keychain Services를 사용하면 사용자를 위해 민감한 정보를 안전하게 저장할 수 있습니다. 이는 인증서, 토큰, 비밀번호 등 민감한 정보를 저장하기에 이상적입니다.

Android
1. Secure Shared Preferences 안드로이드의 지속적인 키-값 데이터 저장소입니다. Shared Preferences의 데이터는 기본적으로 암호화되지 않지만, Encrypted Shared Preferences는 Shared Preferences 클래스를 감싸 안드로이드에서 키와 값을 자동으로 암호화합니다.
2. Keystore 시스템을 사용하면 암호화 키를 컨테이너에 저장하여 장치에서 추출하기 어렵게 만듭니다.

인증 및 딥 링크

딥 링크는 외부 소스에서 네이티브 애플리케이션으로 데이터를 직접 보내는 방법입니다.

app://처럼 생겼으며, app은 앱의 스킴이고 // 뒤에 오는 것은 내부적으로 요청을 처리하는 데 사용될 수 있습니다.

예를 들어, 이커머스 앱을 구축하고 있다면 app://products/1 을 사용해 딥 링크로 앱을 열고 ID가 1인 제품 상세 페이지로 이동할 수 있습니다.

===
딥 링크는 안전하지 않으며, 민감한 정보를 절대 보내서는 안 됩니다.

딥 링크가 안전하지 않은 이유는 URL 스킴을 등록하는 중앙화된 방법이 없기 때문입니다. 애플리케이션 개발자는 iOS에서는 Xcode에서, 안드로이드에서는 인텐트를 추가하여 거의 모든 URL 스킴을 선택할 수 있습니다.
-> Apple은 후속 iOS 버전(iOS 11)에서 이를 해결하기 위해 '선착순' 원칙을 도입했지만, 이 취약점은 여전히 다른 방식으로 악용될 수 있습니다. 이 문제에 대한 자세한 내용은 여기를 참조하세요. iOS에서 안전하게 콘텐츠를 앱 내로 링크하기 위해서는 유니버설 링크를 사용하세요.

OAuth2와 리디렉션

OAuth2 인증 프로토콜은 현재 매우 인기가 많으며, 가장 완전하고 안전한 프로토콜로 자랑됩니다.

OAuth2에서는 사용자가 제3자를 통해 인증을 받도록 요청합니다. 인증이 완료되면 이 제3자는 요청한 애플리케이션으로 검증 코드를 리디렉션하며, 이 코드는 JWT(제이슨 웹 토큰)로 교환될 수 있습니다. JWT는 웹에서 정보를 안전하게 전송하기 위한 개방형 표준입니다.

PKCE(Pixy)는 Proof of Key Code Exchange의 약자이며 OAuth 2 스펙의 확장판입니다. 이는 인증과 토큰 교환 요청이 동일한 클라이언트에서 발생했음을 확인하는 추가적인 보안 계층을 추가하는 방법입니다.

  • code_verifier: 클라이언트에서 생성된 큰 무작위 문자열
  • code_challenge: code_verifier의 SHA 256 해시 값

요약

보안을 처리하는 데 있어 완벽한 방법은 없습니다. 그러나 의식적인 노력과 성실함으로 애플리케이션에서 보안 침해 가능성을 크게 줄일 수 있습니다. 애플리케이션에 저장된 데이터의 민감도, 사용자 수, 해커가 계정에 접근할 경우 초래할 수 있는 피해에 비례해 보안에 투자하세요. 그리고 기억하세요: 요청하지 않은 정보는 접근하기 훨씬 어렵습니다.

성능 Performance Overview

React Native를 사용하여 WebView 기반 도구 대신 선택하는 주요 이유는 초당 60 프레임을 달성하고 네이티브 같은 느낌의 앱을 제공하기 위해서입니다.

JS frame rate (JavaScript thread)

JavaScript 스레드가 한 프레임 동안 반응하지 않으면 프레임이 드롭되며, 예를 들어 복잡한 애플리케이션의 루트 컴포넌트에서 this.setState를 호출하여 많은 하위 트리 컴포넌트를 다시 렌더링하면 200ms가 걸리고 12프레임이 드롭될 수 있습니다.

이로 인해 JavaScript에 의해 제어되는 애니메이션이 그 동안 멈춘 것처럼 보일 수 있습니다. 100ms 이상 걸리는 작업이 있다면 사용자가 느끼는 수준입니다!

JSFrame 속도가 중요한 이유

JSFrame 속도가 떨어지면 애플리케이션에서 다음과 같은 문제가 발생할 수 있습니다

  • 애니메이션 끊김: 애니메이션이 자연스럽지 않고 끊겨 보일 수 있습니다.
  • 느린 터치 반응: 터치 이벤트에 대한 반응이 느려지거나 지연될 수 있습니다.
  • UI 렌더링 지연: UI 업데이트가 지연되어 화면에 변경 사항이 늦게 반영될 수 있습니다.

JSFrame 속도가 낮아지는 원인

  • 복잡한 계산: 자바스크립트 스레드에서 복잡한 계산을 수행하는 경우, 예를 들어 대량의 데이터를 처리하거나 무거운 로직을 실행하는 경우, JSFrame 속도가 떨어질 수 있습니다.
  • 대규모 렌더링 작업: 자바스크립트 스레드가 많은 컴포넌트를 한 번에 렌더링해야 하는 경우, 예를 들어 복잡한 UI나 대규모 리스트를 처리할 때 JSFrame이 느려질 수 있습니다.
  • 비동기 작업: API 호출 등 비동기 작업이 완료된 후, 그 결과를 처리하는 로직이 자바스크립트 스레드에 큰 부하를 줄 수 있습니다.

JSFrame 성능 최적화 방법

  • InteractionManager 사용: 긴 작업을 애니메이션이나 사용자 인터랙션 후로 연기하여 JSFrame 속도를 유지할 수 있습니다.
import { InteractionManager } from 'react-native';

// 버튼 클릭 핸들러
const handleButtonClick = () => {
  // 긴 작업을 예약
  InteractionManager.runAfterInteractions(() => {
    // 긴 작업 실행
    performLongTask();
  });
};

// 긴 작업 함수
const performLongTask = () => {
  // 시간이 많이 걸리는 작업
  console.log('긴 작업 실행 중...');
  // 예시: API 호출, 대량 데이터 처리 등
};
  • useMemo와 useCallback 사용: 계산량이 많은 작업을 메모이제이션하여 불필요한 재계산을 방지합니다. shouldComponentUpdate 및 React.PureComponent 사용)
  • requestAnimationFrame 사용: 애니메이션이나 화면 업데이트 작업을 UI 스레드와 동기화하여 부드러운 애니메이션을 구현합니다.
import React, { useState, useEffect } from 'react';
import { View, Button } from 'react-native';

const SimpleAnimation = () => {
  const [position, setPosition] = useState(0);

  const startAnimation = () => {
    const animate = () => {
      setPosition(prevPosition => {
        const newPosition = prevPosition + 5;
        if (newPosition > 200) return 0; // 다시 처음으로
        requestAnimationFrame(animate);
        return newPosition;
      });
    };
    requestAnimationFrame(animate);
  };

  return (
    <View style={{ padding: 20 }}>
      <Button title="Start Animation" onPress={startAnimation} />
      <View
        style={{
          width: 50,
          height: 50,
          backgroundColor: 'blue',
          marginTop: 20,
          transform: [{ translateX: position }],
        }}
      />
    </View>
  );
};

export default SimpleAnimation;
  • 배치된 업데이트 사용: 여러 상태 업데이트를 한 번에 처리하도록 배치하여 렌더링 성능을 향상시킵니다.
    v0.74
    onLayout 콜백의 상태 업데이트가 이제 일괄 처리됩니다. 이전에는 onLayout 이벤트의 각 상태 업데이트로 인해 새로운 렌더링 발생했습니다.
function MyComponent(props) {
  const [state1, setState1] = useState(false);
  const [state2, setState2] = useState(false);

  return (
    <View>
      <View
        onLayout={() => {
          setState1(true);
       }}>
      <View
         onLayout={() => {
          // When this event is executed, state1's new value is no longer observable here.
          setState2(true);
        }}>
      </View>
    </View>
  );
}
  • 최적화된 리스트 컴포넌트 사용: FlatList나 SectionList와 같은 최적화된 리스트 컴포넌트를 사용하여 대규모 리스트의 성능을 개선합니다.
import React, {memo} from 'react';
import {View, Text} from 'react-native';

const MyListItem = memo(
  ({title}: {title: string}) => (
    <View>
      <Text>{title}</Text>
    </View>
  ),
  (prevProps, nextProps) => {
    return prevProps.title === nextProps.title;
  },
);

export default MyListItem;


const renderItem = useCallback(({item}) => (
   <View key={item.key}>
      <Text>{item.title}</Text>
   </View>
 ), []);

return (
  // ...

  <FlatList data={items} renderItem={renderItem} />;
  // ...
);

UI frame rate (main thread)

많은 사람들이 NavigatorIOS의 성능이 Navigator보다 더 좋은 이유는 애니메이션이 메인 스레드에서 전적으로 실행되기 때문입니다. 따라서 JavaScript 스레드의 프레임 드롭이 애니메이션에 영향을 미치지 않습니다. 이와 유사하게 ScrollView는 JavaScript 스레드가 잠겨 있는 동안에도 스크롤이 가능하며, 이는 ScrollView가 메인 스레드에서 실행되기 때문입니다.

성능 문제의 일반적인 원인

개발 모드에서 실행 중일 때 (dev=true): 개발 모드에서는 JavaScript 스레드 성능이 크게 저하됩니다. 더 많은 작업이 런타임에서 이루어지기 때문에, 성능 테스트는 항상 릴리스 빌드에서 수행해야 합니다.

  • 복잡한 레이아웃 및 뷰 계층 구조: 복잡한 레이아웃이나 중첩된 뷰 계층 구조는 UI 스레드가 모든 요소를 렌더링하는 데 더 많은 시간을 소요하게 만듭니다.
  • 이미지 처리: 고해상도 이미지나 애니메이션 이미지의 렌더링은 많은 리소스를 소모하며, 특히 이미지 크기나 위치를 변경할 때 성능 저하가 발생할 수 있습니다.
  • 비효율적인 애니메이션: 자바스크립트 스레드에서 처리되는 애니메이션이 너무 무겁거나, 애니메이션이 UI 스레드에 과부하를 주는 경우, UIFrame 속도가 떨어질 수 있습니다.
  • 알파 블렌딩(Alpha Blending): 투명한 배경을 가진 텍스트나 뷰를 이미지 위에 오버레이할 때, 알파 블렌딩이 필요한 경우 성능이 저하될 수 있습니다.

UIFrame 성능 개선 방법

  • 레이아웃 단순화: 뷰 계층 구조를 간소화하고, 중첩된 뷰를 최소화하여 UI 스레드의 작업량을 줄입니다. Flexbox 레이아웃을 과도하게 사용하지 않도록 주의하세요.
as-isto-be
  • 이미지 최적화
    1. 이미지 크기 줄이기: 필요한 해상도에 맞는 이미지 크기를 사용하여 메모리 사용량을 줄입니다.

    2. resizeMode 최적화: 이미지의 resizeMode 속성을 적절히 사용하여 성능을 개선할 수 있습니다. 예를 들어, resizeMode: 'cover' 대신 resizeMode: 'contain'을 사용할 수 있습니다.

    3. shouldRasterizeIOS 및 renderToHardwareTextureAndroid 사용: 이미지가 반복적으로 움직이지 않을 경우, 이미지를 하드웨어에 렌더링하도록 하여 성능을 향상시킬 수 있습니다. 단, 이 속성들을 과도하게 사용하면 메모리 사용량이 증가할 수 있으므로 주의가 필요합니다.

      	<View renderToHardwareTextureAndroid=true />
      	<View shouldRasterizeIOS=true />
  1. 네이티브 애니메이션 사용:
    useNativeDriver: true를 설정하여 애니메이션을 네이티브 스레드에서 처리하도록 하여 자바스크립트 스레드에 과부하를 줄일 수 있습니다.
 Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true, // 네이티브 드라이버 사용
    }).start();

동작 원리
애니메이션이 GPU에서 직접 처리되므로, 복잡한 애니메이션이나 반복적인 애니메이션이 더 효율적으로 실행됩니다.
LayoutAnimation 사용: 네이티브 애니메이션을 활용하여 레이아웃 변경 시 발생하는 성능 저하를 방지합니다.

  • 알파 블렌딩 발생 피하기
    투명도 및 그림자 최적화: 투명한 배경이나 그림자는 성능에 부정적인 영향을 줄 수 있습니다. 필요하지 않은 경우, 투명도 사용을 줄이거나 그림자를 제거하는 것이 좋습니다.
    -> 투명도를 처리하는 과정에서 픽셀 계산이 더 복잡해지고, GPU에 더 많은 부하를 줄 수 있습니다.
const styles = StyleSheet.create({
  overlay: {
    width: 300,
    height: 300,
    backgroundColor: 'rgba(0, 0, 0, 0.5)', // 투명 배경
    justifyContent: 'center',
    alignItems: 'center',
    shouldRasterizeIOS: true, // iOS 성능 최적화
    renderToHardwareTextureAndroid: true, // Android 성능 최적화
  },
});
  • 프로파일링 도구 사용:
    React Native의 Performance Monitor를 사용하여 UI 스레드의 성능을 모니터링하고, 병목 현상이 발생하는 부분을 찾아서 최적화할 수 있습니다.

성능 요약

React Native에서 부드러운 성능을 유지하기 위해서는 JS 스레드와 UI 스레드의 역할을 이해하고, 불필요한 재렌더링과 부하를 줄이는 것이 중요합니다.

Timers

InteractionManager

잘 구축된 네이티브 앱이 부드럽게 느껴지는 이유 중 하나는 상호작용 및 애니메이션 중에 비용이 많이 드는 작업을 피하기 때문입니다. React Native에서는 단일 자바스크립트 실행 스레드만 사용할 수 있는 제한이 있지만, InteractionManager를 사용하여 긴 작업이 모든 상호작용/애니메이션이 완료된 후에 시작되도록 할 수 있습니다.

애플리케이션은 다음과 같이 상호작용이 끝난 후 작업을 예약할 수 있습니다:

InteractionManager.runAfterInteractions(() => {
  // ...긴 동기 작업...
});

스케줄링 종류

requestAnimationFrame(): 시간이 지남에 따라 뷰를 애니메이션하는 코드를 실행합니다.
setImmediate/setTimeout/setInterval(): 나중에 코드를 실행하지만, 이는 애니메이션을 지연시킬 수 있습니다.
runAfterInteractions(): 나중에 코드를 실행하지만, 활성 애니메이션을 지연시키지 않습니다.
터치 처리 시스템은 하나 이상의 활성 터치를 '상호작용'으로 간주하며, 모든 터치가 끝나거나 취소될 때까지 runAfterInteractions() 콜백을 지연시킵니다.

InteractionManager는 또한 애플리케이션이 애니메이션을 등록하고 애니메이션이 시작될 때 '핸들'을 생성하고, 애니메이션이 완료되면 이를 지울 수 있게 합니다:

const handle = InteractionManager.createInteractionHandle();
// 애니메이션 실행 중... (`runAfterInteractions` 작업은 대기열에 추가됨)
// 나중에 애니메이션 완료 시:
InteractionManager.clearInteractionHandle(handle);
// 모든 핸들이 지워지면 대기 중인 작업이 실행됩니다.

Reference

Introduction
Core Components and Native Components
Platform-Specific Code
Fast Refresh
Metro
Using Libraries
Debugging
Security
Performance Overview

profile
모르는것은 그때그때 기록하기
post-custom-banner

1개의 댓글

comment-user-thumbnail
2024년 11월 8일

퍼포먼스 관련한 부분이 fabric에서도 아직 유효한건가요?

답글 달기