
マイケル
みなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
こんにちクマ〜〜

マイケル
今日は前回に引き続きReactアプリ開発!
React × TypeScript環境を一から構築 していくよ!
React × TypeScript環境を一から構築 していくよ!

エレキベア
TypeScriptって何クマ?

マイケル
TypeScriptはMicrosoftが開発している言語で、JavaScriptを静的型付で記述できるようになるのが特徴なんだ!

マイケル
前回使ったcreate-react-appでも、–typescriptを付けることでテンプレートを作成することができるけど、今回はより細かいビルドの設定をするためにwebpackを使って一から環境を作っていくよ!

エレキベア
よく分からないクマが楽しみクマ〜〜

マイケル
それから今回もGitHubに一部ソースはあげているので
こちらも参考にご覧ください!
こちらも参考にご覧ください!
[対象フォルダ]
masarito617/react-study – GitHub
– 04_count-app-react-ts
– 05_todo-app-react-ts

エレキベア
気前がいいクマね〜〜
参考書籍

マイケル
参考書籍としては、下記を使用させていただきました!

マイケル
Reactとの組み合わせの他、Vue.jsやNext.js等との組み合わせ等も載っているため、フレームワークと組み合わせて使いたい方は読んでみることをおすすめします!

エレキベア
かっこいい表紙クマ〜〜〜
TypeScriptのビルド環境構築

マイケル
まずはwebpackを使う前に、
TypeScript単体の環境を構築して使ってみます!
TypeScript単体の環境を構築して使ってみます!
TypeScriptを使ってみる

マイケル
プロジェクトフォルダ配下で下記コマンドを実行します!
// package.jsonの作成
npm init -y
// TypeScript関連のパッケージをインストール
npm install --save-dev typescript ts-loader

マイケル
インストールしたら、TypeScriptの設定を記述するtsconfig.jsonというファイルをプロジェクト配下に作成します。
自身で作成しても問題ありませんが、下記tscコマンドを実行することでテンプレートファイルが作成されます!
自身で作成しても問題ありませんが、下記tscコマンドを実行することでテンプレートファイルが作成されます!
// tscongif.jsonの作成
./node_modules/.bin/tsc --init

マイケル
作成したファイルは下記のように記述されていて、
様々な設定をすることができます。
どんな設定があるのか気になる方は下記のマニュアルを参照してください。
様々な設定をすることができます。
どんな設定があるのか気になる方は下記のマニュアルを参照してください。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
↑TypeScriptの設定が記述されている

マイケル
ここで補足ですが、TypeScriptはあくまで開発時の型チェックを行うためのものです。
Webで実行するためにはJavaScriptファイルにする必要があり、
最終的には TypeScriptからJavaScriptに変換(トランスパイル) されます。
Webで実行するためにはJavaScriptファイルにする必要があり、
最終的には TypeScriptからJavaScriptに変換(トランスパイル) されます。

↑JavaScriptに変換される

マイケル
今回は TypeScriptの格納フォルダをsrc/ts配下、
変換されるJavaScriptファイルの格納場所をsrc/js配下として、
configファイルを下記のように修正します。
変換されるJavaScriptファイルの格納場所をsrc/js配下として、
configファイルを下記のように修正します。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "src/js"
},
"include": [
"src/ts/**/*"
]
}
↑格納フォルダ(outDir、include)の追記

マイケル
記述したら試しに変換してみましょう!
下記のようなフォルダ構成にし、test.tsというファイル名で下記のように記述します。
下記のようなフォルダ構成にし、test.tsというファイル名で下記のように記述します。
.
├── src
│ └── ts
│ └── test.ts
├── package.json
└── tsconfig.json
↑フォルダ構成
export function test() {
return "Hello, Michael"
}

マイケル
格納したら、下記コマンドでJavaScriptに変換します。
./node_modules/.bin/tsc
↑JavaScriptファイルに変換
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.test = void 0;
function test() {
return "Hello, Michael";
}
exports.test = test;
↑変換後のファイル

マイケル
正常に変換できれば、このようにsrc/jsファイル配下にJavaScriptファイルが格納されているはずです!

エレキベア
できたクマ〜〜〜
Reactを導入する

マイケル
次はReactを導入してTypeScriptで記述してみましょう!
下記コマンドでReact関連のパッケージをインストールします。
下記コマンドでReact関連のパッケージをインストールします。
// React関連のパッケージをインストール
npm install --save react react-dom
// @typesとついているものは型定義のパッケージである
npm install --save-dev @types/react @types/react-dom
↑React関連のパッケージをインストール

マイケル
tsconfig.jsonには、jsxオプションを追加しましょう!
これだけでReactの設定は完了です!
これだけでReactの設定は完了です!
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "src/js",
"jsx": "react"
},
"include": [
"src/ts/**/*"
]
}
↑jsxオプションを追加

