Skip to main content

React Native에서 TypeScript 사용하기

· 13 min read
Ash Furrow
Artsy 소프트웨어 엔지니어

JavaScript! 모두가 좋아하는 언어다. 하지만 어떤 이들은 타입도 좋아한다. 다행히 JavaScript에 강력한 타입을 추가할 수 있는 옵션이 있다. 필자는 TypeScript를 선호하지만, React Native는 기본적으로 Flow를 지원한다. 어떤 것을 선택할지는 취향에 달렸다. 각각 JavaScript에 타입을 추가하는 방식이 다르기 때문이다. 오늘은 React Native 앱에서 TypeScript를 사용하는 방법을 살펴본다.

이 글은 Microsoft의 TypeScript-React-Native-Starter 저장소를 가이드로 삼는다.

업데이트: 이 블로그 글이 작성된 이후로 상황이 더 간단해졌다. 이 글에서 설명하는 모든 설정을 단 하나의 커맨드로 대체할 수 있다:

npx react-native init MyAwesomeProject --template react-native-template-typescript

하지만 Babel의 TypeScript 지원에는 몇 가지 제한 사항이 있다. 위 블로그 글에서 자세히 다룬다. 이 글에서 설명한 단계도 여전히 유효하며, Artsy는 여전히 react-native-typescript-transformer를 프로덕션에서 사용하고 있다. 하지만 React Native와 TypeScript를 가장 빠르게 시작하려면 위 커맨드를 사용하면 된다. 나중에 필요하면 언제든지 전환할 수 있다.

어쨌든, 즐겁게 진행해보자! 원본 블로그 글은 아래에서 계속된다.

사전 준비

여러분이 다양한 플랫폼에서 개발하거나 여러 타입의 디바이스를 대상으로 작업할 수 있기 때문에 기본 설정이 복잡할 수 있다. 먼저 TypeScript 없이 일반 React Native 앱을 실행할 수 있는지 확인해야 한다. React Native 공식 웹사이트의 시작하기 가이드를 따라 기본 설정을 완료한다. 디바이스나 에뮬레이터에 앱을 배포할 수 있게 되면, TypeScript를 사용한 React Native 앱 개발을 시작할 준비가 된 것이다.

또한 Node.js, npm, 그리고 Yarn이 필요하다.

초기화

일반적인 React Native 프로젝트를 스캐폴딩해본 후, TypeScript를 추가할 준비가 되었다. 바로 시작해보자.

react-native init MyAwesomeProject
cd MyAwesomeProject

TypeScript 추가하기

다음 단계는 프로젝트에 TypeScript를 추가하는 것이다. 아래 명령어들은 다음과 같은 작업을 수행한다:

  • 프로젝트에 TypeScript를 추가한다.
  • 프로젝트에 React Native TypeScript Transformer를 추가한다.
  • 빈 TypeScript 설정 파일을 초기화한다. 이 파일은 다음 단계에서 설정할 것이다.
  • 빈 React Native TypeScript Transformer 설정 파일을 추가한다. 이 파일도 다음 단계에서 설정할 것이다.
  • React와 React Native를 위한 타입 정의를 추가한다.

이제 아래 명령어를 실행해 보자.

yarn add --dev typescript
yarn add --dev react-native-typescript-transformer
yarn tsc --init --pretty --jsx react
touch rn-cli.config.js
yarn add --dev @types/react @types/react-native

tsconfig.json 파일은 TypeScript 컴파일러의 모든 설정을 포함한다. 위 명령어로 생성된 기본값은 대부분 적합하지만, 파일을 열고 다음 줄의 주석을 해제한다.

{
/* 설정 파일에서 다음 줄을 찾아 주석을 해제한다. */
// "allowSyntheticDefaultImports": true, /* 기본 내보내기가 없는 모듈에서 기본 import를 허용한다. 이 설정은 코드 생성에는 영향을 미치지 않고, 타입 검사에만 영향을 준다. */
}

rn-cli.config.js 파일은 React Native TypeScript Transformer의 설정을 포함한다. 파일을 열고 다음 내용을 추가한다.

module.exports = {
getTransformModulePath() {
return require.resolve('react-native-typescript-transformer');
},
getSourceExts() {
return ['ts', 'tsx'];
},
};

타입스크립트로 마이그레이션

생성된 App.js__tests_/App.js 파일을 App.tsx로 이름을 변경한다. index.js.js 확장자를 그대로 유지한다. 모든 새로운 파일은 .tsx 확장자를 사용한다. (JSX를 포함하지 않는 파일은 .ts 확장자를 사용한다.)

이 상태에서 앱을 실행하면 object prototype may only be an object or null과 같은 오류가 발생할 수 있다. 이 오류는 React의 기본 내보내기와 명명된 내보내기를 동일한 줄에서 가져오지 못해 발생한다. App.tsx 파일을 열고 상단의 import 문을 다음과 같이 수정한다:

-import React, { Component } from 'react';
+import React from 'react'
+import { Component } from 'react';

이 문제는 Babel과 타입스크립트가 CommonJS 모듈을 처리하는 방식의 차이에서 비롯된다. 향후 두 도구는 동일한 동작 방식으로 안정화될 것이다.

이제 React Native 앱을 실행할 수 있어야 한다.

타입스크립트 테스트 환경 설정

React Native는 기본적으로 Jest를 제공한다. 따라서 React Native 앱을 타입스크립트로 테스트하려면 devDependenciests-jest를 추가해야 한다.

yarn add --dev ts-jest

그런 다음 package.json 파일을 열고 jest 필드를 다음과 같이 교체한다:

{
"jest": {
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/babel-jest",
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/"
],
"cacheDirectory": ".jest/cache"
}
}

이 설정은 Jest가 .ts.tsx 파일을 ts-jest로 실행하도록 구성한다.

