react + typescriptの開発環境を、モジュールちょっとずつ入れながら作ってみる

# この記事について

主にバックエンドしか触る機会がなく、フロントエンドに弱い自分の備忘録です。

reactを触った事なく、どんなものか手元で触ってみたいな〜と思ってはいるものの、
`jsx` `webpack` `babel`とか何よ??
って状態だったので、触ってみましたというお話。

公式にあるようにreact+typescriptの環境って
```
npx create-react-app {適宜} --template typescript
```
と叩けば一式揃いますが、

何がどう入って何をしてくれているのか、無知な私にはよく理解できなかったので、
一個一個入れて確認していきながら、
ググって得た超基礎知識のおさらいをしていきますという記事になります。

# 目次

- 環境構築
    - webpack入れる
    - react入れる
    - jsxの記法を使ってみる
    - typescriptで書いてみる
- 最後に

# 環境構築

では環境作って行きます。

## webpack入れる

まずは、package.json作ります
```
npm init
```

次に、jsやcss、画像なんかを一個にまとめてるくれるモジュールバンドラであるwebpackを入れます。
cliとローカルサーバ立ち上げてくれる`webpack-dev-server` も入れます。
ローカルにしかいらないので `--save-dev` (`-D`)を指定しておきます。

```
npm install --save-dev webpack webpack-cli webpack-dev-server
```

ここまでで何が入ったかというと、

```
.
├── node_modules
├── package-lock.json
└── package.json
````

まあこれだけです。

ここまでで何ができるようになったか、一回みてみます。

`public` と `src`のディレクトリを作って、index.htmlとindex.jsを用意します。

```public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<h1 id="h1-tile">テストです</h1>
</body>
</html>
```

```src/index.js
import {content} from "./content";

content();
```

```src/content.js
export function content() {
    const content = document.createElement('div');
    content.innerText = 'index.js実行されたよ!!!!';

    const title = document.getElementById('h1-title');
    title.appendChild(content);
}
```

ちょっと意味わからん分け方してますが、最終的にこれをreactに起こすという事をしたいが為にファイル分けてます。


`package.json`のscriptに追記して、startコマンドでdev-serverが立ち上がるようにしておきます。

```package.json
"scripts": {
  "start": "webpack-dev-server"
}
```

さらに、まとめたファイルの出力先と名前を指定してみます。

webpack.config.jsを作成してdevServerについて記載します

```diff: webpack.config.js
const path = require('path');

module.exports = {
+    entry: './src/index.js',
+    output: {
+        path: path.join(__dirname, 'public'),
+        filename: 'output.js'
+    },
    devServer: {
        static: {
            directory: path.join(__dirname, 'public'), // publicをドキュメントルートに指定
        },
        port: 8888, // ポートは8888に
    },
};
```

ここまででこんな感じ
```
.
├── node_modules
├── package-lock.json
├── package.json
├── public
│   └── index.html
├── src
│   ├── index.js
│   └── content.js
└── webpack.config.js
```

起動してみます。

```
npm start
```

8888ポートにアクセスしてみます。

![Screenshot 2022-06-10 2.17.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/a8070277-a0e9-f8f7-69d7-2cb1fe2b8ce3.png)

起動はしますが、index.jsが呼ばれてないようです。

output.jsを読み込むようにindex.htmlを修正します
```diff:public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<h1 id="h1-tile">テストです</h1>
+ <script src="output.js"></script>
</body>
</html>

```

![Screenshot 2022-06-10 2.21.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/63a1cac0-de75-8306-14a4-59e3b7dc84aa.png)


反映されました。

ここでindex.htmlをブラウザで直接開いてみます。

![Screenshot 2022-06-10 2.19.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/2d279206-7fff-603c-3e08-d24d8e803639.png)

こっちは、js読み込まれてません。


output.jsは今の所物理的に存在してないのでそらそう。
なので、webpackにoutput.jsを作ってもらいます。

```
npx webpack --mode development

> asset output.js 4.33 KiB [emitted] (name: main)
> runtime modules 670 bytes 3 modules
> cacheable modules 291 bytes
>   ./src/index.js 61 bytes [built] [code generated]
>   ./src/content.js 230 bytes [built] [code generated]
> webpack 5.73.0 compiled successfully in 45 ms
```

うんpublic下にoutput.jsが生成されました。

これでブラウザで直接index.htmlを開いてもjsがはしります。

ちょっとcontentのテキスト部分を書き換えてみます。

npm startした方はリコンパイルが走って画面からも、更新が確認できました。
ファイルを直接参照している方では反映確認できません。

という事で、dev-serverは、**ローカルサーバ立ち上げ + assetsをウォッチして常に最新のoutput.jsを参照するように変更してくれる**模様でした。

次に進みたいと思います。

## react入れる

物理生成されたpublic/output.jsは削除してreact入れます

```
npm install react react-dom
```

各種jsをreactで書いてみます

```src/content.js
import React from 'react';

export const Content = () => {
    return React.createElement('div', null, "index.js(react)が実行されたよ");
};
```

```src/index.js
import React from "react";
import * as ReactDOM from "react-dom/client";
import {Content} from "./content";