エレキベア
お手軽クマ〜〜〜

マイケル
それでは今度は下記のようなファイル構成にして、
簡単なReactアプリを記述してみます。
簡単なReactアプリを記述してみます。
.
├── src
│ └── ts
│ ├── App.tsx
│ └── index.tsx
├── package.json
└── tsconfig.json
↑ファイル構成
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
import React from 'react'
import {useState} from 'react';
function App() {
// state取得
const [count, setCount] = useState(0);
const [message, setMessage] = useState("10未満");
// カウントを加算する
const doCountUp = (): void => {
// カウントを加算
let nextCount: number = count + 1;
setCount(nextCount);
// メッセージ更新
let nextMessage: string = "10未満";
let isTenOver: boolean = nextCount >= 10;
if (isTenOver) {
nextMessage = "10を超えたね";
}
setMessage(nextMessage);
}
return (
<div>
<h1>カウントAPP</h1>
<div>{count}</div>
<button onClick={doCountUp}>プラス</button>
<div>{message}</div>
</div>
);
}
export default App;

マイケル
変数名や間数名の後に:で型を指定することができます。
またReactHooksにも対応していて、例えば上記のsetCount関数には引数の型が推論で指定されています。
またReactHooksにも対応していて、例えば上記のsetCount関数には引数の型が推論で指定されています。

エレキベア
違う型の値を入れようとするとエラーになるクマね

マイケル
型チェックが入ることで、開発時にミスに気付けるのがメリットだね。
それではこちらのファイルも変換してみましょう!
それではこちらのファイルも変換してみましょう!
./node_modules/.bin/tsc
↑JavaScriptファイルに変換
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importDefault(require("react"));
var react_dom_1 = __importDefault(require("react-dom"));
var App_1 = __importDefault(require("./App"));
react_dom_1.default.render(react_1.default.createElement(react_1.default.StrictMode, null,
react_1.default.createElement(App_1.default, null)), document.getElementById('root'));
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importDefault(require("react"));
var react_2 = require("react");
function App() {
// state取得
var _a = react_2.useState(0), count = _a[0], setCount = _a[1];
var _b = react_2.useState("10未満"), message = _b[0], setMessage = _b[1];
// カウントを加算する
var doCountUp = function () {
// カウントを加算
var nextCount = count + 1;
setCount(nextCount);
// メッセージ更新
var nextMessage = "10未満";
var isTenOver = nextCount >= 10;
if (isTenOver) {
nextMessage = "10を超えたね";
}
setMessage(nextMessage);
};
return (react_1.default.createElement("div", null,
react_1.default.createElement("h1", null, "\u30AB\u30A6\u30F3\u30BF\u30FC"),
react_1.default.createElement("div", null, count),
react_1.default.createElement("button", { onClick: doCountUp }, "\u30D7\u30E9\u30B9"),
react_1.default.createElement("div", null, message)));
}
exports.default = App;

マイケル
このように変換されていれば導入は完了です!

エレキベア
やったクマ〜〜〜
webpackのビルド環境構築

マイケル
次はwebpackを使ったビルド環境を構築していきます!

エレキベア
さっきから何度も言ってるwebpackって何なのクマ?

マイケル
webpackはモジュールバンドラの一つで、複数のファイルを1つにまとめて出力(バンドル)してくれるツールなんだ!
更にローダーを指定するとTypeScriptをJavaScriptに変換したり、SassをCSSに変換したりなど、トランスパイルも行なってくれる機能があるよ。
更にローダーを指定するとTypeScriptをJavaScriptに変換したり、SassをCSSに変換したりなど、トランスパイルも行なってくれる機能があるよ。

↑webpackのイメージ

エレキベア
HTML側では最終的に出力されたファイルだけを
読み込むようにすればいいわけクマね
読み込むようにすればいいわけクマね

マイケル
下記コマンドでwebpack関連のパッケージをインストールします。
ちなみにwebpack-dev-serverは開発環境用のWebサーバを立てるためのパッケージです。
ちなみにwebpack-dev-serverは開発環境用のWebサーバを立てるためのパッケージです。
// webpack関連のパッケージをインストール
npm install --save-dev webpack webpack-cli webpack-dev-server
↑webpack関連のパッケージをインストール

マイケル
インストールが完了したら、次はwebpackの設定を行います。
プロジェクト配下にwebpack.config.jsを作成して下記のように記述しましょう!
プロジェクト配下にwebpack.config.jsを作成して下記のように記述しましょう!
const path = require('path');
module.exports = {
// 開発用モードで出力
mode: 'development',
// メインとなるファイル
entry: './src/ts/index.tsx',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
}
};

