본문 바로가기

프로그램 개발

React Native로 PDF Viewer 앱 만들기; 6. TypeScript 전환, Jest 추가

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 Viewer를 시작할 때는 React Native를 사용하는 방법을 알려 줄 목적으로 만든 예제였는데, 우리 딸이 자기가 원하는 기능을 넣어서 사용할 수 있는 앱으로 만들어 달라고 요구하는 바람에 정식으로 앱을 만들어서 스토어에 무료 앱으로 등록하는 것으로 목적을 바꾸었다. 그래서 이제부터는 일반적인 프로젝트에서 개발하듯이 유지보수할 수 있게 TypeScript로 바꾸고 필요한 부분에 테스트 케이스를 추가해 넣기로 했다.

10. TypeScript 프로젝트로 전환

먼저 지금까지 사용하던 프로젝트 폴더는 백업으로 바꾸고 TypeScript 프로젝트를 만들어 만들어 놓은 컴포넌트를 마이크레이션 하기로 했다.

 

npx create-expo-app --template blank-typescript pdf-viewer

 

이제 .js를 TypeScript으로 전환하기로 하고 먼저, /src/store/PageContext.js를 TypeScript로 바꾸었다. 확장자는 JSX가 포함되어 있으면 .tsx로, 포함되어 있지 않으면 .ts로 지정한다.

 

# /src/store/PageContext.tsx

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

export type Page = {
    total: number;
    current: number;
}

interface IPageContext {
    page: Page;
    setPage: (page: Page) => void;
}

const initial: Page = {
    total: -1,
    current: -1
}

const PageContext = createContext<IPageContext>({
    page: initial,
    setPage: (page: Page) => {}
});

type Props = {
    children?: ReactNode
}

export const PageContextProvider = (props: Props) => {
    const [page, setPage] = useState<Page>(initial);

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

export default PageContext;

 

.js로 작성 (15 lines) 했을 때보다 2배 이상(38 lines) 길어졌다. 그럼 왜 TypeScript을 사용하는 것일까?

 

  • TypeScript를 사용하게 된 이유

JavaScript는 데이터 형에 대한 강제사항이 없을 뿐 아니라 값을 할당하면 데이터 형이 바뀌는 동적타입 언어이다. 그래서 흔히 발생하는 오류 중 하나가 HTML의 input 태그로 가져온 문자를 숫자처럼 더했을 때, 더하는 쪽이 이미 숫자이면 자연스럽게 숫자로 바꾸고 숫자의 덧셈을 하지만, 더하는 쪽도 문자열이면 문자와 문자가 Append 되는 결과를 낳는다. 하지만 이런 류의 에러는 찾기 쉽지 않기 때문에 디버깅에 많은 시간을 낭비할 수 있다. 이런 문제를 방지하기 위해 JavaScript에 데이터형을 강제하는 방법으로 TypeScript가 만들어졌다.

 

다시 PageContext를 TypeScript으로 바꾼 위 예를 보면 PageContext를 만들 때 createContext<IPageContext>와 같이 데이터 형을 지정함으로 만들어진 데이터가 IPageContext형으로 선언되게 하고, 개발하면서 마우스를 사용하거나 해서 데이터 형을 간단히 조회할 수 있게 된다. 같은 방법으로 setPage로 설정하는 값이 Page Type을 사용하고 이 데이터 형에 맞지 않는 데이터를 setPage에 할당할 경우 에러를 발생시킨다. 이런 작업들은 컴파일 과정 없이 바로 실행되는 JavaScript에서는 확인하기 어렵지만 TypeScript는 데이터 형을 확인하고 JavaScript로 바꿔주는 작업을 진행하면서 데이터 형과 관련된 문제를 사전에 막아주는 역할을 한다.

 

이를 통해 오류를 많이 줄일 수 있지만 역설적으로 개발할 때 손이 더 갈 수밖에 없게 되었다. 하지만, 모든 프로그램이 PageContext처럼 많이 바뀌는 것은 아니고, 대부분을 외부 모듈에 의존해서 쓰는 Custom 컴포넌트의 경우 Type만 추가하면 TypeScript로 변경이 가능하다. 아래는 Button.js를 TypeScript로 변환한 예이다.

 

# /src/components/Button.tsx

import React, { ReactNode } from "react";
import { StyleProp, StyleSheet, Text, TouchableOpacity, ViewStyle } from "react-native";

type Props = {
    title: string,
    onPress: () => void,
    style?: StyleProp<ViewStyle>,
}

export const Button = ({title, onPress, style}: Props): ReactNode => {
    return (
        <TouchableOpacity onPress={onPress} style={[styles.button, style]} activeOpacity={0.6}>
            <Text style={[styles.text]}>{title}</Text>
        </TouchableOpacity>
    );
}

const styles = StyleSheet.create({
    button: {
        paddingVertical: 5,
        paddingHorizontal: 10,
        backgroundColor:"#007bff",
        borderRadius: 5,
        elevation: 5,
    },
    text: {
        color: '#fff'
    }
})

 

프로그램에서 볼 수 있듯이 컴포넌트로 들어오는 Property에 대해서 Type을 선언해 주는 것 외에는 많은 부분이 바뀌지 않았다. 물론 마지막에 styles로 형을 지정할 수 있지만 any로 형을 지정해야 할 경우라면 지정하지 않아도 사실 무방하다. JavaScript대신 TypeScript을 쓰는 이유는 Type을 지정해서 오류를 방지하는 것인데 Type이 any이면 아무런 의미가 없기 때문이다. 개인적인 생각으로는 오류를 방지하는데 도움을 주지 않는 부분에 대해 무리하게 Type을 지정하기 위해 시간을 소모할 필요는 없다고 생각한다.

11. Jest 추가; 테스트를 위한 준비

프론트 개발에서는 백엔드와 다르게 테스트 케이스를 엄격하게 적용하는 것이 효율적이지 않다. 하지만 API통신은 한다거나, Store에 데이터를 넣고 빼는 등의 로직이 들어간다면, 테스트 케이스를 만드는 것이 좋을 것이다. 물론, 화면에 대해서도 Jest로 테스트를 하는데 개별속성에 대해 변화를 추적하는 것도 좋지만, Snapshot를 뜨고 변화를 테스트하는 방법이 더 효율적일 수 있다.

 

create-expo-app 명령으로 blank 또는 blank-typescript 템플릿을 사용해서 프로젝트를 만들면 jest가 자동으로 설치되지 않기 때문에 다음과 같은 작업을 통해 Jest를 사용할 수 있도록 만들어야 한다.

 

npm install --save-dev jest
# 또는
npm i -D jest

 

TypeScript을 사용한다면 아래로 설치하고

 

npm install -D @types/jest

 

 

ts-node가 없다는 에러가 나온다면

 

npm install -D ts-node

 

로 설치하고, tsconfig.json 에 다음을 추가하면 에러가 사라진다.

 

...
  "ts-node": {
    "transpileOnly": true,
    "files": true,
    "compilerOptions": {
      "rootDir": "."
    }
...

 

설치가 완료되면 프로젝트 폴더에서

 

npm init jest

 

를 실행하면 jestconfig 파일을 만들 수 있다.

 

마지막으로 package.json파일에 "test": "jest"를 추가하면 npm test로 jest를 실행해서 테스트 케이스를 실행할 수 있다.

 

...
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "test": "jest"
  },
...