TypeScriptに手を出したきっかけ

atsuco_02氏(以下、atsuco_02):前のお二人が難しい話をしていたので、私は簡単な話をしようと思います。TypeScriptの話をしたいなと思います。

ちょっとだけ自己紹介させてください。アサオカアツコといいます。この黒いネコのアイコンでTwitterをやっているので、よかったら仲良くしてください。

フリーランスでフロントエンドとかデザインとか写真を撮ったりしてます。エンジニアさんで「登壇のときに使うかっこいい写真欲しいぞ」みたいな人がいたら言ってください。ワンドリンクで撮りに行きます。

もしかしたら私のこと知ってくださっている方もいらっしゃるかもしれませんが、「We Are JavaScripters!」という運営やってます。お世話になっています。

早速ですが、「型安全やっていくぞ!」と去年の末に決意を新たにしまして、型安全を知るためにTypeScriptに手を出しました。

今まで関わってきたプロジェクトが何件かあったのですが、やはり長規模だったり、コード的に規模が小さかったり関わっている人数的に規模が小さかったりして、できるだけ早く出したいということもあり、生のJSをもりもり書いたり、なんならHTMLを生でもりもり書いたりということが多かったので、実は「We Are JavaScripters!」というJSのコミュニティの運営をやっておきながら、TSぜんぜんわからんマンでした。

なので、本当にTSの知識はゼロでしたが、型安全の入門にはやはりTSなんじゃないかという話をしたいと思います。

TypeScriptに手が出せなかった理由

みなさん、「ふだんからTypeScript使ってるよ」って方、どのぐらいいらっしゃいますか?

(会場挙手)

あ、けっこういますね。今日はなんとなくTypeScriptに手が出せないみたいな状態の私が一歩踏み出した話なので、温かい感じでお願いします。

みなさん普段から使っている方が多いと思いますが、「初学者がどこにつまづくか」みたいな感じで見てもらえると、もしかしたらみなさんの同僚でつまづいている方がいれば助けになれるんじゃないかなと思いました。

目次はだいたいこんな感じです。

「入門したい」と言っていますが、そもそも何ができたら入門できたのかを定めてないと勉強ができないので、環境構築はいったん置いておいて、「そもそも型安全って何なの?」とか「TSで防げるエラーって何なの?」というところを知ること、あとはコードで示せるようになったら入門したと言っていいんじゃないかというところに目標を定めました。

そもそも私がなぜTSに手を出せなかったかというと、強マンがイベントでオラオラ話しているのを見て「あっ、怖い……」ってなっちゃったんですよね。ちょっと強い人が苦手なので「怖いな」ってなっちゃってました。

あとは、単純に難しいんじゃないかという、食わず嫌いみたいなところもありました。あとは、環境構築が面倒くさいんじゃないか。「今ある環境をぶっ壊してまで入れるメリットみたいのは感じられないな」みたいなことを思っていました。

他には、静的型付け強硬派がオラオラしているような気がしていて、ぶっちゃけ静的型付けって何がいいのかわかってないって、もう完全に偏見の塊だったんですね、私。

総合すると、「なんかとっつきにくいし、怖いし、今のままでもなんとなくやれてるからこのままでもいいのでは……?」みたいな気持ちだったんですが、私は年末に決意を新たにしたので、「やっていくぞ!」という気持ちになりました。

それでどうやって始めようかなと思ったんですが、私は環境構築が怖いので、環境構築しなくていい方法をとろうということで、Codesandboxを使いました。

みなさん、Codesandboxは使ったことはありますか? オンライン上でポチポチやるだけで、設定しなくてもTSが書けたりReactが書けたりするものです。うまいことやるとGitHubに上げられたりするらしいですが、私はよくわかりません。

なので、動かす環境としてはCodesandboxです。なにか資料になるものをと思って公式のドキュメントも読みましたが、一番の上のほうの『Programming TypeScript: Making Your JavaScript Applications Scale』という本を読もうと思いました。私は英語は読めないので、中のコードを読もうと思いました。

ラッキーなことに、友人のJavaScriptおじさんがこの本の翻訳を書いていまして、『合法 TypeScript』というページを参考に、原書のコードを見ながら勉強をはじめました。

