React NativeにはデフォルトでFlowなる静的型付けライブラリが入っておりますが、TypeScriptを使いたい人もいるでしょう。
導入にあたり、Microsoftの公式ドキュメントがあるので早速試してみました。
こちらです。
と思ったのですが、この資料は2017年5月ごろのReact Nativeを元にしているので、現在のバージョンとは少々差があります。
なので、今の構成に合わせて書き換えてみました。なお、流れはそのままですが訳はかなり端折ってます。
また、今回のコードはこちらにあります。
前提
- 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.js
を src
ディレクトリに移動させます。
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
に変更します。
import './lib/index';
設定ファイルの作成
tsconfig.json
を作成します。
yarn add --dev typescript
yarn tsc --init --pretty --sourceMap --target es2015 --outDir ./lib --module commonjs --jsx react
出力されたファイルに include
の項目を追加します。
{
"compilerOptions": {
// other options here
},
"include": ["./src/"]
}
テストの設定
ts-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/",
"<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.js
を src/index.tsx
, src/App.tsx
にリネームします。
TypeScriptで解釈できるように 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.js
を 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
を作ります。
// 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
を作成します。
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.json
の types
の項目に以下を追加します。
{
"types": ["react", "react-native", "jest"],
}
error TS2717: Subsequent property declarations must have the same type.
こちらのIssueで討論されています。
差し当たりの解決策は tsconfig.json
に "skipLibCheck": true
を追加します。
これは型定義ファイルの整合性を無視する設定です。
正しい解決策がわかり次第追記します。