Post

Expo Docs

Expo 공식 문서를 정리

Expo Docs

소개


Expo는 Android와 iOS 애플리케이션을 쉽게 개발할 수 있는 프레임워크이다. 파일 기반의 라우팅, 네이티브 모듈의 표준 라이브러리 등을 제공한다.


프로젝트 생성


Expo는 Node.js 가 필요하며, create-expo-app 으로 생성된 프로젝트로 시작하는 것을 권장한다.

새로운 프로젝트를 만들려면 다음 명령어를 실행하면 된다.

1
npx create-expo-app@latest
  • --template 옵션을 추가할 수 있다

그 다음으로 개발 환경을 설정해보자.


개발 환경 설정


로컬 개발 환경을 설정해보자. 사용자에게 어떻게 보이는지 직접 보기 위해 실제 디바이스를 사용하는 것을 권장한다.


Xcode와 Watchman 설정


1. Xcode 설치

App Store에서 Xcode를 설치한다.


2. Xcode Command Line Tools 설치

Xcode를 열고, 설정(cmd + ,)으로 이동한다. Locations 로 이동한 후에 Command Line Tools에서 가장 최신 버전을 설치한다.


3. iOS Simulator 설치

Xcode > Settings > Components > Platform Support 에서 iOS Simulator를 설치한다.


4. Watchman 설치

Watchman은 파일 시스템의 변화를 감지하는 도구이다. 다음과 같이 설치할 수 있다.

1
2
brew update && \
brew install watchman


5. 프로젝트 설정

1. expo-dev-client 설치

프로젝트 루트 위치에서 다음 명령어를 실행한다.

1
npx expo install expo-dev-client

2. 디바이스에 USB 연결 후 개발자 모드 활성화

  1. iOS 디바이스에 USB를 연결한다. 디바이스의 잠금을 푼 후 신뢰가 뜰 경우 클릭한다.
  2. Xcode를 열고, 메뉴바에 Window > Device and Simulators 를 선택한다.
  3. iOS 디바이스에서 Settings > Privacy & Security를 열고, Developer Mode에 접근한다.
  4. Developer Mode를 클릭 한 후 Restart 버튼을 클릭한다.
  5. 디바이스가 재시작한 후 Xcode를 확인해보면 iPhone에 연결된 것을 볼 수 있다.

3. 프로젝트를 디바이스에 실행

  1. Xcode에서 ios/<yourproject>.xcworkspace 을 연다. 만약 ios 폴더가 존재하지 않을 경우 npx expo prebuild -p ios 를 실행한다.
  2. Xcode에서 첫 번째로 선택한 프로젝트에서 TARGETS의 Signing & Capabilities로 이동한다.
  3. Automatically manage signing 을 선택하고 Add Account 를 클릭하여 Apple Developer 계정을 연동한다.
  4. Team을 등록한 계정의 팀으로 설정한다.
  5. 프로젝트 루트에 있는 app.jsonios.bundleIdentifier 를 추가하여 Xcode가 앱 서명 단계에 대한 프로비저닝 프로필을 생성하도록 한다.
  6. 프로젝트 루트에서 다음 명령을 실행한 후, 연결한 iPhone Device를 선택한다.
    1
    
    npx expo run:ios --device
    


개발 시작하기


1. 개발 서버 시작

다음 명령어로 개발 서버를 시작한다.

1
npx expo start

2. 디바이스에서 앱 열기

위 명령어를 실행하면 터미널에서 QR 코드를 확인할 수 있다. 디바이스에서 QR 코드를 스캔하여 앱을 열 수 있다.


파일 구조


  • 기본 프로젝트 파일 구조
    • app : 파일 기반의 앱 네비게이션을 포함한다. app의 파일 구조는 앱의 네비게이션을 결정한다.
    • assets : Android 와 iOS의 앱 아이콘으로 사용되는 adaptive-icon.png 파일이 포함된다. 또한 프로젝트의 스플래시 스크린인 splash.png 와 앱이 브라우저에서 실행되는 경우 favicon.png 가 존재한다.
    • components : 앱의 라이트와 다크 모드의 색상 스키마에서 사용하는 텍스트 요소를 만드는 ThemedText.tsx 와 같은 React Native 컴포넌트들이 존재한다.
    • constants : 앱 전체의 색상 값 목록을 포함하는 Color.ts가 포함되어 있다.
    • hooks : 컴포넌트 간에 공통적인 동작을 공유할 수 있는 React Hooks 가 존재한다. 예를 들어, useThemeColor() 는 현재 테마 기반의 색상을 반환하는 훅이다.
    • scripts : appapp-example 로 이동시키는 rest-project.js 가 포함된다.
    • app.json : 프로젝트 설정이나 앱 설정이 포함된다.
    • package.json : 프로젝트의 의존, 스크립트, 메타데이터를 포함한다.
    • tsconfig.json : TypeScript 규칙을 포함한다.


프로젝트 초기화


다음 명령어로 보일플레이트 코드를 제거할 수 있다.

1
npm run reset-project


개발 도구


Expo CLI


Expo CLI는 개발 도구이며 새로운 프로젝트를 만들면 expo 패키지와 함께 자동으로 설치된다. npx 를 활용하여 사용할 수 있다. 이는 개발을 빠르게 할 수 있도록 설계됐다. 다음 명령어들은 개발하는 동안 사용되는 Expo CLI 이다.

명령어설명
npx expo start개발 서버를 시작한다
npx expo prebuildAndroid와 iOS 빌드
npx expo run:androidAndroid 컴파일
npx expo run:iosiOS 컴파일
npx expo install package-name새로운 라이브러리를 설치하거나 --fix 옵션으로 라이브러리를 검증하고 수정
npx expo lintESLint를 구성하고 설정