あとは、takepepeさんの『実践TypeScript』もちらちら読んでいます。ただ、これは「Nuxt.jsの型定義」みたいなサブタイトルが付いていて、今の私にはまだフレームワークでTSを使うのは早いので、導入の部分だけ読んでフムフムってなっている感じです。

実践TypeScript ~BFFとNext.js&Nuxt.jsの型定義~

TypeScriptに対する印象が変わり始める

そんな感じで「TSを始めるぞ」という環境を整えました。「そもそもTSって何ができるの? 何をしてくれるの?」というところに関しても、私は本当に何も知りませんでした。そもそもTSってJSのオプション的なものだと思ってたぐらいには無知でした。

ですが、公式を見てGoogle翻訳をかけたら、「TypeScriptはプレーンJavaScriptにコンパイルされるJavaScriptの型付きスーパーセットです」ということで、どうやらJavaScriptとTypeScriptは別物のようだぞと。別物というか、JavaScriptのスーパーセットでどうやら同じものではないらしいと。なんなら拡張子も.tsだぞということをここで知ります。

TypeScriptはTypeScriptCompilerによってJavaScriptのコードに変換される。「ああ、やっぱり違うんだ」と。どうやらTypeScriptではコンパイル前にtypecheckerによる型チェックが行われるらしい。

出てきました、型。「そもそも型って何?」「みんな型安全とか静的型付けとか言うけれども、ぜんぜんわからない。やだ」みたいな感じでした。

そもそも型とは、値とその値に対して実行できる操作の集合ということはもうみなさんご存じですよね。私はけっこうこれが目から鱗でした。

数字で「2」とあったらこれはnumber型だよねとか、「text」って文字があったらtext型だよねというのはなんとなくわかっていたんですが、それに対して、型が決まると実行できるアクションが決まるというのは、言われてみれば当たり前のことなんですが、文字にされると「あっ、そうか、そういうことか」ということですごく腑に落ちました。

型ってなんとなくぼやぼや「値の種類を示すもの」みたいな感じだったのですが、「型が決まると操作が決まるんだな」というところが私の中でストンってなった感じですね。なので、この「値に対して実行できる操作/実行できない操作が明確であることを型安全と呼ぶんだぞ」というところで、私はひらめきを得ました。

よく「型がある言語っていいよね」みたいなぼんやりしたこと聞いていて「型の何がいいんだろう?」と思っていたんですが、「『型があると操作が限定されるので、意図しない値の受け付けや操作でエラーを防げるのがいいよね』というのを総合して『型がある言語っていいよね』と言ってるんだな」と思いました。

「関数を作った際に引数の型不一致が防げるとか、そういうやつかな?」みたいな、そういうぼやっとしたTypeScriptに対する印象が、ここで変わったわけです。「けっこう根本的な話っぽいぞ」と思いました。

JavaScriptとTypeScriptの型の違い

「でも、言うてJavaScriptにも型ってあるよね?」と。私はフロントの言語しかほぼ触らなくて、「JSにも一応型ってあるよね?」という話に戻ってきます。

JSにもあるけど、なんでTSのほうがいいのかという話ですが、ここでよく言われるのは静的型付けってやつですね。「オラオラしてて怖い」って言ったやつです。これはJSの動的型付けと呼ばれるものと何が違うかというと、型をコード上で指定するか、コードを実行してから型を判別するかの差です。

あとは、型の自動変換がJavaScriptはされるとかされないとか。typecheckerのタイミングは、さっきも言いましたね、コンパイル時にされるものと実行時にされるものといった、こういった違いがあります。

型の自動変換ってみなさんわかりますよね。これは数字と配列の足し算なんですが、JavaScriptではこれは実行できますよね。文字列として「12」が返ってきます。

みなさんすでにご存じかと思いますが、これを紐解いていくと、1は数字だし、[2]は配列であるとJavaScriptさんがそれぞれ型をなんとなく推測するわけです。「あっ、間に足す(+)を使っているから、どうやらこの2つの値を連結したいっぽいぞ」と推測します。そうなると、数字と配列は足せないので、それぞれを暗黙的に文字列に変換して結合して、12という文字列を得ます。ここで暗黙的に型の変換が行われると。

