React + TypeScript + VScodeの組み合わせがやばかった

やばい

最初に注意書き: この記事たぶん間違ってるからGoogle先生と一緒に読んでね もういっちょ注意書き: 深夜テンションMAXで書いたので文章が支離滅裂

対象読者: ある程度情報リテラシーがあってHTML, JavaScript, CSSでなんかわいわいできるけどTypeScript + Reactの組み合わせには手を出してないひと || TypeScript + Reactの仕組みをもう構築してるけど他の人のも見てみたいひと || マサカリ投げるマン

TypeScriptとVScodeは開発元が同じMicrosoftということもあるのか死ぬほど相性がよくてもう結婚してから20年以上立ってもラブラブな熟年夫婦以上に相性がよくて、なんならVScodeの94% (77f1ef84 現在)はTypeScriptで書かれているぐらいにもうベッタベタで切っても離せないような関係なのですが、TypeScriptとVScodeの組み合わせはまじでやばい。

あとReact と TypeScript の相性もかなりよくて、PropTypesみたいなどんくさいことをしなくてよくなる。ただTypeScriptはanyが絡むとそこから亀裂が入って割れる(比喩)のでそこが唯一しんどいが、まあそれはそれ。

というわけでやばすぎて一本Electronでアプリケーションを書いてしまった。Macの上のバーから簡単になうぷれできるやつ。音楽プレーヤーと投稿先はコードを書けばいくらでも足せるような設計にしてある。ただデザインセンスが虚無すぎてイカれてる。bootstrapでも使えばよかったとむっちゃ反省してる。今からでも遅くない気もするけど。

というわけでここではサンプルにありがちなメモリ上に保持するだけのSPAなToDoアプリを書いていきます。途中でだるくなったのでhello worldまでにしました

今回使うもの

  • VScode: エディタ
  • webpack: なんか変換?とかしてひとつにまとめてくれるすごいやつ
  • TypeScript: JavaScriptに型を付けたりできるすごいやつ
  • React: なんかDOM?とかをいいかんじにしてくれるすごいやつ
  • npm: インターネットの海から誰かさんが書いたコードをひっぱってきてくれるすごいやつ
  • その他調味料少々

とりあえずHello, Worldまでしたい

適当にディレクトリ掘ってnpm initまでしてVScodeでそのフォルダを開いた前提。

$ npm i -D react react-dom @types/react @types/react-dom typescript webpack ts-loader
$ npm i -g typescript webpack webpack-dev-server

いきなりドーン!!!!! えーとまずこれはパッケージをインストールするコマンドなんだけど、インストールしてるパッケージをひとつひとつ雑に説明すると

  • react 前述。React。
  • react-dom React に DOM を触らせるやつ? よくわかってない
  • @types/react ReactにはどういうクラスとかメソッドとかがありますよーみたいなのをTypeScriptに教えてやるやつ。JavaScript製のライブラリを読むときはこれがないとなんでもありになってしまい非常につらい。
  • @types/react-dom react-domにはどういう(以下略)
  • typescript TypeScriptのコンパイラ。こいつがTypeScriptからJavaScriptに変換して、我々のブラウザとかNode.jsで解釈できるように変換してくれる。
  • webpack 前述。
  • ts-loader webpackにTypeScriptを読ませてくれるやつ。料理人的な(違う)
  • webpack-dev-server なんか開発中に自動リロードとかしてくれるすごいやつ

みたいなかんじ。たぶん。

$ tsc --init
message TS6071: Successfully created a tsconfig.json file.

これはTypeScriptのコンパイラさんにどういう設定でコンパイルすればいいかを教えるやつ。 作ったらVScodeかなにかでひらいて

    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */

の行を

    "jsx": "react",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */

にしておいてください(コメントアウトを外してpreserveをreactにしただけ)

$ mkdir src dist

適当にソースコードと成果物を置くディレクトリを掘る。

webpackのconfigのjsを書く

const path = require("path")

module.exports = {
    entry: "./src/index.ts",
    output: {
        filename: "bundle.js",
        path: path.join(__dirname+"/dist/")
    },
    devServer: {
        contentBase: path.join(__dirname+"/dist")
    },
    resolve: {
        extensions: [".ts", ".tsx", ".js"]
    },
    module: {
        loaders: [
            { test: /\.tsx?$/, loader: "ts-loader" },
        ]
    }
}