EAS CLI


EAS CLI는 Expo 회원으로 로그인하고 빌드, 업데이트, 배포 등 다양한 EAS 서비스로 애플리케이션을 컴파일하는데 사용된다. 또한, 다음 도구들을 사용할 수 있다.

  • 앱스토어에 애플리케이션 배포
  • 개발, 프리뷰, 운영 애플리케이션 생성
  • OTA 업데이트
  • 애플리케이션 인증 정보 관리
  • iOS 기기에 대한 임시 프로비저닝 프로필 생성


Expo Doctor


Expo Doctor는 Expo 프로젝트의 이슈를 진단하는데 사용되는 CLI이다. 다음 명령어를 통해 사용할 수 있다.

1
npx expo-doctor

이 명령은 앱 설정, package.json, 의존 호환성, 설정 파일, 프로젝트의 전반적인 상태 등의 일반적인 문제에 대한 것을 프로젝트 코드베이스로부터 확인하고 분석한다.


Orbit


Orbit은 다음 기능들을 제공한다.

  • 디바이스와 애뮬레이터에 EAS 빌드를 설치하고 실행한다.
  • Android 나 iOS에서 EAS를 통해 설치하고 실행한다.
  • Android 나 iOS에 snake 프로젝트를 실행한다
  • Orbit은 Android .apk 나 iOS .app 등을 지원한다



Expo Router


Expo Router는 React Native와 웹 애플리케이션을 위한 라우팅 프레임워크이다. 이를 통해 앱의 화면 간 탐색을 관리하고 여러 플랫폼(Android, iOS 및 웹)에서 동일한 구성 요소를 사용할 수 있다. 앱에서 라우팅 하기 위해 파일 기반의 방식을 사용한다. 또한 네이티브 네비게이션 기능을 제공하며 React Navigation을 기반으로 구축되었다.


app directory


app 디렉터리에 추가한 파일은 네이티브 앱에 라우팅되고, 웹에서도 동일한 경로의 URL에 반영된다.


route 생성


app 디렉터리에 index.tsx 파일을 포함하는 파일이나 중첩 디렉토리를 추가하여 경로를 생성한다.

앱의 초기 경로를 만들려면 다음과 같이 index.tsx를 앱 디렉터리에 추가하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import {View, Text, StyleSheet} from 'react-native';  
  
export default function HomeScreen() {  
  return (  
    <View style={styles.container}>  
      <Text>Home Screen</Text>
    </View>
    )
}  
  
const styles = StyleSheet.create({  
  container: {  
    flex: 1,  
    justifyContent: 'center',  
    alignItems: 'center',  
  }  
});
  • app/index.tsx 파일은 / 경로에 라우팅된다.


파일명 컨벤션


파일 기반 인덱스는 상위 디렉터리에 라우팅되고 경로 세그먼트를 추가하지 않는다. app/settings/index.tsx 로 파일 구조를 구성할 경우, 해당 파일은 /settings 로 라우팅된다.

  • app
    • index.tsx(matches ‘/’)
    • settings
      • index.tsx (matches ‘/settings’)


_layout 파일


레이아웃 파일들은 헤더, 탭 바와 같은 공유 UI 요소를 서로 다른 경로 간에 유지되도록 하는데 사용된다.

프로젝트를 생성하면 기본적으로 app 디렉터리에 루트 레이아웃 파일이 포함된다(app/_layout).

  • app
    • index.tsx (matches ‘/’)
    • _layout (Root layout)


Root layout


React Native 프로젝트는 싱글 루트 컴포넌트(App.js 나 index.js)로 구성된다. 마찬가지로 app 디렉터리의 레이아웃 파일(_layout.tsx)은 싱글 루트 컴포넌트로 간주된다.

다중 라우팅에서는 Expo Router인 루트 레이아웃 파일은 global providers, themes, styles을 주입하거나, assets와 fonts가 로드될 때 까지 splash screen 렌더링을 지연하거나, 앱의 루트 네비게이션 구조를 정의하는 등 여러 경로 간에 UI를 공유하는 데 사용된다.

다음 코드는 RootLayout 이라는 기본 React 구성 요소로 구성된다.

1
2
3
4
5
export default function RootLayout() {  
  return (
	  // ...
  )  
};

Expo Router를 사용하면 app/_layout.tsx 내부에 정의된 React providers에 앱의 모든 경로에서 접근할 수 있다. 성능을 개선하고 렌더링 횟수를 줄이려면 필요한 경로에만 사용되도록 해야 한다.


Stack navigator


Stack navigator는 앱에서 다른 경로들을 탐색하는 패턴이다. 화면 간 전환 및 탐색 기록 관리가 가능하다. 이는 웹 브라우저에서 탐색 상태를 처리하는 방식과 개념적으로 유사하다.

/details 경로는 추가하려면, details.tsx 파일을 생성하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { View, Text, StyleSheet } from 'react-native';

export default function DetailsScreen() {
  return (
    <View style={styles.container}>
      <Text>Details</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

파일을 생성한 후에 다음과 같이 파일을 구성한다.

  • app
    • index.tsx
    • details.tsx
    • _layout


//details 경로를 탐색할 수 있도록 다음과 같이 레이아웃 파일을 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: {
          backgroundColor: '#f4511e',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}>
      <Stack.Screen name="index" />
      <Stack.Screen name="details" />
    </Stack>
  );
}
  • 레이아웃 파일의 <Stack.Screen name={routeName}/> 컴포넌트는 스택에 경로를 정의할 수 있도록 해준다.

위의 screenOptions 는 스택의 모든 경로에 대해 옵션을 설정할 수 있도록 해준다.


경로 간 이동


