본문 바로가기

프로그램 개발

React Native로 PDF Viewer 앱 만들기; 4. 화면 개발 - Context API

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

 

8. Context ; 컴포넌트 간 정보 전달 방법

React은 (React Native를 포함해서) 하향식 단방향 정보전달만 가능하다. 그래서 상하관계가 아닌 컴포넌트가 서로 정보를 전달하기 위해서는

 

  • 상하관계를 이용해서 두 컴포넌트가 속해있는 상위 컴포넌트의 상태(정보)를 바꾸거나
  • 컴포넌트 위치에 관계없이 정보를 전달할 수 있는 방법을 사용해야 한다.

부모 컴포넌트의 상태(정보)를 이용하는 방법은 가장 간단하게 구현할 수는 있지만 부모 컴포넌트가 바뀌면서 하위 컴포넌트도 다시 렌더링 되면서 깜빡이는(flickering)현상이 발생하는 경우가 많기 때문에 상황에 사용하기에 적절하지 않은 경우가 종종 발생한다. 컴포넌트 간 정보전달을 위한 공식적인 방법으로 React은 Context API를 제공한다. 물론 Redux와 같이 좀 더 복잡한 상태를 관리할 수 있는 방법도 있지만 남용할 경우 프로그램이 난해해져서 관리하기 힘들어지기도 한다.

 

이번에 필요한 것은 PDF의 전체 페이지 수와 현재 페이지 수에 대한 정보만 전달하면 되기 때문에 Context API를 사용해서 만들었다.

 

Context는 세 가지 부분으로 구성된다.

  • 상태(정보)를 저장하는 Context 객체
  • Context 객체를 전달하기 위해 사용하는 Provider,
  • Context의 내용을 사용하는 Consumer

로 구성이 되는데, 이 중 Context 객체와 Provider를 아래와 같이 하나의 파일로 만들고 /src/store 폴더를 만든 다음 저장했다.

 

# /src/store/PageContext

import React, { createContext, useState } from "react";

const PageContext = createContext();

export const PageContextProvider = ({ children }) => {
    const [page, setPage] = useState({ total: -1, current: -1 });

    return (
        <PageContext.Provider value={{ page, setPage }}>
            {children}
        </PageContext.Provider>
    );
}

export default PageContext;

 

Provider에서 value로 정의한 부분을 Consumer에서 사용할 수 있는데, PageContext에서는 { page, setPage }의 두 함수를 전달한다. page와 setPage는 또한 로컬 상태를 저장하는 hook인 useState로 만든 함수이기 때문에 setPage로 정보를 변화시키면 page를 사용하는 컴포넌트는 자동으로 다시 그려진다. (Redering 된다.) 

 

PDFWrapper에서는 Page가 바뀔 때마다 호출되는 onPageChanged에 setPage Hook를 사용해서 값을 전달한다. Context로 전달하는 함수를 줄이기 위해 값은 total과 current페이지를 묶어 하나로 전달한다.

 

# /src/components/PDFWrapper

export const PDFWrapper = () => {
    const {page, setPage} = useContext(PageContext);
...
    return (
        <View style={styles.container}>
            <Pdf
                trustAllCerts={false}
                source={source}
                onPageChanged={(page,numberOfPages) => {
                    setPage({ total: numberOfPages, current: page});
                }}
                onError={(error) => {
                    console.log(error);
                }}
                onPressLink={(uri) => {
                    console.log(`Link pressed: ${uri}`);
                }}
                style={styles.pdf}/>
        </View>
    )
}

 

이제 PageWrapper에서 TopBar의 Pagination 컴포넌트로 Context를 통해 정보가 전달될 수 있도록 Provider컴포넌트인 PageContextProvider로 감싸면 자식 컴포넌트에서 { page, setPage }로 만들어진 Context를 사용할 수 있게 된다.

 

# /src/index.js

...
import { PageContextProvider } from './store/PageContext';
import { PDFWrapper } from './components/PDFWrapper';

export const MainPage = () => {
    return (
        <PageContextProvider>
            <View style={styles.container}>
                <TopBar></TopBar>
                <PDFWrapper style={styles.pdf}></PDFWrapper>
            </View>
        </PageContextProvider>
    );
}
...

 

Pagination컴포넌트는 전체 page, total과 현재 페이지 번호, current를 받아 <Text > 컴포넌트로 표시하도록 하면 앱의 상단에 페이지의 번호가 표시된다.

 

# /src/components/Pagination

...
export const Pagination = ({total, current}) => {
    return (
        <View style={style.container}>
            <Text style={style.text}>
                Page {current} / {total}
            </Text>
        </View>
    );
}
...

 

Context API가 반영된 앱을 실행하면 아래와 같이 페이지 정보가 표시되는 것을 확인할 수 있다.