これは便利っちゃ便利なんですが、よしなにやってくれすぎるのでエラーの原因がわかりにくく、これが「JavaScriptの型つらいよ」という話なんだと気づきました。

これをTSで実行してみると……という話で、Codesandboxが出てきます。

先ほどの「1+[2]」をTSで実行したい。下にニョロニョロってエラーが出てるんですよ。これはTypeScriptのファイルなんですが、これは何を言ってるかというと、「演算子+を型1およびnumber型に適用することはできません」。どうやらこれを足してはいけないと言っています。

ここでさらにいいのが、私、英語読めないってさっき言ったじゃないですか。Codesandboxはエラーをある程度日本語にしてくれるので、すごくよかったです。TypeScriptだと「型が違うからダメだぞ」と実行する前に怒られてしまいました。これがきっといいところですね。

「TSの静的型付けっていいよね」ってみなさんが言われていることを私なりに理解すると、型を明示的に指定するので、値に対して可能な操作が明確になるのがいいと。

先ほどの暗黙的に型が変換されるって話ですね。これがTSはないので、予期せぬ値のエラーを踏みにくい。「1+[2]」は実行されず、エラーになってしまいます。

あと、コンパイル時に型周りのチェックをしてくれるので、実行しなくてもある程度のエラーがわかる。ある程度というのは、ユーザーの入力に対しては実行してからじゃないとわからないのでバリデーションはちゃんとしましょうね、みたいな話ですね。

なので、「JSにも型はあるっちゃあるけど、JSより強力な型付けでエラーを防ぎやすくするのがTSのいいところだね」というところに、私は理解が及びました。

ただ、TSは完全な静的型付けではなく、すべての型がわかっていなくてもコンパイル時によしなにやってくれる部分もあるので、JavaScriptからTypeScriptに移行したいというときに、一気に全部やっても絶対無理じゃないですか。なので、ちょっとずつやっていくことができるので便利そうだなと思ったのですが、混在しているままだと、TypeScriptの良さは半減してしまうので、ゆくゆくはすべての型を明確にするTypeScriptの書き方にやっていく必要があるんだなと思いました。

基本的な型付けの方法

「なるほど、よさそうだな……?」「でも、型付けって難しいんでしょ?」と思いました。ただ、みなさん知ってますよね。名前に対してtypeをつけるだけでよいと。「これ、ちょっと簡単そうなのでは?」と思い始めました。

ここに引数をべき乗して返す関数を作りました。引数nは数字を取るべきですよね。これは正方形の面積の計算です。

なので、このnに対して「これは絶対数字じゃなきゃダメだよ」という型をつけたいとすると、こうなります。「あ、すごい。簡単じゃない?」と思いました。

squareOfで、10を渡すと10×10で100になるし、ここにstringって入れちゃうとエラーが出るよと。案外いけるかもしれない。ちょっとここで私の気持ちがやわらいできます。

型を指定しながら値を代入したりとか、textはstring型のsample textって中身だよとか。

そもそも型ではなく値を直接指定したりもできます。hogeというのは123が入ってる数字型だよとか。この下のfugaは456が入るべきなのに0を入れているので、ここでエラーが出るんですね。

ここがさっきの123のところですね。でも、やっぱりちっちゃいですね。下のfugaのところが456をアノテーションしているんですが、0を代入することによって、エラーとして「型0を型456に割り当てることはできません」とTypeScriptが怒ってくれるわけです。

いろいろできるけど、基本の形は一緒っぽいですね。変数の名前を書いて、型を書く。

型を指定しないでconstで宣言する方法もありますが、これは変数というか定数ですね。なので、先ほどのこれとほぼ同じような動きをするという理解になりました。再代入できないし、再宣言はできない。

「ほうほう、なるほどなるほど。じゃあTypeScriptで扱える型って何があるの?」という話なんですが、私はanyがすごく気になりました。書き手がなにも指定しなくて、さらにtypecheckerも型を推定できなかった、もうなんでも入ってくるブラックボックスみたいな扱いというわけです。

これ、みなさんが明示的に使うことってあるんですか? 一応any型って手動でつけられるらしいのですが、私は「いったい何の意味があるんだろう?」って思ってるんですよね。これをわざわざany型つける人とかいないですよね。

