React Native Development Docs - Guide

MM·2025년 5월 20일
0

ReactNative

목록 보기
2/4
post-thumbnail

이미 알고 있는 내용은 넘어갑니다.

웹에 div가 있다면 모바일에는 view가 있다

완전히 동일한 개념은 아니지만, 대략 저런 느낌으로 이해하면 될듯.

공식에서 제공하는 리액트 네이티브 커스텀 모듈 사이트가 있다.
대충 훑어보니 firebase, google 연동 등도 이미 구현해둔 게 있는듯?
가져다 쓰자.

왜 이렇게 세분화된걸까

웹에서도 용도에 따라 다른 태그를 사용하도록 시멘틱 태그가 권장되어 있고 내부 성능도 조금씩 다르긴 하지만, 앱은 그 세분화 정도가 좀더 심하다고 생각이 들었다.
네이티브가 아니라 브리지라서 그런가?

스크롤뷰와 플랫리스트만 해도 아래와 같다.

항목ScrollViewFlatList
렌더링 방식모든 아이템을 한 번에 렌더링보이는 영역만 렌더링 (레이지 로딩)
레이지 로딩❌ 없음✅ 있음
성능 (아이템 많을 때)❌ 성능 저하, 메모리 사용 많음✅ 성능 최적화, 메모리 효율적
사용 방식<ScrollView> children </ScrollView>data, renderItem props를 통해 사용
키 속성자동 적용 안 됨 (직접 설정 필요)keyExtractor 또는 key 필드 자동 처리 가능
추가 기능기본 스크롤만 제공무한 스크롤, pull-to-refresh 등 내장 기능 지원
사용 용도적은 양의 고정 콘텐츠 (예: 설정 화면)많은 양의 데이터 리스트 (예: 게시판, 피드 등)



메트로 번들러

포트 8081에서 실행된다. 비워 놓자.

경량화 번들러

웹팩보다 가볍고, 기능도 적다.
-> 트리셰이킹도 안하고 코드 스플리팅도 안 함.

commonjs 기반

.mp3, .wav, .mp4, .mov, .html 및 .pdf 전부 모듈처럼 require로 처리된다고.

모바일 앱 특화

이미지 번들링

  • 디바이스 해상도에 따라 @2x, @3x 이미지 자동 매칭
  • 이미지의 URI 경로와 메타데이터를 JS 객체로 변환
  • 실제 사용되는 이미지만 패키징됨

-> Webpack은 이미지 URL을 만들어주거나 CDN에 올리지만, React Native는 로컬에 포함해서 앱 설치 시 같이 번들링됨.

즉, 웹에서는 이미지 최적화를 통해 초기 로딩 속도를 줄였다면 앱에서는 이미지 최적화가 초기 로딩 속도에 영향을 주지 않는다는 말이군요

앱에서 이미지 캐싱을 안 한다??

로컬 이미지(require)가 아니라 url로 불러오는 경우는 네트워크 요청으로 인식되어 디스크에 캐싱 안 된다고...
react-native-fast-image같은 외부라이브러리를 쓰거나, Image.prefetch() 로 사전 페칭해서 네이티브 캐싱 전략에 올라타야한다.

네이티브 브리지

플러터에서는 호출브리지를 앱 개발자와 상의해서 하나하나 뚫었어야 했는데,
리액트 네이티브는 그냥 제공을 해준다. 야호.
-> 다만 그만큼 자율도는 낮아질듯...

소스맵 제공

에러 로그에서 원래 소스 코드 라인으로 매핑하는 것.
디버깅 서드파티와도 연동된다! (센트리 같은..)




리액트 네이티브의 특징

Platform-Specific Code

플랫폼 모듈

리액트 네이티브는 실행 중인 플랫폼이 안드로이드인지 ios인지 구분할 수 있다.
-> window 대신 Platform을 쓰는 느낌인가?

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

const styles = StyleSheet.create({
  height: Platform.OS === 'ios' ? 200 : 100,
}); //'ios' | 'android' | 'native' | 'default'

혹은 아예 이렇게 플랫폼에 따라 다른 파일을 가져다 쓰게 할 수도 있다고!

