【React.js】第二回 Reactでアプリ開発! 〜React×TypeScript環境を一から構築するぜ編〜

スポンサーリンク
PC創作
マイケル
マイケル
みなさんこんにちは!
マイケルです!
エレキベア
エレキベア
こんにちクマ〜〜
マイケル
マイケル
今日は前回に引き続きReactアプリ開発!
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

エレキベア
エレキベア
気前がいいクマね〜〜
スポンサーリンク

参考書籍

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

実践TypeScript

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

スポンサーリンク

TypeScriptのビルド環境構築

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

TypeScriptを使ってみる

マイケル
マイケル
プロジェクトフォルダ配下で下記コマンドを実行します!
// package.jsonの作成
npm init -y
// TypeScript関連のパッケージをインストール
npm install --save-dev typescript ts-loader
マイケル
マイケル
インストールしたら、TypeScriptの設定を記述するtsconfig.jsonというファイルをプロジェクト配下に作成します。
自身で作成しても問題ありませんが、下記tscコマンドを実行することでテンプレートファイルが作成されます!
// tscongif.jsonの作成
./node_modules/.bin/tsc --init
マイケル
マイケル
作成したファイルは下記のように記述されていて、
様々な設定をすることができます。
どんな設定があるのか気になる方は下記のマニュアルを参照してください。

What is a tsconfig.json


{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

↑TypeScriptの設定が記述されている

マイケル
マイケル
ここで補足ですが、TypeScriptはあくまで開発時の型チェックを行うためのものです。
Webで実行するためにはJavaScriptファイルにする必要があり、
最終的には TypeScriptからJavaScriptに変換(トランスパイル) されます。
ScreenShot 2021 07 11 0 07 16
↑JavaScriptに変換される
マイケル
マイケル
今回は TypeScriptの格納フォルダをsrc/ts配下
変換される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というファイル名で下記のように記述します。
.
├── 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関連のパッケージをインストール
npm install --save react react-dom
// @typesとついているものは型定義のパッケージである
npm install --save-dev @types/react @types/react-dom

↑React関連のパッケージをインストール

マイケル
マイケル
tsconfig.jsonには、jsxオプションを追加しましょう!
これだけでReactの設定は完了です!

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "src/js",
    "jsx": "react"
  },
  "include": [
    "src/ts/**/*"
  ]
}

↑jsxオプションを追加

エレキベア
エレキベア
お手軽クマ〜〜〜
マイケル
マイケル
それでは今度は下記のようなファイル構成にして、
簡単な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関数には引数の型が推論で指定されています。
エレキベア
エレキベア
違う型の値を入れようとするとエラーになるクマね
マイケル
マイケル
型チェックが入ることで、開発時にミスに気付けるのがメリットだね。
それではこちらのファイルも変換してみましょう!
./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に変換したりなど、トランスパイルも行なってくれる機能があるよ。
ScreenShot 2021 07 11 0 07 26
↑webpackのイメージ
エレキベア
エレキベア
HTML側では最終的に出力されたファイルだけを
読み込むようにすればいいわけクマね
マイケル
マイケル
下記コマンドでwebpack関連のパッケージをインストールします。
ちなみにwebpack-dev-serverは開発環境用のWebサーバを立てるためのパッケージです。
// webpack関連のパッケージをインストール
npm install --save-dev webpack webpack-cli webpack-dev-server

↑webpack関連のパッケージをインストール

マイケル
マイケル
インストールが完了したら、次はwebpackの設定を行います。
プロジェクト配下にwebpack.config.jsを作成して下記のように記述しましょう!

参考:TypeScript|webpack

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ファイルをバンドルしてくれます。
マイケル
マイケル
あとはwebpackのコマンドを実行できるようにするため、
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ファイルが出力されるかと思います。
npm run build

↑webpackでのビルド実行

エレキベア
エレキベア
bundle.jsというファイルが出力されたクマ
マイケル
マイケル
ビルドできるのを確認したら、次はwebサーバで実行してみましょう!
まず、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

↑アプリ起動

01 count app
↑ブラウザでアプリを起動できる
マイケル
マイケル
このようにアプリが起動すれば成功です!
エレキベア
エレキベア
起動したクマ〜〜〜!!!
スポンサーリンク

TODOアプリを移行する

マイケル
マイケル
これでTypeScriptとwebpackの基本的な設定は覚えたので、
前回作った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.jsonwebpack.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を追加しています。
.
├── 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間で共有することで型チェックを可能とするためです。
エレキベア
エレキベア
手間はかかるクマがこの方が安心感があるクマね
マイケル
マイケル
各ファイルの修正が完了したら実行してみましょう!
// devServer起動
npm run start
02 todo app
マイケル
マイケル
前回と変わらず実行することができました!
エレキベア
エレキベア
TSマスタークマ〜〜〜!!
スポンサーリンク

おわりに

マイケル
マイケル
というわけで今回はTypeScript環境の構築でした!
どうだったかな?
エレキベア
エレキベア
環境構築と聞いてなめてたクマが
いろいろ覚えることがあって大変だったクマ・・・
マイケル
マイケル
少しややこしいけど、フロントエンド開発だとモジュールバンドラを使用することは多いと思うからこれを機に覚えておこう!
マイケル
マイケル
それでは今日はこの辺で!
次回はついにこれまでよりも実践的なアプリを開発してみます!
お楽しみに〜〜!!
エレキベア
エレキベア
クマ〜〜〜〜〜

【React.js】第二回 Reactでアプリ開発! 〜React×TypeScript環境を一から構築するぜ編〜 〜完〜

※次回記事はこちら!

コメント