新人「先輩、TypeScriptのコーディングできました!」
先輩社員「どれどれ」
先輩社員「いやそこら中コンパイルエラーだらけ...なのは型システムが働いてる証拠だ!」
先輩社員「そうだろ?」
先輩社員「型は...ガードレールだ」
先輩社員「進むべきじゃない場所へ進もうとしたら、ちゃんとブロックしてくれる...」
(ぺこぱ風)
ってことで、静的型付言語って良いですよね。
今回はTypeScriptを使ってみて嬉しかったことを書いてみます。
登場人物
ワイ・・・ワイ(36歳)
社長・・・社長
ハスケル子・・・インターンの中学2年生
今日から新しいプロジェクト開始
社長「おーい、やめ太郎、ハスケル子ちゃん」
社長「新しいお仕事を獲得してきたで」
社長「技術記事投稿サイトを作る案件や」
ワイ「おお〜、さすが社長はんや」
社長「おおきにやで」
社長「ほんで、そのお仕事の話なんやけど」
社長「クライアントはんから一つ要望があんねん」
社長「Reactを使って作ってくれ、って言われてんねや」
社長「やめ太郎、ハスケル子ちゃん、Reactはイケるか?」
ワイ「やった事なくはないですわ」
ハスケル子「私も大丈夫です」
TypeScriptはどうする?
社長「TypeScriptはどうしよか?」
社長「そこはクライアントはんからは何も言われてへんねやけど」
ワイ「TSはよう分からんから、今回はやめときまひょ」
ワイ「型とかよう読まれへんし」
ハスケル子「私は絶対TypeScript有りでやりたいです」
ハスケル子「型がないとコード読むの面倒なので...」
型がないと面倒、とは
ワイ「いや、型があるほうが型情報を読まなあかんから面倒やん」
ハスケル子「いえ、型が書いてあるほうがコードを把握しやすいです」
ワイ「ええ...何で...?」
例えば:マイページ
ハスケル子「例えば、技術投稿サイトのマイページみたいなのを作るとしますね」
ハスケル子「↑こんな感じのやつです」
ワイ「ほうほう」
ハスケル子「で、ユーザ情報を表示するためのUserInfo
っていうコンポーネントを作るとします」
ハスケル子「TypeScriptなしで書くとすると、コードは↓こんな感じです」
const UserInfo = ({ user }) => (
<div>
<section>
<h2>ユーザ情報</h2>
<p>アカウント名:{user.account}</p>
<p>名前:{user.name}</p>
</section>
<section>
<h2>記事一覧</h2>
<ol>
{
user.articles.map(article => (
<li>
<a href={article.url}>{article.title}</a>
</li>
))
}
</ol>
</section>
</div>
)
ワイ「なるほどな」
ハスケル子「やめ太郎さんなら、このコンポーネントのコードを見て、」
ハスケル子「どんな風に使うべきコンポーネントなのか読み取れますか?」
ワイ「そら簡単やで」
ワイ「余裕で読み取れますがな」
ワイ「まず...」
const UserInfo = ({ user }) => (
ワイ「↑こう書いてあるから」
ワイ「このUserInfo
コンポーネントは」
ワイ「親コンポーネントからuser
っていうpropsを受け取ってる...」
ワイ「っちゅうことが分かるわ」
ワイ「他にも...」
<h2>ユーザ情報</h2>
<p>アカウント名:{user.account}</p>
<p>名前:{user.name}</p>
ワイ「↑こんなコードがあるから」
ワイ「親から受け取ったuser
っていうpropsは、account
とname
というプロパティを持ったオブジェクトである...」
ワイ「ってこともわかるで」
ワイ「あとは...」
<h2>記事一覧</h2>
<ol>
{
user.articles.map(article => (
<li>
<a href={article.url}>{article.title}</a>
</li>
))
}
</ol>
ワイ「↑こんな部分があるな...」
ワイ「せやから、user
はarticles
っちゅうプロパティも持っとんな」
ワイ「map
メソッドを使っているということは、そのuser.articles
は配列やということが分かるで」
ワイ「ほんで、その配列の中身はオブジェクトやな」
ワイ「url
とtitle
というプロパティを持ったオブジェクトっちゅうことや」
ハスケル子「はい、そんな感じですよね」
ハスケル子「まとめると、どんな風に使うべきコンポーネントだと言えますか?」
ワイ「ええ...?」
ワイ「まあ、まとめるならば」
const user = {
account: 'Yametaro',
name: 'やめ太郎',
articles: [
{
title: "記事名1",
url: "/article1.html"
},
{
title: "記事名2",
url: "/article2.html"
}
]
};
ワイ「propsとして↑こんなオブジェクトを渡して使うべきやな」
ハスケル子「そうですね」
TypeScriptの場合はどうか
ハスケル子「次に、TypeScriptを使った場合のコードを見てみます」
type Props = {
user: User
}
type User = {
account: string,
name: string,
articles: Article[]
}
type Article = {
title: string,
url: string
}
const UserInfo: React.FC<Props> = ({ user }) => (
<div>
<section>
<h2>ユーザ情報</h2>
<p>アカウント名:{user.account}</p>
<p>名前:{user.name}</p>
</section>
<section>
<h2>記事一覧</h2>
<ol>
{
user.articles.map(article => (
<li>
<a href={article.url}>{article.title}</a>
</li>
))
}
</ol>
</section>
</div>
)
ハスケル子「↑こんな感じです」
ワイ「ほら、やっぱ型が書いてあるぶん長いやん...」
ハスケル子「そりゃあコード量は少し増えますけど」
ハスケル子「可読性の面でメリットもありますよ」
ハスケル子「例えば...」
type Props = {
user: User
}
ハスケル子「↑ここを見ると」
ハスケル子「このコンポーネントはuser
というpropsを受け取る」
ハスケル子「そしてそのuser
はUser型の値である」
ハスケル子「...ということが分かります」
ワイ「なるほどな、受け取るprops一覧がここで分かるわけやな」
ワイ「今回の例ではuser
一個だけってことか」
ワイ「でも、なんやのUser型って」
ワイ「number型とかstring型なら知っとるけど」
ハスケル子「それは↓この部分に書いてあります」
type User = {
account: string,
name: string,
articles: Article[]
}
ハスケル子「User型の値は、account
という文字列と」
ハスケル子「name
という文字列...」
ハスケル子「そしてarticles
という配列を持っているよ、ってことがわかります」
ワイ「ほうほう、つまり...」
ワイ「User型っていうのはハスケル子ちゃんの自作の型ってこと?」
ハスケル子「そうです」
ハスケル子「自分で新しい型を定義して名前を付けることができるんです」
ワイ「ほー、なんか...」
ワイ「Userってのはこんなやつですよ、みたいな」
ワイ「世界観的なものが分かりやすいな」
ハスケル子「そうですね」
ワイ「ほんで、User
が持ってるaccount
とname
がstring型なのは分かったけど」
ワイ「Article[]
ってのはどういう型なん?」
ハスケル子「Article[]
っていうのは」
ハスケル子「Article型の値が入ってる配列だよ、って意味です」
ワイ「Article型ってのは、また自作の型やな?」
ハスケル子「はい」
type Article = {
title: string,
url: string
}
ハスケル子「↑この部分でArticle型を定義しています」
ワイ「なるほど」
ワイ「Article型の値は、title
とurl
というプロパティを持ってますよ...」
ワイ「ほんで、title
もurl
も文字列ですよ...」
ワイ「っちゅうことやな」
ハスケル子「そうです」
型が書いてあると、何が嬉しいか
ハスケル子「TypeScriptなしの場合は」
ハスケル子「コンポーネントの中身を全部読んで...」
「なるほど、このコンポーネントには
user
というオブジェクトを渡して使うんだな」
「そのuser
は、account
とname
とarticles
というプロパティを持ってるんだな」
「articles
はmap
メソッドを持っているようだから、配列だな・・・!」
ハスケル子「...っていうことがようやく分かったじゃないですか」
ワイ「せやな」
ハスケル子「でもTypeScriptありの場合だと」
type Props = {
user: User
}
type User = {
account: string,
name: string,
articles: Article[]
}
type Article = {
title: string,
url: string
}
ハスケル子「↑この型定義の部分を見ただけで...」
const user = {
account: 'Yametaro',
name: 'やめ太郎',
articles: [
{
title: "記事名1",
url: "/article1.html"
},
{
title: "記事名2",
url: "/article2.html"
}
]
};
ハスケル子「↑こんなpropsを受け取るコンポーネントだな!」
ハスケル子「ってことが分かるじゃないですか」
ハスケル子「コンポーネントの実装部分を読まなくても!」
ワイ「おお...」
ワイ「やっぱリーダブルやなぁ...」
社長「(何がやっぱやねん...)」
ワイ「仕事やと、一つの案件のコーディングを複数人で担当することも多いから」
ワイ「人の作ったコンポーネントを使う機会とかも多いしな」
ワイ「せやから、読み取りやすいのはええよなぁ」
ワイ「あと思ったんやけど」
ワイ「型ってなんか、コメントみたいな効果もあるんやね」
ワイ「このプロパティには文字列を入れてくださいな、みたいな」
ワイ「コード内に仕様書が書いてある感じがええな」
ハスケル子「そうですよね」
ハスケル子「しかも、ちゃんと守らなきゃ前に進めない...」
ハスケル子「強制力を持ったコメントですよね」
ハスケル子「型定義の通りにpropsを渡さないとエラーが出て、コンパイルが通らない...」
ハスケル子「だから、マウスでぽちぽちページを見て回る前にミスに気付けるんです」
ワイ「ああ、普通はコードを実行してみるまで」
ワイ「エラーが出るかどうか分からへんもんなぁ...」
ワイ「それはええかも...」
他にも嬉しいことが
ハスケル子「そういえば、やめ太郎さんって」
ハスケル子「name
っていう単語をよくnamae
ってスペルミスするじゃないですか」
ワイ「まぁ、比較的するな」
ハスケル子「そういうタイポ対策にもTypeScriptが役立ったりしますよ」
ハスケル子「例えば...」
<p>名前:{user.namae}</p>
ハスケル子「↑こんな風にタイポしちゃったとしたら...」
Property 'namae' does not exist on type 'User'. Did you mean 'name'?
(プロパティ「namae」はタイプ「User」に存在しません。 「name」という意味ですか?
ハスケル子「なんて教えてくれるんです」
ワイ「おお、まじか...」
ワイ「まさにワイのための機能やん...」
ハスケル子「そもそもタイポしなくなるようなメリットもありますよ」
ハスケル子「↑こんな風に、user.
まで打つと入力候補が出てくるので」
ハスケル子「その中から選択すればいいんです」
ワイ「おお...」
ワイ「もう、使わん理由ないやん...」
ワイ「ハスケル子先生...!!」
ワイ「TypeScriptがしたいです......」
社長「ほな、決まりやな!」
そんなこんなで終業時刻
ワイ「ハスケル子ちゃん、今日も色々教えてくれてありがとうやで...」
ハスケル子「教えるの楽しいから全然いいですよ」
ワイ「ハスケル子ちゃんは凄いなぁ...まだ中学生なのに色々知ってて...」
ワイ「チート過ぎるハスケル子ちゃんを見てると、自分が情けなくなってくるわ...」
ワイ「負け過ぎてるんやもん...」
ハスケル子「私もよく思いますよ」
ハスケル子「私だけ周りの子たちより出遅れてて不安になったり、嫉妬したりします」
ハスケル子「でも結局」
ハスケル子「今の自分から生きることしかできないんです」
ハスケル子「誰かに負けてても、誰かより劣っていても」
ハスケル子「自分なりの精一杯をやればいいんです」
ワイ「ハスケル子ちゃん...!」
ハスケル子「...もしくは、嫌なら死ぬとか...」
ワイ「いや死を提案すな!」
〜おしまい〜
続編もよろしくやで
4歳娘「パパ、実行時エラーの出ないフロントエンド言語ってなーんだ?」
さらに続編
おまけ:簡単にReact + TypeScriptを始めるには
React + TypeScriptを触ってみたい人は、
npx create-next-app --example with-typescript my-app-name
cd my-app-name
npm run dev
↑この3つのコマンドを打ってから
http://localhost:3000/
にアクセスするだけやで!
(もちろんNode.jsは先にインストールしといてや!)