Expo Router는 앱에서 경로간에 이동할 수 있도록 Link 라고 불리는 내장 컴포넌트를 사용한다. 이는 <a> 태그와 href 속성과 개념적으로 유사하다.

이를 Expo Router library로부터 임포트한 후, 이동할 라우트를 href prop에 값으로 전달하여 사용할 수 있다. 예를 들어/ 에서 /details 로 넘기기 위해 index.tsx 파일에 Link 컴포넌트를 추가해라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {View, Text, StyleSheet} from 'react-native';  
import {Link} from "expo-router";  
  
export default function HomeScreen() {  
  return (  
    <View style={styles.container}>  
      <Text>Home Screen</Text>  
      <Link href="/details">View details</Link>  
    </View>  
    );  
}  
  
const styles = StyleSheet.create({  
  container: {  
    flex: 1,  
    justifyContent: 'center',  
    alignItems: 'center',  
  }  
});



Link 는 기본적으로 자식을 <Text> 로 감싼다. 다른 버튼 컴포넌트를 사용하여 커스텀을 할 수도 있다.

Link 컴포넌트를 사용해 커스텀 버튼 컴포넌트를 감싸고, asChild prop을 사용해 모든 prop을 Link 의 첫 번째 자식에게 전달해라. 더 자세한 것은 Link 컴포넌트의 prop을 확인해라.


Groups


그룹은 유사한 경로들이나 앱의 특정 섹션을 정리하기 위해 생성된다. 각 그룹은 레이아웃 파일을 가지고, 그룹화된 디렉터리는 괄호 안에 이름을 넣어야 한다(group).

예를 들어, app/(home) 디렉터리에 그룹화 할 //detail 경로가 있을 수 있다. 이는 다음과 같이 파일 구조를 수정해라

  • app
    • _layout.tsx - Root layout
    • (home)
      • index.tsx - ‘/’
      • details.tsx - ‘/details’
      • _layout_tsx - Home layout


Root 레이아웃 파일도 변경되어 (home) 그룹을 포함하며, (home)/index 를 앱의 초기 경로로 사용한다.

1
2
3
4
5
6
7
8
9
import {Stack} from "expo-router";  
  
export default function RootLayout() {  
  return (  
    <Stack>  
      <Stack.Screen name="(home)"/>  
    </Stack>  
    )  
}


Tab navigator


tab navigator 는 tab bar를 사용하여 앱의 다른 섹션 간을 이동하는 일반적인 패턴이다. Expo Router는 Tab navigation 컴포넌트를 제공한다.

예를 들어, 현재 파일 구조에서 Home(//details)과 Settings(/settings)과 같은 다른 두 섹션을 가질 수 있다. (tabs) 디렉터리를 추가하고, 기존에 존재하던 Home을 옮기고 settings.tsx 를 만들어라.


  • app
    • _layout.tsx - Root
    • (tabs)
      • _layout.tsx - Tab
      • (home)
        • index.tsx - ‘/’
        • details.tsx - ‘/details’
        • _layout.tsx - Home
      • settings.tsx - ‘/settings’


(tabs) 안의 어느 파일이나 디렉터리는 tab navigator의 경로가 된다. tab bar를 사용하여 다른 경로 간에 이동을 하려면, (tabs)_layout 에 레이아웃 파일을 추가하고 TabLayout 컴포넌트에 추가하면 된다.

1
2
3
4
5
6
7
8
9
10
import {Tabs} from "expo-router";  
  
export default function TabLayout() {  
  return (  
    <Tabs>  
      <Tabs.Screen name="(home)"/>  
      <Tabs.Screen name="settings"/>  
    </Tabs>  
    )  
}


이처럼 동작하게 하려면 app/_layout.tsx 파일의 첫 번째 경로에 (tabs) 를 추가하면 된다.

1
2
3
4
5
6
7
8
9
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" />
    </Stack>
  );
}


Not found routes


Expo Router는 404와 같은 경로를 다루기 위한 +not-found.tsx 파일을 제공한다. 이 파일은 모든 경로에 매칭되지 않을 경우에 사용된다.

app 디렉터리에 다음 파일을 생성해라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Link, Stack } from 'expo-router';
import { View, StyleSheet } from 'react-native';

export default function NotFoundScreen() {
  return (
    <>
      <Stack.Screen options={{ title: "Oops! This screen doesn't exist." }} />
      <View style={styles.container}>
        <Link href="/">Go to home screen</Link>
      </View>
    </>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});



동적 경로는 URL에 포함된 동적 세그먼트를 기반으로 하나 이상의 경로를 매칭할 수 있게 해준다. 트는 고유 식별자와 같은 변수 형식이며, 앱은 이 세그먼트를 사전에 정확히 할 수 없다.

다음으로 동적 경로를 다루는 방법에 대해서 알아보자.


Dynamic route convention


경로의 동적 세그먼트는 대괄호로 구성된 파일 이름으로 생성된다(ex. [id].tsx).


Create a dynamic route


동적 경로를 구성한 예시를 살펴보자.

  • app
    • _layout.tsx - Root layout
    • (tabs)
      • _layout.tsx - Tab layout
      • settings.tsx - ‘/settings’
      • (home)
        • _layout.tsx - Home layout
        • index.tsx - ‘/’
        • details
          • [id].tsx - ‘/details/1’


위와 같은 파일 구조에서 [id]details/[id].tsx 경로에 대한 정보를 표시하는 데 사용된다. 이 경로는 id 값에 따라 고유한 정보를 표시한다.

이러한 동적 세그먼트 컨벤션은 앱 사용자가 홈 화면에서 상세 화면으로 이동할 때 해당 경로의 동적 세그먼트에 대한 올바른 정보를 볼 수 있도록 해준다.



