LoginSignup
6

More than 5 years have passed since last update.

Microsoft公式ドキュメントでReact NativeにTypeScriptを導入する

Posted at

React NativeにはデフォルトでFlowなる静的型付けライブラリが入っておりますが、TypeScriptを使いたい人もいるでしょう。
導入にあたり、Microsoftの公式ドキュメントがあるので早速試してみました。

こちらです。

Microsoft/TypeScript-React-Native-Starter

と思ったのですが、この資料は2017年5月ごろのReact Nativeを元にしているので、現在のバージョンとは少々差があります。
なので、今の構成に合わせて書き換えてみました。なお、流れはそのままですが訳はかなり端折ってます。

また、今回のコードはこちらにあります。

taquaki-satwo/TypescriptInReactNative

前提

  • Node.jsおよびnpmまたはYarnがインストールされていること
  • TypeScriptなしでReact Nativeアプリが実行できる環境が整っていること

基本的なReact Nativeアプリの作り方は こちら を参照してください。

初期設定

まずはReact Nativeプロジェクトを作成します。

react-native init MyAwesomeProject

ファイル構成を変える

React Nativeのjsファイルは、React Native Packagerが用いるBabelで変換されバンドルされます。
つまり、TypeScriptから出力されたファイルをReact Native Packagerに渡す形になります。

まずはエントリーポイントの index.js と中身の App.jssrc ディレクトリに移動させます。

mkdir src
mv index.js App.js src

新たにエントリーポイントとなるファイル index.js を作成し、 src/index.js を読み込みます。

echo "import './src/index';" > index.js

テストディレクトリもsrc配下に移動します。

mv ./__tests__/ ./src/__tests__/

ここで一旦動作確認をします。

react-native run-android
react-native run-ios

テストが通るのも確認します。

yarn test

ここまで確認できたら、gitなどのバージョン管理ツールでコミットしておきましょう。

TypeScriptの導入

さきほどのエントリーポイントのimport元を src から lib に変更します。

index.js
import './lib/index';

設定ファイルの作成

tsconfig.json を作成します。

yarn add --dev typescript
yarn tsc --init --pretty --sourceMap --target es2015 --outDir ./lib --module commonjs --jsx react

出力されたファイルに include の項目を追加します。

tsconfig.json
{
    "compilerOptions": {
        // other options here
    },
    "include": ["./src/"]
}

テストの設定

ts-jestをインストールします。

yarn add --dev ts-jest

package.jsonのjestの項目を以下のように書き換えます。

package.json
"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/",
        "<rootDir>/lib/"
    ],
    "cacheDirectory": ".jest/cache"
}

型定義ファイル

Jest、React、React Native、React Test Rendererで使用するTypeScriptの型定義ファイルをインストールします。

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

定義ファイルについて詳しくは こちら

TypeScriptファイルの作成

src/index.js , src/App.jssrc/index.tsx, src/App.tsx にリネームします。

TypeScriptで解釈できるように App.tsx の以下の部分を書き換えます。

App.tsx
// import React, { Component } from 'react'; 
import * as React from 'react';

...中略

// export default class App extends Component<Props> {
export default class App extends React.Component<object, object> {

また、flowは使わないのでファイルのコメントから @flow を削除します。

次にテストファイルも同様に変更していきます。

src/__test__/App.jssrc/__test__/App.tsx にリネームして以下を書き換えます。

src/__test__/App.tsx
// import React, {Component} from 'react';
import * as React from 'react';

...中略

// import renderer from 'react-test-renderer';
import * as renderer from 'react-test-renderer';

ここでTypeScriptを実行します。

yarn tsc

正しくlibディレクトリにファイルが出力されているのが確認できたら、ここでコミットしておきます。

実際はここで
error TS2300: Duplicate identifier 'require'. や
error TS2717: Subsequent property declarations must have the same type.
といったエラーが出るはずです。解決策を記事の終わりに記載しました。

ignoreファイルを追加する

.gitignoreに以下を追記します。

# TypeScript
#
lib/

# Jest
#
.jest/

コンポーネントの作成

src/components/ ディレクトリを作成し以下の内容で Hello.tsx を作ります。

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

export interface Props {
  name: string;
  enthusiasmLevel?: number;
  onIncrement?: () => void;
  onDecrement?: () => void;
}

function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
  if (enthusiasmLevel <= 0) {
    throw new Error('You could be a little more enthusiastic. :D');
  }

  return (
    <View style={styles.root}>
        <Text style={styles.greeting}>
        Hello {name + getExclamationMarks(enthusiasmLevel)}
        </Text>
        <View style={styles.buttons}>
            <View style={styles.button}>
            <Button title="-" onPress={onDecrement || (() => {})} accessibilityLabel="decrement" color='red' />
            </View>
            <View style={styles.button}>
                <Button title="+" onPress={onIncrement || (() => {})}  accessibilityLabel="increment" color='blue' />
            </View>
        </View>
    </View>
  );
}

export default Hello;

// styles

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"
    }
});

// helpers

function getExclamationMarks(numChars: number) {
  return Array(numChars + 1).join('!');
}

コンポーネントのテスト

上記のコンポーネントのテストを作成します。
まず、Reactコンポーネント用テストユーティリティのEnzymeをインストールします。

yarn add --dev enzyme @types/enzyme react-addons-test-utils react-dom

src/components/__tests__/ ディレクトリを作成し、以下の内容で Hello.tsx を作成します。

src/components/__tests__/Hello.tsx
import * as React from 'react';
import { Text } from 'react-native';
import { shallow } from 'enzyme';

import Hello from '../Hello';

it('renders correctly with defaults', () => {
    const hello = shallow(<Hello name="World" />);
    expect(hello.find(Text).render().text()).toEqual("Hello World!");
})

次のステップ

TypeScriptコンパイル時のエラー解決

error TS2300: Duplicate identifier 'require'.

定義が重複した場合のエラーだそうです。対策としては tsconfig.jsontypes の項目に以下を追加します。

{
  "types": ["react", "react-native", "jest"],
} 

Is there a conflict between @types/node and @types/react-native? · Issue #15960 · DefinitelyTyped/DefinitelyTyped

error TS2717: Subsequent property declarations must have the same type.

こちらのIssueで討論されています。

[@types/react-native] Property 'geolocation' must be of type 'Geolocation', but here has type 'GeolocationStatic'. · Issue #24573 · DefinitelyTyped/DefinitelyTyped

差し当たりの解決策は tsconfig.json"skipLibCheck": true を追加します。
これは型定義ファイルの整合性を無視する設定です。
正しい解決策がわかり次第追記します。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6