BigButton.ios.js
BigButton.android.js

그냥 import로 가져다 쓰면 React Native는 실행 중인 플랫폼에 따라 올바른 파일을 자동으로 선택해준다! 일일이 분기문 넣을 필요가 없다!

참고로 이렇게 웹/모바일로 나눌 수도 있다. 웹팩은 위에 걸 선택하고, 메트로는 아래 걸 선택해서 자동으로 알아서 나눠줌. 편하다.

Container.js
Container.native.js 

무엇보다 이렇게 확장자를 나눠 놓으면, 각 플랫폼별로 번들링할때 특정 확장자는 무시하도록 하게 해서 번들 크기를 줄일 수 있다고.
-> 나무를 내가 직접 흔들어야 하는군요...



다양한 플랫폼 지원

tv도 지원된다!

TV 장치 지원은 기존 React Native 애플리케이션이 Apple TV 및 Android TV에서 작동하도록 하기 위해 구현되었으며 애플리케이션의 JavaScript 코드를 거의 또는 전혀 변경할 필요가 없습니다.

tv가 없어서 쓸 것 같진 않지만..
어쨌던 애플 비전도 지원하고, macOS, window도 지원한다고 한다.

일렉트론과 리액트 네이티브의 차이

일렉트론은 크로니움 엔진을 자체적으로 돌리고, 리액트 네이티브는 어디까지나 브리지다!!

구분React Native (Windows/macOS)Electron
기본 구조네이티브 UI 컴포넌트와 API 직접 호출Chromium + Node.js
UI 렌더링네이티브 위젯 사용웹뷰 내 렌더링(무겁다!)
네이티브 API 접근성직접 네이티브 API 접근 및 제어 가능Node.js API 통한 제한적 접근
생태계성장 중매우 활발
개발 생산성네이티브 코드 병행 필요, 학습 곡선 존재웹 개발자 친숙, 빠른 프로토타이핑



리액트 네이티브 css

nativewind

요즘 nextjs때문에 리액트에서 tailwind가 핫하듯, 리액트 네이티브에서는 nativewind라는 걸 많이 쓴다고 한다.

Image

// BAD
const icon = this.props.active
  ? 'my-icon-active'
  : 'my-icon-inactive';
<Image source={require('./' + icon + '.png')} />;

// GOOD
const icon = this.props.active
  ? require('./my-icon-active.png')
  : require('./my-icon-inactive.png');
<Image source={icon} />;

이미지 동적 크기

이미지를 flex로 동적으로 확대/축소해야 하는 경우 style 속성을 수동으로 설정해야 {width: undefined, height: undefined} 함!

-> 참고로, url로 가져오는 경우는 width, height를 명확하게 스타일에 명시해주어야 함.

data 인코딩 이미지

이미지에서 직접 uri를 가져다 박을수도 있다.
-> 다만 작은 이미지에서만 사용하자..

<Image
  style={{
    width: 51,
    height: 51,
    resizeMode: 'contain',
  }}
  source={{
    uri: 'data:image/png;base64,iVBORw0KGgoAAA..',
  }}
/>

이미지 캐시 컨트롤

  • default: 네이티브 플랫폼 기본 캐시 전략
  • reload: URL의 데이터가 원래 소스에서 로드 (캐시x)
  • force-cache: 무조건 캐시 사용. 없으면 reload
  • only-if-cached: 무조건 캐시 사용. 없으면 실패 처리
<Image
  source={{
    uri: 'https://reactjs.org/logo-og.png',
    cache: 'only-if-cached',
  }}
  style={{width: 400, height: 400}}
/>

ios에서는 이미지 캐시 제한을 재정의할 수도 있다.
기본 AppDelegate 코드 내에서 호출하라는데 아마 일렉트론의 메인같은 부분이겠지요?

RCTSetImageCacheLimits(4*1024*1024, 200*1024*1024);

ios 카메라 이미지