정적 방식 또는 href 객체를 사용해 Link 컴포넌트에 쿼리 파라미터를 전달함으로써, 일반 라우트에서 동적 라우트로 이동할 수 있다.

예를 들어, 다음과 같은 코드는 동적 라우트에 정적인 쿼리 파라미터를 사용하여 이동시킬 수 있도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Link } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text>Home</Text>
      <Link href="/details/1">View first user details</Link>
      <Link href="/details/2">View second user details</Link>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});


동적 라우트로부터 가져온 pathnameparamshref 객체에 전달하여 사용할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Link } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text>Home</Text>
      <Link
        href={{
          pathname: '/details/[id]',
          params: { id: 'bacon' },
        }}>
        View user details
      </Link>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});


Access parameters from dynamic segments


URL의 동적 세그먼트는 라우트 컴포넌트의 라우트 파라미터에 접근할 수 있다. 예를 들어, 선택된 경로에 대해 URL 파라미터를 반환하는 useLocalSearchParam 를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useLocalSearchParams } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';

export default function DetailsScreen() {
  const { id } = useLocalSearchParams();

  return (
    <View style={styles.container}>
      <Text>Details of user {id} </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

  • / 에서 /details/1 로 이동할 때, /details/1 이 선택되었으므로 useLocalSearchParams{ id: '1' } 을 반환한다.


UI: Splash screen and app icon


스플래시 화면과 앱 아이콘은 모바일 앱의 기본 요소이다.


Splash screen


실행 화면과 같은 스플래시 화면은 앱을 열 때 처음 보이는 화면이다. 앱이 로딩되는 동안 스플래시 화면이 표시된 상태로 유지된다. SplashScreen API 를 사용해 스플래시 화면이 사라지는 동작에 대해 수정할 수도 있다.

expo-splash-screen 에는 스플래시 아이콘이나 배경 색상에 대한 속성을 설정할 수 있는 내장 설정 플러그인이 포함된다.


1. Create a splash screen icon


먼저 Figma template을 사용해서 스플래시 화면 아이콘을 만든다. 해당 디자인은 Android와 iOS를 위한 아이콘과 스플래시 이미지들의 최소한의 디자인을 제공한다.

  • 권장 사항
    • 1024x1024 이미지를 사용
    • .png 파일을 사용
    • 투명 배경 사용


2. Export the splash icon as a .png


스플래시 화면 아이콘을 .png 로 내보내기 한 후에 assets/images 에 저장한다. 기본적으로 Expo는 파일 이름으로 splash-icon.png 를 사용한다. 스플래시 화면 파일의 이름을 변경하기로 했다면, 다음 단계에서 해당 이름을 사용해야 한다.


3. Configure the splash screen icon


앱 설정 파일을 열고 plugins 에 다음과 같이 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "expo": {
    "plugins": [
      [
        "expo-splash-screen",
        {
          "backgroundColor": "#232323",
          "image": "./assets/images/splash-icon.png",
          "dark": {
            "image": "./assets/images/splash-icon-dark.png",
            "backgroundColor": "#000000"
          },
          "imageWidth": 200
        }
      ]
    ]
  }
}


새로운 스플래시 화면이 iOS에 나오지 않으면 다음 명령어를 사용해라.

1
npx expo run:ios --no-build-cache


App icon


앱 아이콘은 사용자의 홈 화면과 앱 스토어에서 보인다. 앱 설정에서 icon 값을 변경하여 바꿀 수 있다.

1
2
3
{
  "icon": "./assets/images/icon.png"
}


UI: Safe areas


safe area를 생성하면 앱 화면의 콘텐츠가 올바르게 배치되도록 보장할 수 있다.


Use react-native-safe-area-context library


react-native-safe-area-context 는 Android와 iOS 디바이스의 safe area 인셋을 처리하기 위한 유연한 API를 제공한다. 또한 <View> 대신에 SafeAreaView 컴포넌트를 제공하여, 화면 컴포넌트에서 safe area를 자동으로 처리할 수 있게 해준다.


Installation


기본 템플릿으로 구성된 프로젝트를 사용중이면 react-native-safe-area-context 를 설치할 필요없다. 설치가 필요하다면 다음 명령어를 실행하면 된다.

1
npx expo install react-native-safe-area-context


Usage


화면 컴포넌트를 컨텐츠를 감싸는 SafeAreaView 를 사용하면 된다. 이는 safe area 인셋이 적용된 <View> 이다.

1
2
3
4
5
6
7
8
9
10
import { Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

export default function HomeScreen() {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <Text>Content is in safe area.</Text>
    </SafeAreaView>
  );
}


Usage with React Navigation


기본적으로 React Navigation은 safe area를 제공하고 react-native-safe-area-context 를 사용한다.


UI: System bars


system bar는 화면의 모서리에 위치한 UI 요소로, 필수적인 기기 정보와 네비게이션 컨트롤을 제공한다. 모바일 OS에 따라, 시스템 바에는 status bar(Android and iOS), caption bar(Android only), navigation ba(Android and iOS), home indicator(iOS only)가 포함된다.

이러한 컴포넌트들은 배터리 잔량, 시간, 알림 등의 기기 정보를 표시하고, 기기 인터페이스 어디에서든 직접 상호작용할 수 있도록 해준다. 예를 들어, 앱 사용자는 현재 어떤 앱을 사용 중이든 상관없이 status bar를 아래로 내려 빠른 설정과 알림에 접근할 수 있다.

system bar는 모바일 환경의 핵심 요소이며, 이를 올바르게 다루는 방법을 이해하는 것은 앱을 만드는 데 있어 중요하다.


Handling overlaps using safe areas


앱의 일부 컨텐츠는 시스템 바 뒤에 그려질 수 있다. 이를 처리하려면, 겹침을 피하고 시스템 바가 나타나도록 콘텐츠의 위치를 올바르게 조정해야 한다.


Safe ares and edge-to-edge layout on Android


Android의 edge-to-edge를 사용하기 전에는 translucent(반투명) 상태 바와 내비게이션 바를 사용하는 것이 일반적이였다. 이 방식에서는 콘텐츠가 이미 바 뒤에 그려졌기 때문에 safe area를 고려할 필요가 없었다. 지금은 safe area를 사용하여 컨텐츠가 시스템 바에 겹치지 않도록 해야 한다.


Customizing system bars


시스템 바는 앱의 디자인에 맞게 커스텀 될 수 있으며, 다른 시나리오에서 더 나은 가시성을 제공할 수 있다. Expo를 사용할 때 두 라이브러리가 존재한다. expo-status-barexpo-navigation-bar (Android only).


Status bar configuration


상태 바는 Android와 iOS에서 화면의 최상단에 표시된다. expo-status-bar 를 사용하여 커스텀을 할 수 있다. 이 라이브러리는 style 속성이나 setStatusBarStyle 메서드를 사용하여 앱이 실행되는 동안 상태 바를 수정할 수 있는 StatusBar 컴포넌트를 제공한다.

1
2
3
4
5
6
7
8
import { StatusBar } from 'expo-status-bar';

export default function RootLayout() {
  <>
    {/* Use light text instead of dark text in the status bar to provide more contrast with a dark background. */}
    <StatusBar style="light" />
  </>;
}


StatusBar 의 가시성을 설정하기 위해 hidden 속성을 true 로 설정하거나 setStatusBarHidden 메서드를 사용할 수 있다.

Android에서 edge-to-edge가 활성화된 경우, 불투명한 상태 표시줄에 의존하는 expo-status-bar 의 기능들은 사용할 수 없다. 스타일과 표시 여부만 커스텀 할 수 있으며, 다른 속성들은 무시되고 경고가 출력된다.



안드로이드 기기에서, 네비게이션 바는 화면의 아래에 나타난다. expo-navigation-bar 라이브러리를 사용하여 이를 커스텀 할 수 있다. 이 라이브러리는 setStyle 메서드를 사용하는 네비게이션 바의 스타일을 설정할 수 있는 NavigationBar 컴포넌트를 제공한다.

1
2
3
4
5
6
7
8
9
import { NavigationBar } from 'expo-navigation-bar';
import { useEffect } from 'react';

useEffect(() => {
  if (Platform.OS === 'android') {
    // Set the navigation bar style
    NavigationBar.setStyle('dark');
  }
}, []);


NavigationBar 가시성을 설정하려면 setVisibilityAsync 메서드를 사용하면 된다.

Android에서 edge-to-edge가 활성화된 경우, 불투명한 내비게이션 바에 의존하는 expo-navigation-bar 의 기능은 사용할 수 없다. 스타일과 표시 여부만 커스텀할 수 있으며, 다른 속성들은 무시되고 경고가 출력된다.


UI: Fonts


Android와 iOS는 각자의 플랫폼 전용 기본 폰트를 제공한다. 일관된 사용자 경험과 앱 브랜딩을 향상시키기 위해 커스텀 폰트를 사용할 수도 있다.

이 가이드는 커스텀 폰트를 프로젝트에 추가하고 로드하는 다양한 방법을 다루며, 폰트와 관련된 추가 정보도 제공한다.


Add a custom font


프로젝트에 커스텀 폰트를 추가하는 방법은 두 가지가 존재한다.

  1. local assets에 폰트 파일을 추가. 예를 들어, 폰트 파일을 assets/fonts 디렉터리에 추가
  2. Google Font 패키지를 설치. 예를 들어, @expo-google-fonts/inter 패키지를 설치


Supported font formats


Expo SDK는 공식적으로 Android, iOS, web 플랫폼 전반에서 OTF 및 TTF 폰트 포맷을 지원한다. 폰트가 다른 포맷이라면 프로젝트가 해당 포맷을 지원하기 위한 추가 설정을 해야 한다.


How to choose between OTF and TTF


사용하는 폰트가 OTF 와 TTF 둘 다 있을 경우, OTF를 권장한다. .otf 파일은 .ttf 파일보다 가볍기 때문이다. 경우에 따라 OTF는 특정 환경에서 TTF보다 약간 더 나은 렌더링을 제공하기도 한다.


Use a local font file


폰트 파일을 assets/fonts 디렉터리에 복사한다.

프로젝트에서 로컬 폰트 파일을 두 방법으로 사용할 수 있다.

  • expo-font 설정 플러그인을 사용하여 폰트 파일을 포함한다.
  • 런타임 useFonts 훅을 사용해 폰트 파일을 비동기적으로 로딩한다.


With expo-font config plugin


expo-font 설정 플러그인은 프로젝트의 네이티브 코드에 하나 이상의 폰트 파일을 내장시킬 수 있다. 이는 다음과 같은 장점으로 인해 권장되는 방법이다.

  • 기기에서 앱이 시작할 때 폰트를 즉시 사용할 수 있다.
  • 비동기적으로 폰트를 로드하기 위한 코드가 필요하지 않다.
  • 폰트는 앱에 번들되어 있기 때문에 앱이 설치된 모든 기기에서 일관되게 사용할 수 있다.


그러나 이 방법은 다음과 같은 제한이 있다.

  • 이 방법은 개발용 빌드를 생성해야 하므로 Expo Go에서는 작동하지 않는다.
  • SDK 50 이상에서 가능하다.


프로젝트에 폰트를 내장하려면 다음 과정을 따르면 된다.

1. expo-font 라이브러리 설치
1
npx expo install expo-font


2. 앱 설정 파일에서 플러그인 설정

앱 설정 파일에서 fonts 속성에 폰트 파일을 추가해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
{
  "expo": {
    "plugins": [
      [
        "expo-font",
        {
          "fonts": ["./assets/fonts/Inter-Black.otf"]
        }
      ]
    ]
  }
}


3. 개발 빌드 및 어플 설치

설정 플러그인에 폰트를 넣은 후, 개발 빌드를 수행하고 기기에 설치한다.

특정 fontFamily style prop 을 지정한 <Text> 를 사용할 수 있다.

1
<Text style={{ fontFamily: 'Inter-Black' }}>Inter Black.</Text>


With useFonts hook


expo-font 라이브러리의 useFonts 훅은 폰트 파일을 비동기로 로드할 수 있도록 한다. 이 훅은 로딩 상태를 추적하며, 앱이 초기화될 때 폰트를 로드한다.

이 훅은 모든 Expo SDK 버전과 Expo GO에서 동작한다. useFonts 훅을 사용해 폰트를 로드하기 위해서는 다음 과정을 따르면 된다.


1. expo-fontexpo-splash-screen 라이브러리 설치
1
npx expo install expo-font expo-splash-screen
  • expo-splash-screen 라이브러리는 SplashScreen 컴포넌트를 제공하며, 이를 사용해 폰트가 로드되어 준비될 때까지 앱 렌더링을 지연시킬 수 있다.


2. useFonts 훅 매핑

프로젝트의 최상위 컴포넌트(ex. app/layout.tsx)에서 useFonts 훅을 사용해 폰트 파일을 매핑해라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 import { useFonts } from 'expo-font'; 
 import * as SplashScreen from 'expo-splash-screen'; 
import {useEffect} from 'react';


SplashScreen.preventAutoHideAsync();


export default function RootLayout() {
  const [loaded, error] = useFonts({
    'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
  });

  useEffect(() => {
    if (loaded || error) {
      SplashScreen.hideAsync();
    }
  }, [loaded, error]);

  if (!loaded && !error) {
    return null;
  }

  return (
  )
}


3. <Text> 컴포넌트 설정

fontFamily style prop 을 사용하여 <Text> 에 폰트를 설정한다.

1
<Text style={{ fontFamily: 'Inter-Black' }}>Inter Black</Text>


Use Google Fonts


Expo는 Google Fonts에 등록된 모든 폰트를 완벽하게 지원한다. 이는 @expo-google-fonts 라이브러리를 통해 사용할 수 있다. 이 라이브러리의 폰트 패키지 중 하나를 사용하면, 해당 폰트와 그 변형들을 빠르게 통합할 수 있다.

Google Font를 사용하는 방법은 두 가지이다.

  • 설치한 폰트를 expo-font 설정 플러그인으로 포함한다.
  • 런타임에 useFonts 훅을 사용해 해당 폰트를 비동기적으로 로드한다.


With expo-font config plugin


1. @expo-google-fonts/inter 설치
1
npx expo install expo-font @expo-google-fonts/inter


2. 앱 설정에 플러그인 구성
1
2
3
4
5
6
7
8
9
10
{
  "plugins": [
    [
      "expo-font",
      {
        "fonts": ["node_modules/@expo-google-fonts/inter/Inter_900Black.ttf"]
      }
    ]
  ]
}


3. 개발 빌드 및 설치

설정 플러그인에 폰트를 넣은 후, 개발 빌드를 수행하고 기기에 설치한다.

플랫폼 별로 다르게 폰트를 설정하려면 다음과 같이 적용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
import { Platform } from 'react-native';

// Inside a React component:
<Text
  style={{
    fontFamily: Platform.select({
      android: 'Inter_900Black',
      ios: 'Inter-Black',
    }),
  }}>
  Inter Black
</Text>


With useFonts hook


1. 라이브러리 설치
1
npx expo install @expo-google-fonts/inter expo-font expo-splash-screen


2. useFonts 훅 매핑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Rest of the import statements
import { Inter_900Black, useFonts } from '@expo-google-fonts/inter';
import * as SplashScreen from 'expo-splash-screen';
import {useEffect} from 'react';

SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
  const [loaded, error] = useFonts({
    Inter_900Black,
  });

  useEffect(() => {
    if (loaded || error) {
      SplashScreen.hideAsync();
    }
  }, [loaded, error]);

  if (!loaded && !error) {
    return null;
  }

  return (
  )
}