의존성 타입 선언 설치하기

TypeScript에서 최상의 경험을 얻으려면 타입 체커가 의존성의 구조와 API를 이해할 수 있도록 해야 한다. 일부 라이브러리는 .d.ts 파일(타입 선언/타입 정의 파일)을 패키지와 함께 배포한다. 이 파일은 기본 JavaScript의 구조를 설명한다. 다른 라이브러리의 경우, @types/ npm 범위에서 적절한 패키지를 명시적으로 설치해야 한다.

예를 들어, Jest, React, React Native, React Test Renderer에 대한 타입이 필요하다.

yarn add --dev @types/jest @types/react @types/react-native @types/react-test-renderer

이 선언 파일 패키지를 dev 의존성으로 저장했다. 이는 React Native 이 런타임이 아닌 개발 중에만 이 의존성을 사용하기 때문이다. NPM에 라이브러리를 배포한다면, 일부 타입 의존성을 일반 의존성으로 추가해야 할 수도 있다.

.d.ts 파일에 대해 더 자세히 알아보려면 여기를 참고한다.

더 많은 파일 무시하기

소스 컨트롤을 위해 .jest 폴더를 무시하고 싶을 것이다. git을 사용한다면 .gitignore 파일에 항목을 추가하면 된다.

# Jest
#
.jest/

체크포인트로, 파일을 버전 컨트롤에 커밋하는 것을 고려해 보자.

git init
git add .gitignore # 파일을 무시하기 위해 먼저 이 작업을 수행하는 것이 중요하다
git add .
git commit -am "Initial commit."

컴포넌트 추가하기

앱에 새로운 컴포넌트를 추가해 보자. Hello.tsx 컴포넌트를 만들어 보자. 이 컴포넌트는 실제 앱에서 사용할 만한 것은 아니지만, React Native에서 TypeScript를 어떻게 사용하는지 보여주는 교육용 예제이다.

components 디렉토리를 만들고 다음 예제를 추가해 보자.

// components/Hello.tsx
import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';

export interface Props {
name: string;
enthusiasmLevel?: number;
}

interface State {
enthusiasmLevel: number;
}

export class Hello extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

if ((props.enthusiasmLevel || 0) <= 0) {
throw new Error(
'조금 더 열정을 보여주세요. :D',
);
}

this.state = {
enthusiasmLevel: props.enthusiasmLevel || 1,
};
}

onIncrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel + 1,
});
onDecrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel - 1,
});
getExclamationMarks = (numChars: number) =>
Array(numChars + 1).join('!');

render() {
return (
<View style={styles.root}>
<Text style={styles.greeting}>
Hello{' '}
{this.props.name +
this.getExclamationMarks(this.state.enthusiasmLevel)}
</Text>

<View style={styles.buttons}>
<View style={styles.button}>
<Button
title="-"
onPress={this.onDecrement}
accessibilityLabel="감소"
color="red"
/>
</View>

<View style={styles.button}>
<Button
title="+"
onPress={this.onIncrement}
accessibilityLabel="증가"
color="blue"
/>
</View>
</View>
</View>
);
}
}

// 스타일
const styles = StyleSheet.create({
root: {
alignItems: 'center',
alignSelf: 'center',
},
buttons: {
flexDirection: 'row',
minHeight: 70,
alignItems: 'stretch',
alignSelf: 'center',
borderWidth: 5,
},
button: {
flex: 1,
paddingVertical: 0,
},
greeting: {
color: '#999',
fontWeight: 'bold',
},
});

이 코드는 상당히 길지만, 주요 부분을 살펴보자:

  • HTML 엘리먼트인 div, span, h1 등을 렌더링하는 대신, ViewButton 같은 네이티브 컴포넌트를 사용한다. 이 컴포넌트들은 다양한 플랫폼에서 동작한다.
  • 스타일은 React Native가 제공하는 StyleSheet.create 함수를 사용해 지정한다. React의 스타일시트는 플렉스 박스를 통해 레이아웃을 제어하고, CSS와 유사한 구문으로 스타일을 적용할 수 있다.

컴포넌트 테스트 추가하기

이제 컴포넌트를 만들었으니 테스트를 작성해 보자.

이미 테스트 러너로 Jest를 설치했다. 컴포넌트에 대한 스냅샷 테스트를 작성할 것이므로, 필요한 애드온을 추가한다:

yarn add --dev react-addons-test-utils

이제 components 디렉토리에 __tests__ 폴더를 만들고 Hello.tsx에 대한 테스트를 추가한다:

// components/__tests__/Hello.tsx
import React from 'react';
import renderer from 'react-test-renderer';

import {Hello} from '../Hello';

it('기본값으로 정상 렌더링된다', () => {
const button = renderer
.create(<Hello name="World" enthusiasmLevel={1} />)
.toJSON();
expect(button).toMatchSnapshot();
});

테스트를 처음 실행하면 렌더링된 컴포넌트의 스냅샷을 생성하고 components/__tests__/__snapshots__/Hello.tsx.snap 파일에 저장한다. 컴포넌트를 수정할 때는 스냅샷을 업데이트하고 의도치 않은 변경 사항을 검토해야 한다. React Native 컴포넌트 테스트에 대해 더 자세히 알고 싶다면 여기를 참고한다.

다음 단계

공식 React 튜토리얼과 상태 관리 라이브러리 Redux를 확인해 보자. 이 리소스들은 React Native 앱을 개발할 때 유용하다. 또한 ReactXP도 살펴볼 만하다. ReactXP는 TypeScript로 작성된 컴포넌트 라이브러리로, 웹용 React와 React Native를 모두 지원한다.

더 안전한 타입 시스템을 활용한 React Native 개발 환경에서 즐거운 코딩을 해보자!