いやもうまじでwebpackのconfigを説明するのはつらいのでなんかしりたいひとはgoogleに聞いてもらうとして、とりあえずこれを変な前後のが入らないように丸々コピーしてwebpack.config.jsにペーストすればおっけー。webpack.config.jsはプロジェクトのルート(package.jsonがあるところ)にないとだめなのでそこに置きましょう。src/に置いちゃだめよ。

dist/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script src="bundle.js"></script>
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>

これはsrc/じゃなくてdist/に置いてね。

src/app.tsx

ヨッシャーーーーついにTypeScriptだぜヒャッホイ

えーっと、tsxってなんじゃという人もいると思うんですけど、なんかTypeScriptでJSXを使うよ的なアレです。JSXに付いてはなんかHTMLとJavaScript混っちゃったみたいな。

まずReactを読み込みます。

import * as React from "react"

TypeScriptはなんかES6のimportっぽい文法でライブラリを読み込みます。ライブラリを使うのにrequire()を使うとanyになって終わるので気をつけましょう。

export default class App extends React.Component {

!?!?!?!?!?!?!?

あにこれって思うかもしれませんが、なんかES6のモジュールをexportするときのあれそれなんですって。詳しくはようしらん。 なんかextendsとかあるけど普通に他言語のextendsみたいなやつです。

    render() {
        return <div>
            <h1>Hello, World!</h1>
        </div>
    }

ウワーーーーTypeScriptにHTMLまじっちゃったアビャーーーー 世界よ、これがJSXだ。いやTypeScriptだからTSXだけど。 JSX について詳しくは触れませんがなんかすごいやつです。調べるときはJavaScript界隈にはJSXという名前の付いたライブラリがいっぱいあるのでreact jsx とはとかで検索しましょう。

}

class宣言閉じておわり。

src/index.ts

アプリの起動部分。適当にコメント付けたから適当に理解してほしい。

import * as React from "react" // reactを読みこむ
import * as ReactDOM from "react-dom" // react-domを読みこむ
import App from "./app" // さっき書いたapp.tsxを読みこむ

addEventListener("load", () => { // いつもの 定番 親の顔より見た (red big shita)
    const root = document.getElementById("root")
    ReactDOM.render(React.createElement(App), root)
    // ReactDOM.これかけ(書くやつ, 書くとこ) みたいな感じ
})

とりあえず hello world

$ webpack-dev-server
Project is running at http://localhost:8080/
webpack output is served from /
Content not from webpack is served from /Users/user/Desktop/work/nodejs/react-ts-test/dist
// ここに間がある
Hash: 876e11885cbfeed521f9
Version: webpack 3.10.0
Time: 2504ms
    Asset     Size  Chunks                    Chunk Names
bundle.js  1.05 MB       0  [emitted]  [big]  main
   [2] ./node_modules/react/index.js 190 bytes {0} [built]
  [16] multi (webpack)-dev-server/client?http://localhost:8080 ./src/index.ts 40 bytes {0} [built]
  [17] (webpack)-dev-server/client?http://localhost:8080 7.95 kB {0} [built]
  [18] (webpack)/node_modules/url/url.js 23.3 kB {0} [built]
  [25] (webpack)-dev-server/node_modules/strip-ansi/index.js 161 bytes {0} [built]
  [27] (webpack)-dev-server/node_modules/loglevel/lib/loglevel.js 7.86 kB {0} [built]
  [28] (webpack)-dev-server/client/socket.js 1.05 kB {0} [built]
  [30] (webpack)-dev-server/client/overlay.js 3.73 kB {0} [built]
  [31] (webpack)-dev-server/node_modules/ansi-html/index.js 4.26 kB {0} [built]
  [32] (webpack)-dev-server/node_modules/html-entities/index.js 231 bytes {0} [built]
  [35] (webpack)/hot nonrecursive ^\.\/log$ 170 bytes {0} [built]
  [37] (webpack)/hot/emitter.js 77 bytes {0} [built]
  [39] ./src/index.ts 328 bytes {0} [built]
  [43] ./node_modules/react-dom/index.js 1.36 kB {0} [built]
  [52] ./src/app.tsx 992 bytes {0} [built]
    + 38 hidden modules
webpack: Compiled successfully.

こんなんでたら http://localhost:8080 開くとHello Worldができてると思います。お疲れさまでした。 f:id:rinsuki:20180116033732p:plain