3. <Text> 사용
1
<Text style={{ fontFamily: 'Inter_900Black' }}>Inter Black</Text>


Additional information



UI: Assets


static asset은 앱의 바이너리와 번들되는 파일이다. 이 파일 타입은 앱의 코드를 포함하는 JS 번들의 일부가 아니다. static assets는 이미지, 비디오, 음성, SQLite 데이터베이스 파일, 폰트 등이 포함된다. assets은 프로젝트 내에서 로컬로 제공되거나, 네트워크를 통해 원격으로 제공될 수 있다.

이 가이드는 프로젝트에서 static asset을 로드하고 사용하는 방법을 다룬다. 또한 assets을 최적화하고 최소화하는 방법에 대한 추가 정보도 제공한다.


Serve an asset locally


에셋이 프로젝트에 저장되어 있을 때 빌드 시 앱 바이너리에 포함되거나 런타임에 될 수 있다. 이를 require 이나 import 문을 통해 불러올 수 있다.

예를 들어, App.js 에서 example.png 이미지를 불러와서 렌더링 하려면 프로젝트의 assets/images 디렉터리로부터 이미지를 require 로 불러와 다음과 같이 <Image> 컴포넌트에 전달하면 된다.

1
<Image source={require('./assets/images/example.png')}
  • 위 예시는 번들러가 불러온 이미지의 메타데이터를 읽어오고 자동으로 가로와 세로를 제공한다.
  • expo-imagesexpo-file-system 과 같은 라이브러리는 로컬 에셋과 함께 <Image> 컴포넌트와 유사하게 동작한다.


