본문 바로가기

프로그램 개발

React Native로 PDF Viewer 앱 만들기; 7. React Navigation - 1

React Native로 PDF Viewer 앱 만들기

1. React Native 개발 환경 설치

2. react-native-pdf 테스트 앱

3. 화면 개발 - JSX 컴포넌트

4. 화면 개발 - Context API

5. 화면 개발- 팝업 창 (Modal)

6. TypeScript 전환, Jest 추가

7. React Navigation - 1

8. React Navigation - 2

 

지금까지 앱은 화면이 PDF를 보는 화면 하나만 있었다. 이제 내 핸드폰에 저장되어 있는 PDF 파일을 보는 기능을 추가하면서

  • 첫 화면이 "파일 목록을 보는 화면"이고
  • 두 번째 화면이 "PDF 파일을 보는 화면"

으로 나눌 것이다. 그래서 첫 번째 화면에서 파일을 선택하면 두 번째 화면에서 PDF를 보여주기 위해, 첫 번째 화면에서 두 번째 화면으로 이동하는 것이 필요하다. 

 

이 같은 페이지 이동을 위해 사용하는 패키지가 React (Native) Navigation이다.

 

React Navigation | React Navigation

Routing and navigation for your React Native apps

reactnavigation.org

React Native Navigation은 보통 앱에서 흔하게 보는 위쪽의 현재 페이지 위치 정보를 표현하는 Header Bar와 아래쪽 탭을 표시하고 이것을 사용해서 페이지 간의 이동을 쉽게 구현할 수 있게 해주는 패키지인데, 이 패키지를 이용해서 만든 위의 화면은 보이는 것처럼 두 개의 페이지에 Header Bar가 나타나 있고 페이지 정보와 페이지 이동에 필요한 아이콘을 설정 없이 표시해 주기 때문에 화면 간 이동을 쉽게 구현할 수 있다.

12. React Navigation을 사용한 화면 이동 구현

a. 필요한 패키지 설치

먼저 Navigation 기본 패키지를 설치한다.

 

npm install @react-navigation/native

 

expo를 사용할 경우 expo install로, pure native인 경우 npm install로 react-natvie-screens와 react-native-safe-area-context를 설치한다.

 

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

 

이제 상단에 나타나는 Header bar를 만들기 위해 react-navigation/native-stack을 설치한다.

 

npm install @react-navigation/native-stack

b. Navigation 사용을 위한 프로그램 구조 변경

Navigation과 관련된 모든 구현은 /src/index.tsx에 만들고, 파일 목록을 표시하는 페이지는 FileListPage, PDF를 보는 페이지는 ViewerPage로 각각 분리한다.

 

# /src/index.tsx
...
export type RootStackParamList = {
  FileListPage: undefined;
  ViewerPage: { source: string, title: string };
};

const RootStack = createNativeStackNavigator<RootStackParamList>();

const NavStack = () => {
  const { page } = useContext(PageContext);
  return (
    <RootStack.Navigator initialRouteName="FileListPage">
      <RootStack.Screen name="FileListPage" component={FileListPage}
        options={{ title: "PDF Viewer" }} />
      <RootStack.Screen name="ViewerPage" component={ViewerPage}
        options={(({ route }) => ({
          title: route.params.title,
          headerRight: () => (<Pagination page={page}/>)
            
        }))} />
    </RootStack.Navigator>
  )
}

export const MainPage = () => {
  return (
    <PageContextProvider>
      <NavigationContainer>
        <NavStack />
      </NavigationContainer>
    </PageContextProvider>

  );
}
...

 

코드를 많이 넣고 나니 블로그 길이가 길어져서, 상단의 import부분은 대부분 VSCode에서 자동으로 생성이 가능하니 빼고, 하단의 style부분도 생략했다. Stack Navigator는 화면 위쪽에 표시되면서 페이지 이동을 도와주는 컴포넌트인데 createNativeStackNavigator로 생성한 다음 <Stack.Navigator>로 감싸고 <Stack.Screen>으로 Navigation을 사용해서 이동할 화면을 지정한다. Stack이라고 불리는 이유는 모바일에서는 화면을 지우는 게 아니라 위로 쌓아서 아래 화면을 덮기(Stacking) 때문에 나온 말인 것 같은데, 화면 위쪽에 Header Bar를 사용하는 부분을 React Navigation에서는 StackNavigator로 부르고 있다.

 

이전에 TopBar로 구현한 Pagination정보를 Navigator로 옮기기 위해 PageContextProvider를 Navigator보다 위로 옮기고 전체를 감싸게 만들었다. ViewerPage는 PDFWapper를 사용해서 PDF를 보는 페이지로, FileListPage에서 선택된 File정보를 전달하기 위해 아래와 같이 만들었다.

 

# /src/ViewerPage.tsx

...
type Props = NativeStackScreenProps<RootStackParamList, 'ViewerPage'>;

export const ViewerPage = ({ route }: Props) => {
  return (
    <>
      <View style={styles.container}>
        <PDFWrapper style={styles.pdf}
          source={route.params.source}
        ></PDFWrapper>
      </View>
    </>
  );
}
...

 

index.tsx처럼, import는 VSCode에서 자동으로 생성되기 때문에 생략하고, style은 이전 코드에서 바뀐 부분이 없기 때문에 생략했다. 이전에 Modal을 사용해서 입력을 받으면서 로컬 상태를 저장할 때 사용했던 useState는 더 이상 필요 없어 제거하고, GetModal 컴포넌트를 빼고 나니 코드가 한결 간결해졌다. Navigation에서는 route.param을 통해 화면사이의 정보를 전달받을 수 있기 때문에 다른 Property 없이 route만 Property로 사용했다. 이 route.params에 전달하는 데이터 구조는 index.tsx의 RootStackParamList에서 정의해야 한다.

 

아마 TypeScript를 사용하지 않았으면 코드가 좀 더 짧아지고, 덜 복잡해 보였을 수는 있다. 하지만, 프로그램이 복잡해질수록 변수의 데이터 형이 어떻게 정의되었는지 찾기도 어렵고, 이 때문에 데이터형을 잘못사용해서 에러가 발생하기 쉽기 때문에 TypeScript을 사용해서 프로그램을 만들면, VSCode와 같은 IDE에서 언제든 필요할 떼 마우스 조작으로 데이터 정의를 찾을 수 있고, Code Completion을 사용할 수 있기 때문에 실수가 줄어든다.