おそらくすべてのチェックをスルーした先にここに入ってくるんだろうなと思ったので、あまり使ったらダメということだけは思いました。すべての値が入るし、すべての操作を受け付けてしまうので、ブラックボックス化してしまうということですね。

anyと同じくすべての値を取るものの、ほとんどの操作を受け付けないunknownというのもありました。

ここで「any、unknown……ちょっとよくわからない」ってなったのですが、booleanやnumber、stringという「あっ、見たことある、これ」みたいなものが出てきました。

booleanはわかりますよね。真偽値、true/falseのどちらかを取るし、比較できるし、「!」で値を反転することもできる。numberは、いろいろ数値が入るし、四則演算できたり比較演算子で比較ができたり、数値に対してできる操作もろもろができたり。stringは、すべての文字列をとりますし、結合やスライスなどの処理ができます。

TypeScriptでObject

「これ、なんか見たことあるから安心してきたぞ」というところで、今度はTypeScriptでobjectを扱うところに入りました。

先ほどのやり方だと、let userに対してobjectという型を指定するのかと思いきや、「ここではやり方が違うのね」と思ったのが、objectのshapeを指定するというやり方でした。

このuserはnameプロパティを持ち、そのプロパティの値は文字列だとか、中身に対してアノテーションしていくところに「ああ、なるほど」と思いました。ただ単にobjectであることだけではなくて、中身に対しても型の安全を保証するということです。

次にobjectです。nameの値にstringを持ち、idにnumberを持ち、flagにbooleanを持つ。でもname・id・flagって必須ですよね。user1というobjectに対してnameしか指定しなかった場合、idとflagが足りないよとエラーが出ます。

でもこれっておそらくobjectってこういう使い方だけではないと思うので、flagプロパティが必須ではないプロパティが含まれているとか、ほかに追加予定のプロパティがある場合は? みたいなところはオプションがあることを知りました。

これですね。「?」をつけたり、keyに対してアノテーションして、さらにその値に対してもアノテーションする。これによってflagプロパティは必須じゃなくなるし、こちらをオブジェクトの中で最初に指定している中にはなかった「10」という名前のプロパティと「20」というプロパティを受け付けることができます。

「ほうほう、なるほど。便利っぽいぞ」と。なんとなくobjectがわかってきた気がしました。

TypeScriptでArray

次にArrayです。だいたい最初の3つ、numberとstringとbooleanと、あとobjectとarrayがわかればなんとなくわかった気になれるのではないかということで、ここまでやってみました。

配列の中を均質にしないと型安全を担保しにくいというのはまさにそのとおりで、「数値だけ」「文字列だけ」のように同じ型の値を中に入れるべきです。

これ、一応配列の中に複数の値を許可することはできるといえばできます。でも、この書き方、気持ち悪くないですか? そんなことない? 私だけ? なんか気持ち悪いですよね。単純に1つの値の中にいろんな型のものが混じっているのが気持ち悪いなと思いました。

何がよくないのかというと、気持ち悪さだけじゃなくて、なにかしたいときそれぞれがどの型かを判別して操作する必要があるので、本当によくないなと。これだと、受け取ったuserの中にある値が、それぞれnumberであるか、それともstringかによって操作を変えています。numberの場合はべき乗するし、stringの場合はそのstringの文字数をとります。

「いやいや、それじゃあ、それぞれの配列を作ったほうがやっぱりよくない? 型安全だよね」っていうのは、私が今まで型安全について学んできたところで自然に思ったところでした。型でなんでもうまくいくわけじゃないんだなってところも思いました。

結局最初の話に戻るのですが、「型安全って何?」というのと、「TypeScriptで防げるエラーって何?」というのをコードで示せるようになろうと思いましたよね。なので、ちょっとやりたいなと思います。

まず型安全。先ほどから何度も言っていますが、値に対して実行できる操作/できない操作が明確なので、それを型安全と呼ぶ。TSの型付けはJSの型付けよりも強力なので型安全を実現しやすくなるということを学びました。