How are assets served locally


로컬에 저장된 에셋은 개발 환경에서 HTTP를 통해 제공된다. 이는 프로덕션 앱을 위한 빌드 타입에 앱 바이너리에 자동으로 번들되며 디바이스의 디스크로부터 제공된다.


Load an asset at build time with expo-asset config plugin


빌드 시 에셋을 로드하려면, expo-asset 라이브러리의 설정 플러그인을 사용할 수 있다. 이 플러그인은 네이티브 프로젝트에 에셋 파일로 내장된다.


1. expo-asset 라이브러리 설치
1
npx expo install expo-asset


2. 앱 설정에 에셋 구성

앱 설정 파일에 expo-asset 설정 플러그인을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
{
  "expo": {
    "plugins": [
      [
        "expo-asset",
        {
          "assets": ["./assets/images/example.png"]
        }
      ]
    ]
  }
}


3. 개발 빌드 및 컴포넌트와 에셋 사용

빌드 한 후 require 이나 import 문 없이 에셋을 불러오고 사용할 수 있다.

1
2
3
4
5
import { Image } from 'expo-image';

export default function HomeScreen() {
  return <Image source={{ uri: 'example' }} style={{ width: 100, height: 100 }} />;
}


Load an asset at runtime with useAssets hook


expo-asset 라이브러리의 useAssets 훅은 에셋을 비동기적으로 불러올 수 있게 한다. 이 훅은 에셋을 다운로드하여 로컬에 저장하며, 로딩이 완료되면 해당 에셋 인스턴스들의 목록을 반환한다.