const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(React.createElement(Content, null, null));
```

content.jsに記載した関数コンポーネントを、
メインとなるindex.jsの方で呼び出している形です。

content.jsの方の関数コンポーネントが何してるかというと、
createElementをでdivを追加しているだけですね。

npm startします

![Screenshot 2022-06-10 2.24.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/7d28cc8e-7f10-ea39-50c8-e9d7b906dd49.png)


オッケイそうです。

## jsxの記法を使ってみる

createElementで毎回記載するのは大変で、
そういうときのために拡張言語のjsxが登場します。

createElementで書いてるような部分を、直接htmlタグを記載する感覚で書くことができます。

ちょっとjsxの書き方を使って変更してみましょう。

↓↓

```src/content.jsx
import React from 'react';

export const Content = () => {
    return (
        <div>
            index.jsxが実行されたよ!!!!
        </div>
    );
};
```

```src/index.jsx
import React from "react";
import * as ReactDOM from "react-dom/client";
import {Content} from "./content";

const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(
    <Content />
);
```

npm start すると

> You may need an appropriate loader to handle this file type

と怒られます。

jsxの記法をjsに変換しなければならないので、
ここで、babelが登場します

npm install --save babel-loader
npm install --save-dev @babel/preset-react

webpackの設定で、js/jsxの書き換えに使うモジュールを指定します
```webpack.config.js
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ['babel-loader'], // ← jsとjsxファイルはbabel-loader使ってねー
            },
        ],
    },
    resolve: {
        extensions: ['*', '.js', '.js'],
    },
```

もっかいnpm startします

>  to the 'presets' section of your Babel config to enable transformation.

babelのconfigが必要みたいなので追加します

```babel.config.json
{
  "presets": ["@babel/preset-react"]
}
```

今度こそ、お願い。という気持ちでnpm startします

![Screenshot 2022-06-10 2.32.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/53f59cde-d877-5aa9-b55a-df820d75ddd5.png)


おっけい、反映されました。

srcの2ファイルはjsxが使われているので一応.js　⇨ .jsx、と拡張子変えてます。

ここまででこんな感じ
```
.
├── node_modules
├── package-lock.json
├── package.json
├── public
│   └── index.html
├── src
│   ├── index.jsx
│   └── content.jsx
├── webpack.config.js
└── babel.config.json
```

さあ次に進みます

## typescriptで書いてみる

reactはtypescriptで書いて初めて本領発揮するので、typescriptを入れます

```bash
npm install --save-dev typescript ts-loader
npm install --save-dev tslint tslint-loader tslint-config-airbnb

npx tsc --init
```

```bash
npm i --save-dev @types/react
npm i --save-dev @types/react-dom
```

webpack.config.jsを改変します

```webpack.config.js
    entry: './src/index.tsx',
    module: {
        rules: [
            {
                loader: 'ts-loader', // ts-loaderでトランスパイルする
                test: /\.tsx?$/,
                exclude: /node_modules/,
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ] // .tsx、.tsを追記した、jsxは不要
    },
```

jsxファイルをtypescriptで記載してみましょう

```src/index.tsx
import React from "react";
import * as ReactDOM from "react-dom/client";
import {Content} from "./content";

const root = ReactDOM.createRoot(document.getElementById('app') as HTMLDivElement);
root.render(
    <Content
        text={"index.tsxが実行されたよ!"}
    />
);
```

```src/content.tsx
import React from "react";

interface ContentProps {
    text: string;
}
export const Content = (props: ContentProps) => {
    return (
        <div>{props.text}</div>
    );
};
```

ここでtypescriptの型推論の恩恵を感じてみたかったので、
今ままでdivの文字列は、Contentコンポーネントの方で保持していましたが、
あえて、index.tsx側からContentコンポーネントにpropsを渡す形をとってみました。

```tsx
                               // ↓↓↓ ここ　!!
export const Content = (props: ContentProps) => {
    return (
        <div>{props.text}</div>
    );
};
```

この部分で、Contentコンポーネントの引数は `ContentProps` なるものである事を示しています。


`ContentProps` って誰だよ、となるわけですが、ひとつ上の所で

```tsx
interface ContentProps {
    text: string;
}
```

と、 `text` というstringのプロパティを持つんだよ。と型定義してます。


これ何が嬉しいかって、
index.tsxでContentを呼ぶときに、何が必要かエディタが教えてくれます。

![Screenshot 2022-06-10 2.43.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/42150e8e-fbdd-bdf4-0c71-98afbe6c1a40.png)
![Screenshot 2022-06-10 2.43.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/260c9865-ddaa-2255-8dae-abfdaa094eaa.png)

numberとかいれるとガチギレされます。

![Screenshot 2022-06-10 2.43.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/aa536f3a-7e88-e06a-c0ec-9ea7aa1c09a5.png)

もちろんトランスパイルで怒られますし、開発するときもいろいろ嬉しいですね。

さあ、画面開いてみます。

![Screenshot 2022-06-10 2.45.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240041/3e8779c4-42cb-0239-b8ab-cfc30cd8c108.png)

反映されたのでこれにて完。

# 最後に

jsxのトランスパイルはbabelがやってくれましたが、
tsxのトランスパイルの際にts-nodeはjsxの部分もまとめてjsにしてくれています、
この時点でbabelは不要という事になりますね。

プロジェクト内で、
.jsx と .tsx を混在させるようなときは双方使っていく形になるのでしょう。

なにはともあれ、無事 
**シェフの気まぐれソースコード react by typescript ~jsxを添えて~** の完成です。

今まで意味わからんかった単語達と、少し仲良くなれた気がします。

# 参考文献

https://memo.morelents.com/kaisou-henkan/