マイケル
modeには developement を指定することで開発用モードにすることができます。
そうすることで見やすい形でJavaScriptファイルをバンドルしてくれます。
そうすることで見やすい形でJavaScriptファイルをバンドルしてくれます。

マイケル
あとはwebpackのコマンドを実行できるようにするため、
package.json にコマンドを追記します。
package.json にコマンドを追記します。
{
"name": "04_todo-app_react-ts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/react-dom": "^17.0.9",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5",
"webpack": "^5.44.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
↑webpackコマンドの追記(build、start)

マイケル
ここまで完了したら下記コマンドでビルドしてみましょう!
distフォルダ配下にバンドルされたJavaScriptファイルが出力されるかと思います。
distフォルダ配下にバンドルされたJavaScriptファイルが出力されるかと思います。
npm run build
↑webpackでのビルド実行

エレキベア
bundle.jsというファイルが出力されたクマ

マイケル
ビルドできるのを確認したら、次はwebサーバで実行してみましょう!
まず、distフォルダ配下にindex.htmlを作成します。
まず、distフォルダ配下にindex.htmlを作成します。
.
├── dist
│ ├── bundle.js
│ └── index.html
├── src
│ └── ts
│ ├── App.tsx
│ └── index.tsx
├── package.json
└── tsconfig.json
<!doctype html>
<html>
<head>
<title>カウントAPP</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
↑index.htmlの作成

マイケル
そして下記のようにwebpack.config.js にwebサーバ用の設定を追記します!
const path = require('path');
module.exports = {
// 開発用モードで出力
mode: 'development',
// メインとなるファイル
entry: './src/ts/index.tsx',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
open: true,
contentBase: path.join(__dirname, 'dist'),
watchContentBase: true,
port: 8080,
}
};
↑devServerの設定を追記

マイケル
記述したら下記コマンドで実行!
// devServer起動
npm run start
↑アプリ起動

↑ブラウザでアプリを起動できる

マイケル
このようにアプリが起動すれば成功です!

エレキベア
起動したクマ〜〜〜!!!
TODOアプリを移行する

マイケル
これでTypeScriptとwebpackの基本的な設定は覚えたので、
前回作ったTODOアプリをTypeScript環境で作り直してみます!
前回作ったTODOアプリをTypeScript環境で作り直してみます!

エレキベア
Reduxスタイルで書いたアプリクマね
環境構築

マイケル
まずは必要なパッケージをインストールします!
多いですが、必要なものは分かっているので一気に実行しましょう。
多いですが、必要なものは分かっているので一気に実行しましょう。
// package.jsonの作成
npm init -y
// TypeScript関連のパッケージをインストール
npm install --save-dev typescript ts-loader
// React関連のパッケージをインストール
npm install --save react react-dom
npm install --save-dev @types/react @types/react-dom
// webpack関連のパッケージをインストール
npm install --save-dev webpack webpack-cli webpack-dev-server
// Redux関連もインストール
npm install --save redux react-redux redux-logger redux-thunk
npm install --save-dev @types/redux @types/react-redux @types/redux-logger @types/redux-thunk
↑必要パッケージのインストール

マイケル
パッケージをインストールしたら、tsconfig.jsonとwebpack.config.jsも先ほどと同様に作成します。
const path = require('path');
module.exports = {
// 開発用モードで出力
mode: 'development',
// メインとなるファイル
entry: './src/ts/index.tsx',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
open: true,
contentBase: path.join(__dirname, 'dist'),
watchContentBase: true,
port: 8080,
}
};
↑webpack.config.jsの記述
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "src/js",
"jsx": "react"
},
"include": [
"src/ts/**/*"
]
}
↑tsconfig.jsonの記述