1. expo-asset 라이브러리 설치
1
npx expo install expo-asset


2. useAsset 사용
1
2
3
4
5
6
7
8
9
10
import { useAssets } from 'expo-asset';

export default function HomeScreen() {
  const [assets, error] = useAssets([
    require('path/to/example-1.jpg'),
    require('path/to/example-2.png'),
  ]);

  return assets ? <Image source={assets[0]} /> : null;
}


Serve an asset remotely


에셋을 원격으로 제공할 때, 이는 빌드 시 앱 바이너리에 번들되지 않는다. 원격에 호스트되는 프로젝트의 에셋 리소스의 URL을 사용할 수 있다. 예를 들어, 원격 이미지를 렌더링 하기 위한 <Image> 컴포넌트에 URL을 넘기면 된다.

1
2
3
4
5
6
7
import { Image } from 'expo-image';

function App() {
  return (
    <Image source={{ uri: 'https://example.com/logo.png' }} style={{ width: 50, height: 50 }} />
  );
}


Additional information



UI: Color themes


앱은 일반적으로 라이트와 다크 테마를 지원한다.


Configuration


지원할 화면 스타일을 설정하려면, 앱 설정에서 userInterfaceStyle 속성을 사용할 수 있다. 이 속성의 기본 값은 automatic 이다.

예시 설정은 다음과 같다.

1
2
3
4
5
{
  "expo": {
    "userInterfaceStyle": "automatic"
  }
}
  • android.userInterfaceStyle 이나 ios.userInterfaceStyle 에 원하는 값을 지정하여, 특정 플랫폼에 대해 userInterfaceStyle 을 설정할 수 있다.


Detect the color scheme


색상 스키마를 감지하려면 다음과 같이 AppearanceuseColorScheme를 사용해라.

1
2
3
4
5
6
7
8
9
10
11
import { Appearance, useColorScheme } from 'react-native';

function MyComponent() {
  let colorScheme = useColorScheme();

  if (colorScheme === 'dark') {
    // render some dark thing
  } else {
    // render some light thing
  }
}


UI: Animation


React Native의 Animated API를 사용하여 애니메이션을 보여줄 수 있다. 하지만 애니메이션의 성능을 향상시키기를 원하면 react-native-reanimated 라이브러리를 사용해야 한다. 이 API는 부드럽고 강력하며 유지 관리하기 쉬운 애니메이션을 손쉽게 만들 수 있도록 해준다.


Installation


기본 템플릿을 활용해 프로젝트를 만들었으면 react-native-reanimated 설치를 하지 않아도 된다.

1
npx expo install react-native-reanimated


Usage


Minimal example


다음과 같이 react-native-reanimated 라이브러리를 사용하면 쉽게 애니메이션을생성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  Easing,
} from 'react-native-reanimated';
import { View, Button, StyleSheet } from 'react-native';

export default function AnimatedStyleUpdateExample() {
  const randomWidth = useSharedValue(10);

  const config = {
    duration: 500,
    easing: Easing.bezier(0.5, 0.01, 0, 1),
  };

  const style = useAnimatedStyle(() => {
    return {
      width: withTiming(randomWidth.value, config),
    };
  });

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.box, style]} />
      <Button
        title="toggle"
        onPress={() => {
          randomWidth.value = Math.random() * 350;
        }}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 100,
    height: 80,
    backgroundColor: 'black',
    margin: 30,
  },
});


UI: Store data


데이터 저장은 앱에 구현된 기능에 있어 필수적일 수 있다. 저장하려는 데이터의 유형과 보안 요구사항에 따라 Expo 프로젝트에서 데이터를 저장하는 방법은 다양하다. 이 페이지에서는 다양한 라이브러리를 설명한다.


Expo SecureStore


expo-secure-store 는 키-값 쌍으로 기기 내에 암호화하여 안전하게 저장할 수 있는 방법을 제공한다.

Expo SecureStore API reference


Expo FileSystem