「防げるエラーとか型安全とは?」というところですが、これですよね。stringを指定している変数を数字の2とかけるとエラーが出るよとか。numberを指定している変数に対してsliceをするとエラーが出るとか。「プロパティsliceは型numberに存在しません」、こういうエラーが出てきますと。こういうのは全部TypeScriptがコンパイルの時に防いでくれるエラーですね。

もちろんチラッと出てきた関数に対しても有効なので、こういうよくない数字というか、よくない値が入ってくることを防いでくれる。これが示せるようになったので、私は入門できたと言えるんじゃないかというところで満足しました。

TypeScriptを実際に使うようになるか?

便利っぽくてそこまで怖くないっぽいというところに着地したのですが、実際私は使うようになるんだろうか?

現状の私の話をすると、人数が少ないとかめちゃくちゃ急いでるとか、いわゆるちょっと燃えている案件に入ることが多いんです。なので、丁寧にやるよりはできるだけ早く出すみたいな、手元の環境で動いてればいいからできるだけ早く出すみたいな渋い環境に私自身が置かれていることが多いので、こういうところまで気が回らなかったなというのが正直なところです。

繰り返しなんですが、今までの私はこういうことを思ってました。

実際はたぶん本当はこうなんじゃないかなというところに行き着きました。

ほぼJSのままなので、導入のメンタルコストはちょっと知るだけでもだいぶ下がるんじゃないかなと思いました。「とっつきにくいな」という人がいたら、「いや、なんならJSのまま書けるよ」みたいなことを言ってあげるといいんじゃないかなと思いました。

私はVueのプロジェクトにいることが多くて、Vueのプロジェクト自体はVue cliでポチポチやって環境構築しているので、その中にTypeScriptのオプションもあるんですね。なので、一緒にチェック入れちゃえば一発なので、「思ったより開発環境簡単そうじゃない?」というのを思いました。

ただ、「使わなくても開発できるから使わなくていいんじゃないか」って私思ってたって書いたんですけど、ここは重要かなと思って、そう見えてるだけで、実は余計コストが発生してる。

気にしなきゃいけないところが増えてるとか、よくない値が入ってくるんじゃないかって気にしてエラー処理書いたりとかっていうのが増えているから、開発自体はできてるのかもしれないけど、手戻りが多かったりよくないことになってるんじゃないのかなというのは、TSに対しての学びというよりは、現状に対しての学びとしてありました。なので、ここはちょっと推していってもいいかなと思います。

私にこれ以上何ができたら実際にTypeScriptを使うようになるのかなって考えたときに、新規プロジェクトに入れるのはやってみてもいいかなって思いました。さっきから言ってるようにVue cliがあるのでポチポチするだけだし、書き方もほぼJSだし、なんかいけそうって思いました。

ただ、既存の生で書いてるプロジェクトに入れるのが億劫だなという気持ちがやっぱりまだあって、現状で開発できてるって思ってしまうこともあります。

あとは、私、開発環境がやっぱり嫌いなんですね。20分の間に3回も言うことになると思いませんでしたが、本当に環境構築が苦手なので。また、「なんとか動いてるのに手を加えるのか」みたいなハードルはちょっとあります。

言っていることが矛盾するかもしれませんが、私がいるプロジェクトの場合、フロントエンドでゴリゴリ開発していくというよりはバックエンドとフロントエンドが密結合していたりするので、私個人の裁量で動かせない箇所がすごく多かったりするとすぐわからなくなってしまって、ちょっとハードルが高いかなって思いました。

ただ、入れたほうがランニングコスト的には下がるのは認識してしまったので、私の中でどれだけその面倒くささよりメリットが勝るかということは考えています。

なので、プロジェクトの規模が大きくないということもありますが、そもそも私に学習がまだ足りてないなと思います。ただ、ここで学習が足りてないまま終わらせようという気にはならなかったので、「導入するためにもうちょっと勉強してみたいな」という気持ちになれたのはすごくよかったです。

なので、もしみなさんが今TypeScriptを使って開発している中で、使ったことがない人が入ってくる。「ああ、TypeScriptかー……」みたいになってるぞという方がもしいらっしゃたら、「いやいや、こういう感じなんだよ」みたいなことを言ってあげると、私みたいに気持ちが上向きになるかもしれないと思いました。

もうしばらく入門続けてみようかなと思います。ありがとうございます。

(会場拍手)