マイケル
合わせてpackage.jsonにもコマンドを追加しておきましょう!
{
"name": "04_todo-app_react-ts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/react-dom": "^17.0.9",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5",
"webpack": "^5.44.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
↑build、startコマンドの追加
Reduxの型付け

マイケル
続いてソースの修正を行ないますが、フォルダの構成は以下にします!
基本的な構成は前回作ったものと同じですが、新たにActionTypes.tsxを追加しています。
基本的な構成は前回作ったものと同じですが、新たにActionTypes.tsxを追加しています。
.
├── dist
│ └── index.html
├── src
│ └── ts
│ ├── actions
│ │ ├── ActionTypes.tsx
│ │ └── TodoApp.tsx
│ ├── components
│ │ └── TodoApp.tsx
│ ├── index.tsx
│ └── reducers
│ └── TodoApp.tsx
├── package.json
└── tsconfig.json
↑フォルダ構成

マイケル
各ファイルを下記のように記述しましょう!
基本的には前と同じで、型を付与する対処を行っています。
基本的には前と同じで、型を付与する対処を行っています。
import { Action, Dispatch } from "redux";
import { ThunkAction } from "redux-thunk";
import ActionTypes from "./ActionTypes";
/* Action定義 */
export function inputTask(task: string) {
return {
type: ActionTypes.INPUT_TASK,
payload: {
task
}
};
}
export function addTask(task: string) {
return {
type: ActionTypes.ADD_TASK,
payload: {
task
}
};
}
export function resetTask() {
return {
type: ActionTypes.RESET_TASK,
payload: {}
};
}
export function asyncAddTask(task: string): ThunkAction<void, any, any, Action> {
return (dispatch: Dispatch<Action>) => {
setTimeout(() => {
dispatch(addTask(task));
}, 500);
};
}
// ActionをUnionTypeで型定義
// ※thunkを使ったActionにはtypeが含まれていないため省く
export type TodoAppActions =
ReturnType<typeof inputTask> |
ReturnType<typeof addTask> |
ReturnType<typeof resetTask>;
/* ActionTypes型定義 */
export = {
INPUT_TASK: 'INPUT_TASK',
ADD_TASK: 'ADD_TASK',
RESET_TASK: 'RESET_TASK'
} as const;
import {TodoAppActions} from "../actions/TodoApp";
import ActionTypes from "../actions/ActionTypes";
// stateの型定義
export type TodoAppState = {
task: string,
tasks: string[]
};
const initialState: TodoAppState = {
task: '',
tasks: []
};
export default function todoAppReducer(state: TodoAppState = initialState, action: TodoAppActions): TodoAppState {
switch (action.type) {
case ActionTypes.INPUT_TASK:
return {
...state,
task: action.payload.task
};
case ActionTypes.ADD_TASK:
return {
...state,
tasks: state.tasks.concat([action.payload.task])
};
case ActionTypes.RESET_TASK:
return {
...state,
tasks: []
};
default:
return state;
}
}
import React, {ChangeEvent} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {inputTask, addTask, resetTask, asyncAddTask} from "../actions/TodoApp";
import {TodoAppState} from "../reducers/TodoApp";
function TodoApp() {
const dispatch = useDispatch();
const task = useSelector((state: TodoAppState) => state.task);
const tasks = useSelector((state: TodoAppState) => state.tasks);
return (
<div>
<h1>TODO☆アプリ</h1>
<button onClick={() => dispatch(resetTask())}>リセット</button>
<br/>
<input placeholder="タスクを入力するクマ"
onChange={(e: ChangeEvent<HTMLInputElement>) => dispatch(inputTask(e.target.value))} />
<button onClick={() => dispatch(asyncAddTask(task))}>追加</button>
<ul>
{
tasks.map((item: string, i: number) => {
return <li key={i}>{item}</li>;
})
}
</ul>
</div>
);
}
export default TodoApp;
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux';
import {Provider} from 'react-redux';
import {logger} from "redux-logger";
import thunk from "redux-thunk";
import TodoApp from './components/TodoApp';
import todoAppReducer from "./reducers/TodoApp";
// storeの作成
const store = createStore(
todoAppReducer,
applyMiddleware(logger, thunk)
);
ReactDOM.render(
<Provider store={store}>
<TodoApp />
</Provider>,
document.getElementById('root')
);
<!doctype html>
<html>
<head>
<title>TODO APP</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>

マイケル
上記のように記述することで、Reduxにも型を適用することができます!
ちなみにActionTypes.tsxを追加したのは、ActionTypeをReducer、Action間で共有することで型チェックを可能とするためです。
ちなみにActionTypes.tsxを追加したのは、ActionTypeをReducer、Action間で共有することで型チェックを可能とするためです。

エレキベア
手間はかかるクマがこの方が安心感があるクマね

マイケル
各ファイルの修正が完了したら実行してみましょう!
// devServer起動
npm run start


マイケル
前回と変わらず実行することができました!

エレキベア
TSマスタークマ〜〜〜!!
おわりに

マイケル
というわけで今回はTypeScript環境の構築でした!
どうだったかな?
どうだったかな?

エレキベア
環境構築と聞いてなめてたクマが
いろいろ覚えることがあって大変だったクマ・・・
いろいろ覚えることがあって大変だったクマ・・・

マイケル
少しややこしいけど、フロントエンド開発だとモジュールバンドラを使用することは多いと思うからこれを機に覚えておこう!

マイケル
それでは今日はこの辺で!
次回はついにこれまでよりも実践的なアプリを開発してみます!
お楽しみに〜〜!!
次回はついにこれまでよりも実践的なアプリを開発してみます!
お楽しみに〜〜!!

エレキベア
クマ〜〜〜〜〜
【React.js】第二回 Reactでアプリ開発! 〜React×TypeScript環境を一から構築するぜ編〜 〜完〜
※次回記事はこちら!
コメント