expo-file-system 은 기기 내에 파일 시스템 접근을 제공한다. Expo Go에서는 각 프로젝트 별로 분리된 파일 시스템을 갖으며 다른 Expo 프로젝트의 파일에는 접근할 수 없다. 그러나 이 파일 시스템은 로컬 파일시스템에 다른 프로젝트에 공유되는 내용을 저장할 수 있으며 다른 프로젝트에 로컬 파일을 공유한다. 또한 네트워크 URL로 업로드하고 다운로드하는 기능도 제공한다.

Expo FileSystem API reference


Expo SQLite


expo-sqlite 패키지는 WebSQL과 유사한 API를 통해 쿼리 할 수 있는 데이터베이스에 접근할 수 있도록 해준다. 이 데이터베이스는 앱을 재시작해도 영속화된다. 기존의 데이터베이스를 불러오거나 데이터베이스 여는 것, 테이블 생성, 아이템 삽입, 쿼리와 결과 조회, prepared statement를 사용할 수 있다.

Expo SQLite API reference


Async Storage


Async Storage는 React Native 앱을 위한 비동기적이며 암호화되지 않은 영속적인 키-값 저장소이다. 이는 간단한 API로 되어 있고 소량의 데이터를 저장하기에 유용하다. 또한 사용자 선호도와 같은 암호화가 필요없는 데이터를 저장할 때 유용하다.

Async Storage documentation


Development builds: Introduction


개발 빌드는 expo-dev-client 라이브러리를 포함한 앱의 “디버그(Debug)” 빌드를 지칭하는 용어이다. 이 라이브러리는 기본 React Native 개발 도구에 기능을 확장해주며, 네트워크 요청 검사 지원, 실행 중인 다양한 개발 서버 간 전환, EAS Update로 배포된 앱 버전 간 전환 등을 할 수있는 ‘런쳐’ UI를 제공한다.

자세한 내용은 링크를 참고하자.


Config plugins: Introduction


네이티브 모듈을 프로젝트에 추가하는 자동 설정이 가능하다. 경우에 따라 모듈이 더 복잡한 설정을 요구하기도 하며, 이때 설정 플러그인을 사용하면 네이티브 프로젝트를 직접 수정하지 않고도 자동으로 설정을 구성해 복잡도를 줄일 수 있다.


What is a config plugin


설정 플러그인은 앱 설정을 확장하고 앱의 사전 빌드 프로세스를 커스텀 하기 위한 시스템이다. 이는 기본적으로 포함되지않은 네이티브 모듈을 추가하거나, 추가 설정이 필요한 네이티브 코드를 삽입하는데 사용할 수 있다.

내부적으로 Expo CLI는 설정 플러그인을 사용해 프로젝트를 관리하기 위한 네이티브 코드를 생성하고 구성한다. 플러그인은 앱 아이콘 생성, 앱 이름 설정, AndroidManifest.xml 및 Info.plist 구성 등의 작업을 수행한다.

플러그인은 네이티브 프로젝트를 위한 번들러처럼 생각할 수 있으며, npx expo prebuild 명령은 모든 프로젝트 플러그인을 평가하여 프로젝트를 번들링하는 과정이라 볼 수 있다. 이 작업을 실행하면 android와 ios 디렉터리가 생성된다. 생성된 이후에는 해당 디렉터리를 수동으로 수정할 수 있지만, 그렇게 하면 이후 안전하게 다시 생성할 수 없고 수동 수정 내용이 덮어써질 수 있다.


Use a config plugin


Expo 설정 플러그인은 대부분 Node.js 모듈에서 제공되며, 다른 패키지처럼 프로젝트에 설치할 수 있다.

예를 들어, expo-camera 는 AndroidManifest.xml 과 Info.plist에 카메라 권한을 추가하는 플러그인이다. 이를 설치하기 위해 다음 명령을 실행하면 된다.

1
npx expo install expo-camera


설치 후 앱 설정에서 expo-camera 를 플러그인에 추가하면 된다.

1
2
3
4
5
{
  "expo": {
    "plugins": ["expo-camera"]
  }
}


일부 설정 플러그인은 옵션을 전달해 구성을 커스텀할 수 있도록 유연성을 제공한다. 이를 위해, 첫 번째 인자로Expo 라이브러리 이름을, 두 번째 인자로 옵션을 담은 객체를 배열 형태로 전달하면 된다. 예를 들어, expo-camera 플러그인은 카메라 권한 메시지를 커스텀할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
{
  "expo": {
    "plugins": [
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera."
        }
      ]
    ]
  }
}


npx expo prebuild 를 실행하면 mods 가 컴파일 되고 네이티브 파일들이 수정된다.

이러한 변경 사항은 네이티브 프로젝트를 다시 빌드해야 적용된다. 예를 들어, Xcode로 빌드할 때 적용된다. 관리형 앱에서 설정 플로그인을 사용하는 경우, 해당 플러그인은 eas build 의 사전 빌드 단계에서 적용된다.


Config plugins: Plugins and mods


플러그인은 ExpoConfig 를 인자로 받아 수정된 ExpoConfig 를 반환하는 동기 함수이다.

  • 플러그인은 with<Plugin Functionality> (ex. withFacebook) 형식으로 이름을 지정해야 한다.
  • 플러그인은 동기 함수여야 하며, 반환 값은 직렬화 가능해야 한다(mods 제외).
  • 선택적으로 두 번째 인자를 전달해 플러그인을 설정할 수 있다.
  • pluginsexpo/configgetConfig 메서드로 설정을 읽을 때 항상 실행되지만, modsnpx expo prebuild 의 “syncing(동기화)” 단계에서만 실행된다.

자세한 내용은 링크를 참고하자.


Reference


https://docs.expo.dev

This post is licensed under CC BY 4.0 by the author.