はじめに
TypeScript Advent Calendarの初日ということで、TypeScriptの導入についての記事を書いてみました。
主に「いまReactを使っていて、TypeScriptも使ってみたい!」という方に向けた記事です。
最初にTypeScriptについて触れておくと、
TypeScriptは「型を持ったJavaScriptのスーパーセットです」
TypeScriptの公式サイト
TypeScriptのリポジトリ
- Microsoftによって開発され、メンテナンスされているオープンソースのプログラミング言語
- クラスベースのオブジェクト指向
- 型情報をもとに実行前にエラーを検出でき、コンパイルする事でJavaScriptのコードにトランスパイルされる
- ES2015移行で書かれたコードをES5にトランスパイルできる
- JSXをサポートしているので、Reactとも併用できる
という特徴を持っています。
Google Trendsを見ても、その人気が分かると思います。
TypeScriptのトレンド動向
それではReactのプロジェクトにTypeScriptを導入する流れを紹介します。
導入するReactのプロジェクト
今回、Reactで書かれたカウンターアプリをサンプルとして使います。
サンプルプロジェクトのリポジトリはこちらを参照ください。
プロジェクトの構成は以下の通りです。
- index.jsを起点にしたReactのコンポーネント
- トランスパイルやコンパイルはwebpack 4 + Babel 7
$ tree (master)
.
├── dist
│ ├── bundle.js
│ └── index.html
├── package.json
├── .babelrc
├── src
│ ├── index.html
│ ├── index.js
│ └── js
│ └── components
│ ├── container
│ │ └── CounterContainer.js
│ └── presentational
│ └── Button.js
└── webpack.config.js
TypeScriptの導入
それでは紹介したカウンターアプリにTypeScriptを導入します。
※ 新規でTypeScriptの環境を構築する場合はcreate-react-appで簡単に行えます。
今回変更した内容はこのプルリクエストで確認できます。
TypeScriptを用いたReactのファイルは拡張子が.tsx
になります。
なので、今回は「Reactのファイルが全て.tsx
になり、コンパイルが通る」ことがゴールです。
1. TypeScriptのインストール
まずはTypeScriptに必要なライブラリをインストールします。
$ npm i typescript ts-loader --save-dev
ts-loaderは、webpackの中でTypeScriptで書かれたファイルをjsファイルに変換するためにインストールします。
2. webpackの設定(webpack.config.js)
webpackの設定をTypeScript(tsx)用に変更します。
webpack.config.jsの変更内容は以下の通りです。
module.exports = {
- entry: './src/index.js',
+ entry: './src/index.tsx',
...
resolve: {
- extensions: ['*', '.js', '.jsx']
+ extensions: ['.ts', '.tsx', '.js']
},
module: {
rules: [
{
- test: /\.(js|jsx)$/,
+ test: /\.tsx?$/,
exclude: /node_modules/,
use: {
- loader: "babel-loader"
+ loader: "ts-loader"
}
...
};
entryや拡張子、そして先程インストールしたts-loader
にloaderを変更します。
3. TypeScriptの設定(tsconfig.json)
次にTypeScriptのコンパイルに必要な設定ファイルとしてtsconfig.json
を追加します。
$ ./node_modules/.bin/tsc --init
.tsconfig.json
でjsxの有効化やトランスパイルのターゲット、出力するモジュール方式などを設定します。
noUnusedLocals
やnoUnusedParameters
など自由に設定してください。
tsconfig.json
の各種プロパティはこちらのTypeScript HandBook tsconfig.jsonを参照してください。
今回は最小限以下のように設定しました。
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "ES2015",
"target": "es5",
"jsx": "react"
}
}
4. 型定義ファイル@types/
のインストール
TypeScriptではnpmパッケージとして型定義ファイルが提供されています。(TypeScript2.0系から)
Reactなどのnpmライブラリを使う場合、型情報として@types/xxxを一緒にインストールします。
またReduxのようにパッケージに型定義が同梱されている場合はインストールは不要です。
ReactでTypeScriptを利用する場合、Reactの型定義ファイル(@types/react
と@types/react-dom
)をインストールする必要があります。
$ npm i @types/{react,react-dom} --save-dev
5. 不要になったファイルやライブラリを削除
TypeScriptを導入したことで、不要になったファイルやライブラリを削除します。
ES5へのトランスパイルやjsx記法のコンパイルはTypeScriptで行うことができるのでBabel
が不要になります。
また、propsの型情報はTypeScriptで定義するのでprop-types
も不要になります。
$ npm remove @babel/core @babel/preset-env @babel/preset-react babel/loader
$ npm remove prop-types
$ rm .babelrc
6. Reactコンポーネントのファイルに型を書く(js -> tsx)
最後にReactコンポーネントに型を導入して、コンパイルを行います。
まずは拡張子を.js
から.tsx
にリネームしましょう。
$ git mv src/index.js src/index.tsx
$ git mv src/js/components/container/CounterContainer.js src/js/components/container/CounterContainer.tsx
$ git mv src/js/components/presentational/Button.js src/js/components/presentational/Button.tsx
あとはファイルの中身を書き換えていきます。
index.tsx
の変更点は以下の通りです。
- import React, { Component } from "react";
- import ReactDOM from "react-dom";
+ import * as React from 'react';
+ import * as ReactDOM from 'react-dom';
import CounterContainer from "./js/components/container/CounterContainer";
const rootEl = document.getElementById('root')
rootEl ? ReactDOM.render(<CounterContainer />, rootEl) : false;
ここでは、react
とreact-dom
のimportの書き方が変わります。
次にCounterContainer.tsx
を修正します。
変更点は以下の通りです。
- import React, { Component } from "react";
+ import * as React from 'react';
import Button from "../presentational/Button";
- class CounterContainer extends Component {
- constructor() {
- super();
- this.state = {
- count: 0
- }
- this.countUp = this.countUp.bind(this)
- }
- countUp() {
+ interface Props {}
+ interface State {
+ count: number;
+ }
+ export default class CounterContainer extends React.Component<Props, State> {
+ public state: State = {
+ count: 0
+ };
+ public countUp = () => {
this.setState({count: this.state.count + 1})
}
render() {
return (
<div>
<div>count:{this.state.count}</div>
<Button label="count up!" onClick={this.countUp}/>
</div>
)
}
}
- export default CounterContainer;
class Componentの定義はReact.Component<P, S>
をextendsして作成します。
P
がpropsの型、S
がstateの型となります。
最後にButton.tsx
を修正します。
変更点は以下の通りです。
- import React from "react";
- import PropTypes from "prop-types";
+ import * as React from 'react';
+ interface Props {
+ label: string;
+ onClick: () => void;
+ }
- const Button = ({ label, onClick }) => (
+ const Button = (props: Props) => (
- <button onClick={onClick}>{label}</button>
+ <button onClick={props.onClick}>{props.label}</button>
);
- Button.propTypes = {
- label: PropTypes.string.isRequired,
- onClick: PropTypes.func.isRequired
- };
-
export default Button;
propTypesではなく、TypeScriptでpropsの型を定義します。
これでtsx
ファイルの修正は終わりです。
7. tsxファイルをコンパイルする
最後に、これでコンパイルが通ることを確認します。
$ npm run build
> webpack --mode production
Hash: 18d8fcfcb0ded510ffa2
Version: webpack 4.26.1
Time: 1776ms
Built at: 2018-11-30 21:14:11
Asset Size Chunks Chunk Names
./index.html 281 bytes [emitted]
bundle.js 109 KiB 0 [emitted] main
Entrypoint main = bundle.js
[7] ./src/index.tsx + 2 modules 1.88 KiB {0} [built]
| ./src/index.tsx 279 bytes [built]
| + 2 hidden modules
+ 7 hidden modules
Child html-webpack-plugin for "index.html":
1 asset
Entrypoint undefined = ./index.html
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 265 bytes {0} [built]
無事、コンパイルすることができました!
コンパイル済みのファイルはdist/bundle.js
に出力され、dist/index.html
から読み込まれます。
最終的な構成は以下のようになりました。
$ tree
.
├── dist
│ ├── bundle.js
│ └── index.html
├── package.json
├── src
│ ├── index.html
│ ├── index.tsx
│ └── js
│ └── components
│ ├── container
│ │ └── CounterContainer.tsx
│ └── presentational
│ └── Button.tsx
├── tsconfig.json
└── webpack.config.js