iOS는 카메라 롤에 동일한 이미지에 대해 여러 크기를 저장하므로..
-> 현재 화면도 정확히 일치하는 항목이 있으면 그것 선택
-> 없으면 최소 50% 더 큰 항목 씀
-> 리액트 네이티브가 자동으로 해주니까 내가 뭘 할 필요는 없다!

Off-thread Decoding

리액트 네이티브는 이미지 디코딩을 메인 스레드가 아닌 별도 스레드에서 처리한다!
-> 즉, 이미지가 커도 메인프레임 렌더링을 방해하지 않음!



영상

이미지가 아니면 크기 정보가 전달되지 않는다!

flexGrow 대신 절대위치를 써야함.
-> 단, 네이티브 코드에 직접 연결된 영상은 상관없다고.




애니메이션

Tracking gestures

제스처와 애니메이션 값을 매핑할 수 있다!

import {
  ...,
  Animated,
  useWindowDimensions,
  useAnimatedValue,
} from 'react-native';


const App = () => {
  const scrollX = useAnimatedValue(0);
  const {width: windowWidth} = useWindowDimensions();

  return (
    <SafeAreaProvider>
      <SafeAreaView style={styles.container}>
        <View style={styles.scrollContainer}>
          <ScrollView
            horizontal={true}
            pagingEnabled
            showsHorizontalScrollIndicator={false}
            onScroll={Animated.event([
              {
                nativeEvent: {
                  contentOffset: {
                    x: scrollX,
                  },
               ...

애니메이션에 네이티브 드라이버 사용하기

useNavieDriver 옵션을 true로 줘서 쓸 수 있다

네이티브 드라이버를 사용하지 않으면..

js에서 애니메이션이 실행되어 매 프레임 결과를 네이티브에 전달
-> 성능 저하!

네이티브 드라이버를 사용하면..

애니메이션을 시작하기 전에 애니메이션을 네이티브로 전송!
-> 브리지를 거치지 않고 애니메이션 수행 = 메인 스레드와 별개로 애니메이션

  • 리페인팅 요소(transform, opacity)에서는 네이티브 드라이버 작동
    • 색 관련 요소는 GPU 계층이 아닌 CPU 계층에서 그려지므로 애니메이션 안됨.
  • 리플로우 요소(position, flexbox)에서는 네이티브 드라이버 작동하지 않음
    • 리플로우되면 js 메인스레드에서 재계산해야하기 때문에..!
  • 버블링이나 캡쳐링되는 이벤트에서도 작동하지 않음!

LayoutAnimation

레이아웃 변경에 사용되는 애니메이션
-> 레이아웃에 영향 주는 리플로우 변경을 자동 추적함!
-> 코어 애니메이션을 활용한다!

코어 애니메이션

iOS의 시스템 레벨에서 제공하는 애니메이션 프레임워크
-> JS 스레드 및 메인 스레드 프레임 드롭의 영향을 받지 않는다!

안드로이드에서 레이아웃 애니메이션 쓰기

UIManager.setLayoutAnimationEnabledExperimental(true);

애니메이션 최적화

프레임이 떨어지는 애니메이션(초당 60프레임 미만으로 수행)에서 할 수 있는 최적화 사항

  • setNativeProps
  • shouldComponentUpdate
  • useNativeDriver로 네이티브 드라이버 사용하기
  • InteractionManager를 사용하여 애니메이션이 완료될 때까지 무거운 작업 연기하기


대표적인 스타일 관련 버그

  • 안드로이드에서 음수 여백이 지원 안 됨. (어차피 안쓴다.)
  • 안드로이드에서 rotateY, rotateX 사용시 안 될수 있음.
  • 부모 영역을 넘어서서 터치 지원 안 됨. (당연한 거 아닌가??)
  • iOS의 이미지 구성 요소에서 아래 요소 무시됨
    • borderTopLeftRadius
    • borderTopRightRadius
    • borderBottomLeftRadius
    • borderBottomRightRadius



제스처 응답기 시스템

제스처의 수명 주기 관리

공식 권장사항

앱의 느낌을 좋게 하려면 모든 작업에 다음과 같은 속성이 있어야 한다!

  • 피드백/강조 표시
    • 사용자는 터치 가능 항목을 알 수 있어야 함
    • 사용자는 제스처를 해제하면 어떤 일이 발생하는지 알 수 있어야 함
  • 취소 기능
    • 사용자는 제스처 중간에 작업을 중단할 수 있어야 함

Responder Lifecycle

리액트에서 쓰는 거랑 별반 다르지 않음.
다만 흥미로워 보이는 게 몇 개 있어서 추가해본다.

속성명설명예시
changedTouches이번 이벤트에서 바뀐 터치들의 배열멀티 터치 중, 하나의 손가락만 움직였을 경우 → 해당 손가락 정보만 들어 있음
identifier터치 고유 ID멀티 터치 시 각 손가락 구별
target이벤트를 발생시킨 뷰 ID이벤트 발생 요소 확인
locationX터치한 요소 기준의 X 좌표버튼의 왼쪽에서 20px 떨어진 곳을 눌렀다면 locationX = 20
pageX전체 화면 기준의 X 좌표스크롤 내린 상태에서 눌렀다면 pageX는 더 커짐
timestamp이벤트 발생 시각 (ms)터치 이동 속도 계산
touches현재 화면에 모든 활성화된 터치들의 배열2개의 손가락으로 터치하고 있다면 touches.length === 2

Capture ShouldSet Handlers

ShouldSet 응답기들은 버블링되어 가장 깊은 노드에서 자동 호출!
-> 중간에서 호출하고 싶다면..
-> 끝까지 버블링되기 전에 ShouldSetResponderCapture를 실행하면 됨.




네트워킹

네이티브 앱에는 CORS의 개념이 없다!!

AST(앱 보안 전송)

기본적으로 iOS 9.0 이상은 ATS(앱 전송 보안)를 적용-> https만 가능!

  • http를 쓰려면 ATS 예외에 추가하기
  • Apple App Store 제출시 ATS를 비활성화하기 위한 합당한 사유가 필요
  • Android에서는 API 레벨 28부터 일반 텍스트 트래픽도 기본적으로 차단
    • 앱 매니페스트 파일에서 android:usesCleartextTraffic을 재정의해 풀 수는 있음!

잘 알려진 버그

  • Android에서 동일한 이름의 헤더를 사용하면 최신 헤더만 표시
  • 쿠키 기반 인증은 현재 불안정함!
    • 리액트 네이티브에서는 쿠키가 아니라 앱 내부 저장소에 토큰을 저장
    • 브라우저가 아니기 때문에 요청에 자동 포함되지도 않음~ 쓸 이유가 없다



보안

env도 코드에 포함된다

키 넣어둘 곳이 없는 서버리스는 어떡하죠

배포할때.. 방법을.. 찾아봅시다...

asnyc storage

암호화되지 않은 비동기 키-값 저장소를 제공하는 React Native용 커뮤니티 유지 관리 모듈
-> 앱별 샌드박스 존재 (공유x)
-> 웹으로 따지면 Local storage같은 거라고!

secure storage

expo-secure-store나 react-native-keychain같은 외부 라이브러리를 사용해야 접근 가능

iOS 키체인 서비스

ios에서는 앱에 키체인이라는 암호화된 데이터베이스에 소량의 사용자 데이터를 저장할 수 있다!
위와 같이 data는 한번 더 암호화됨.

자세한 건 애플 공식문서를 참조.

Android

보안 공유 환경설정

영구 키-값 데이터 저장소.
일반 공유 환경설정과 달리 키와 값을 자동으로 암호화해줌.

키 저장소

암호화 키를 컨테이너에 저장 -> 꺼내기 더 어려워짐!

Deep Linking

모바일 앱 고유의 취약점.

외부 소스에서 네이티브 애플리케이션으로 직접 데이터를 보내는 방법.
url을 직접 수정해서 앱의 특정 페이지에 접근하는 것 같다.

왜 딥링크가 위험한가?

URL 스킴을 등록하는 중앙 집중식 방법이 없기 때문!

뭔소리냐?

  • 웹 URL은 도메인 주소를 DNS에서 관리 -> 중복x
  • 모바일 앱 URL 스킴(도메인)은 각 앱이 마음대로 등록 가능! -> 예??

그..이래도 되는거임?

  • 그래서 같은 스킴을 여러 앱이 쓸 수도 있고, 충돌하거나 가로챌 수도 있음..

딥링크를 통한 탈취 방지법

ios 유니버설 링크 / android 앱링크

일반 HTTPs URL을 이용한 딥링크 방식.
웹은 DNS가 관리해주고 있다는 부분을 차용한 듯?

  • 사용자가 링크를 클릭
    • 해당 앱이 설치되어 있으면 앱을 염
    • 없으면 해당 도메인의 웹사이트가 열림
      -> 즉, URL 도메인 소유자가 소유권을 증명!

같은 스킴을 쓰는 다른 앱이 있다면 사용자가 선택하게 한다고.




성능

성능에 영향을 끼치는 요소

개발 모드

js 스레드 성능은 개발 모드에서 크게 저하된다
-> 성능 테스트를 할거면 릴리스해서 하자.

console.log

병목 현상을 일으킨다.
바벨 플러그인 중 콘솔 로그 싹 없애서 빌드해주는 걸 쓰자.

npm i babel-plugin-transform-remove-console --save-dev

//.babelrc
{
  "env": {
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}

지나친 투명도 사용

웹에서도 blur를 쓸 때 그랬지만, 메모리가 터질 수 있다..
아래 두 개를 사용해서 최적화할 수 있음.

  • shouldRasterizeIOS 활성화
  • renderToHardwareTextureAndroid 사용

불변화면 재렌더링

최적화하기 위해 컴포넌트 재렌더링 필요성을 코드로 알려주어야 한다.

  • shouldComponentUpdate -> 클래스 펑션임.. 쓰지 말자..
  • PureComponent (React.memo, useCallback..)
  • ListView에서 rowHasChanged 주기

메모 쓰라는 말을 이렇게 어렵게 할 일인지?

ios에서 이미지 크기 조정하기

iOS에서는 이미지 width, height를 조정할 때마다 원본 이미지를 직접 조정
-> 이미지가 클수록 비싸다!
-> width, height를 직접 바꾸기보다 transform으로 scale을 바꿔주자.

Touchable

터치 반응 애니메이션이 onPress 함수가 반환될 때까지 실행되지 않을 때가 있다!
-> requestAnimationFrame 에서 onPress 핸들러 내부의 모든 작업을 래핑하면 된다는데..
-> 공식문서가 왜 자꾸 클래스형 접근을 예시로 드냐?? 함수형으로 바꿔옴.

 const handleOnPress = useCallback(() => {
    requestAnimationFrame(() => {
      doExpensiveAction();
    });
  }, []);


빌드 속도 개선

ABI란?

특정 CPU 아키텍처에서 실행 가능한 이진 코드 형식과 운영체제 간의 약속

뭔소리냐?

어떤 CPU에서, 어떤 방식으로 실행할 수 있는 앱이냐를 결정하는 규격!
-> ARM CPU용 컴파일 코드와 Intel CPU용 컴파일 코드는 서로 다른 이진코드!
-> 따라서, 각각 다른 ABI로 표시해주어야 한다!

안드로이드

로컬 빌드시 아래 ABI를 모두 빌드

  • armeabi-v7a (레거시)
  • arm64-v8a (최신)
  • x86 (32비트 에뮬레이터)
  • x86_64 (64비트 에뮬레이터)

-> 로컬에서 테스트할 땐 하나만 빌드해도 된다.
-> 이러면 속도가 75%까지 줄어든다!

안드로이드에서 특정 ABI만 빌드하기

  • React Native CLI
    • run-android에 --active-arch-only 플래그를 추가
      -> 현재 연결되어있는 기기에 관련된 ABI만 빌드됨
  • expo
    • Expo는 기본적으로 네이티브 빌드를 안 한답니당
    • 알아서 성능 최적화 해줘서 내가 신경 안 써도 됨!


이 외에도 뭐 빌드 캐시 사용전략 이런 게 있는데.. 당장 필요하진 않을 것 같아서 건너뜀.

profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글