ヒキダスブログ

テック系や最近見たもの感じたことを書いて残す引き出しスペースです

React x TypeScript 学習 (1) - 開発環境

背景

現在、ReactとTypeScriptを扱う案件に加わっています。Reactは前職で3案件関わりましたが、そのうち1つが一部UIで取り入れ、他2案件は他のエンジニアのヘルプで参画した程度でした。 一方、TypeScriptは現職前に軽く電子書籍で見た位で、実践になるとVSCodeで色々エラー吐いて四苦八苦している状態です。

いずれも本質的な理解に至っていないので、この際少しずつでも学び備忘録としても書き留めて置こうと思った次第ではじめようと思いました。現状の理解レベルからシリーズ的な構成ではなく、都度感じた疑問や不明点を調べつらつら挙げていこうと考えています。

環境構築

今回は、React + TypeScriptの開発環境を作ってみようと思います。
ReactだとCreate React Appという雛形テンプレートを作るものがあり、オプションでTypeScriptも追加することができるようです。

npx create-react-app my-app --typescript  
  
# or  
  
yarn create react-app my-app --typescript  

facebook.github.io

でも、実案件だとカスタマイズしたものになりがちなので、一から作る前提で用意してみようと思います。
なお、こちらのサイトを参考にしました。

www.typescriptlang.org

ディレクトリ構造

参考サイトに沿って以下の階層で進めます。

project  ... プロジェクトルート  
  |- dist ... tsxファイルのビルド先ディレクトリ
  |- src ... エントリファイル(index.tsx)を格納
  |  |- components ... エントリファイルから読み込まれるコンポーネントファイルを格納
  |- index.html ... dist内のjsを読み込む  
  |  
  |- package.json ... npm initで作成
  |- tsconfig.json ... TypeScriptの設定情報
  |- webpack.config.js ... tsxのビルド設定

npm

以下用途別のパッケージが必要になります。
[ Reactセットアップに使用 ]
- react
- react-dom

[ ReactをTypeScript上で扱う際に必要な型定義ファイル ]
*@types/ というプレフィックスが付いていたりする
- @types/react
- @types/react-dom

[ webpackでtsxをビルドするawesome-typescript-loader、ビルド前ファイルでインスペクトできるsource-map-loader ]
* awesome-typescript-loaderの代わりにts-loaderでも良い
- awesome-typescript-loader
- source-map-loader

[ TypeScript、webpack環境 ]
- typescript
- webpack
- webpack-cli

これらを以下のようにパッケージ名の間をスペースで区切って複数パッケージをインストールします。個別にインストールしてもOK。

npm install --save-dev typescript webpack webpack-cli react react-dom @types/react @types/react-dom

参考サイトでは webpack をグローバルインストールしていますが、ローカルに閉じて管理しておきたかったのでその辺りを変えています。その関係で webpack-cli というパッケージも追加でインストールしています。

パッケージをインストールしたら、package.jsonがこのようになっていると思います(バージョンの違いはありますが)。
npm scriptsを使い、npm startをターミナルで叩いたらwebpackコマンドが実行されるようにしています。

{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/react": "^16.8.22",
    "@types/react-dom": "^16.8.4",
    "awesome-typescript-loader": "^5.2.1",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "source-map-loader": "^0.2.4",
    "typescript": "^3.5.2",
    "webpack": "^4.35.0",
    "webpack-cli": "^3.3.4"
  }
}

tsconfig.json

次は、プロジェクト内のts, tsxファイルの設定を司るtsconfig.jsonを見ていきます。

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es6",
    "jsx": "react"
  },
  "include": [
    "./src/**/*"
  ]
}

compilerOptionsでは「どのディレクトリにどういう形式で出力するのか」を指定できます。他にもtsxファイルでJSXをサポートするjsxオプションや、ソース上any型を許容しないnoImplicitAnyオプションも設定することができます。細かいオプション情報は以下リンクを参照すると良さそうです。

www.typescriptlang.org

webpack.config.js

そして、webpackコマンドを実行した際のwebpack設定を以下のように設定します。

module.exports = {
  mode: 'development',
  entry: './src/index.tsx',
  output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
  },
  devtool: 'source-map',
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json']
  },
  module: {
    rules: [
      { test: /\.tsx?$/, loader: 'awesome-typescript-loader' },
      { enforce: 'pre', test: /\.js?$/, loader: 'source-map-loader' }
    ]
  }
}

entry, outputオプションで読み込むエントリファイルと出力先とファイル名を指定しています。また、moduleオプションではtsxファイルに対し実行するloader、そして変換されたjsファイルに適用するloaderも設定しています。
なお、modeオプションも設定していないとビルド実行時にwarningが出てしまうので、忘れずに設定しておくと良さげです。

tsx

まずはエントリファイルである、src/index.tsx
Helloコンポーネント<div id="example"></div>要素にレンダリングしています。

import * as React from 'react'
import * as ReactDOM from 'react-dom'

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

ReactDOM.render(
  <Hello compiler="TypeScript" framework="React" />,
  document.getElementById('example')
)

それから、src/components/Hello.tsxのHelloコンポーネントコメントアウトしていますが、state管理していないため、関数型コンポーネントの書き方でもOKです。 TypeScriptは変数名: 型名で型指定を行うので、関数型は割とイメージしやすかったですが、クラス型の場合 React.Component<Props, State>のように第一引数にprops、第二引数にstateを指定するので、個人的に少し理解に手間取ってしまった感じです。

import * as React from 'react'

export interface Props {
  compiler: string
  framework: string
}

// - Functional Component
// export const Hello = (props: Props) => {
//   <h1>Hello from {props.compiler} and {props.framework}!</h1>
// }

// - Class Component
export class Hello extends React.Component<Props, {}> {
  render() {
    return (
      <h1>Hello from {this.props.compiler} and {this.props.framework}!</h1>
    )
  }
}

html

最後に、プロジェクト直下にindex.htmlを格納。ビルドして/dist/bundle.jsに出力されたファイルを読み込むようにしています。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>React x TypeScript practice</title>
</head>
<body>
  <div id="example"></div>

  <script src="./dist/bundle.js"></script>
</body>
</html>

ここまでやって、npm startをプロジェクト直下で実行した後ブラウザで見ると、「Hello from TypeScript and React!」という文字列が出ているでしょう。

振り返り

開発環境についてはこの辺りで、そこからオプショナルで追加していったり、React /TypsScriptの不明なところをピンポイントで消化していこうと思います。