TypeScriptについて学び始めたのでアウトプットしていく。
今回は基本的な型について。
型定義ってめんどくさいし時間かかるしーと思ってたけど、めんどくさくてもやった方が結果的に早いことが多いなーというのが学び始めた今感じる印象。
まさに急がば回れ。
そしてコーディング量増えると思ってたんだけど、VS Codeが優秀すぎて型定義しておけば、「これでしょ?」って感じで補完してくれるし、返り値が何になるかとかもマウスオーバーで出してくれるから書いてて本当に楽しい。
もうJavaScriptには戻れない気がする。。笑
では早速基本的な型定義について見ていく。
JavaScriptの型チェック vs TypeScriptの型チェック
TypeScriptでは型をチェックすることができるが、
実はJavaScriptでも以下のようにしてtypeofで型をチェックすることはできる。
console.log(typeof 'Suna') // string
JSとTSの型チェックの違いはズバリ、JSは実行時にチェックする、TSはコンパイル時にチェックするということ
TSはコンパイル時点でチェックしてくれるのでコンパイル時点で事前にエラーを回避できるようになっている。
あと型定義って全てにおいて明示的にしなきゃいけないと思ってたんだけど、TSでは入力された値に応じて型推論してくれるので、基本的には型推論に任せておいて、推論が異なる場合のみ明示的に示すという方針でいいらしい。
こうすればコーディング量もさほど増えないし、有能。
型定義の基本形
以下でいくつかの型定義の仕方を見ていく。
なお、stringとnumber, objectは自明なので省略。
Array型とTuple型
Array型
const person = {
...
role: [2, "author"]
}
// personは(string | number)と推論されるため、以下のことは許可されてしまう
person.role[1] = 10;
配列のrole[1]はstringだけを指定したくても、Array自体が(string | number)となっている。
この場合、配列の中の要素の場所と型は一致しない。
このような時はTuple型を使う
Tuple型
const person: {
...,
role: [number, string]
} = {
...
role: [2, "autor"]
}
person.role[1] = 10; // Error!
1-4行目で明示的に型を指定している。
型推論で問題なければここは記載しなくて良い。
配列は型推論だとArray型になるため、Tuple型にするためには明示する必要がある。
そして3行目がTuple型で、ここで配列の中の要素の場所と型を指定する。
長さも指定するため、以下のことは許可されない。
person.role[0, "admin", "user"] //Error!
しかし、Array, Tupleともにperson.pushは許可されてしまうので注意。
Enum型
enum型は対応する文字列を数値と一緒に設定してくれるもの。
JSにはなく、TSのみ。でもコンパイルする時にJSでいい感じの記法に直してくれる。
コンパイルされたJSを見ると長ったらしい記述になってるけど、TSではスッキリかけてとてもよき。
まずはenumを使ってない場合の問題点。
const person = {
...
role: "READ ONLY USER"
}
// 文字が異なるので条件に合わない
if (person.role === "READ-ONLY-USER") {
console.log("読み取り専用");
}
このような場合は”READ ONLY USER”の文字列を一言一句合わせなければならなくなる。
ここでタイポしてても別にエラーではないから特にコンパイルエラーなどは得られない。
こんな時にenumを使うと便利
enum Role {
ADMIN,
READ_ONLY,
AUTHOR
}
const person = {
…
role: Role.ADMIN
}
if (person.role === Role.ADMIN) {
console.log("管理者");
}
結局ADMINとか書いてるから間違えうるじゃねーかと思ったけど、間違えてたらVS Codeがエラーだぞって教えてくれる。
単純な文字列の記載だったらエラーじゃないから教えてくれない。TS & VS Code are 有能 of 有能。
enumの数字は0から始まる。
ADMIN = 1と明示すれば1から始まり、その次は+1ずつincrementしてくれる。
Literal型
stringやnumberよりもさらに狭い定義。
type GAFA = "Google" | "Amazon" | "Facebook" | "Apple";
const gafa: GAFA = "Microsoft"; // エラー
その値そのものを定義する。Unionと一緒に使って特定の文字列 or 数値しか認めない時によく使う。
型エイリアス
Literal型のような定義を以下のように毎回書くのは面倒
functionA (
...
result: "as-number" | "as-string"
) {
...
}
このような場合にエイリアスを使う。
型の定義を変数化するみたいな感じ。
type ConversionDescriptor = "as-number" | "as-string"
functionA (
...
result: ConversionDescriptor
) {
...
}
Function型
function add(n1: number, n2: number) {
return n1 + n2;
}
function printResult(num: number): void {
…
}
let functionA: Function;
functionA = add;
console.log(functionA(1, 2));
上記のようにFunction型を定義することができる。
しかしこれはfunctionAにprintResultを入れることもできてしまう。
なぜならprintResultもFunction型だから。
TSが関数の中についてもう少し理解できるようにすればここでエラーを得ることができる。
そこで以下のようにする。
let functionA: (a: number, b: number) => number;
これはfunctionAが2つのnumberの引数を受け取り、numberを返すということを示している。
この状態でfunctionA = printResult
とするとエラーで教えてくれる。
callback
function addHandle(n1: number, n2: number, cb(num: number) => void) {
const result = n1 + n2;
cb(result);
}
addHandle(10, 20, (result) => {
console.log(result);
});
6行目でresult: numberとするのは冗長。
関数の定義でcb(num: number)と指定しているのでそれで十分。
返り値がvoidになっているが、console.log(result)の下にreturnを入れてもエラーにはならない。
その関数がreturnで何を返そうが、受け取るのはvoidなので関係ない。
TSは引数の型は厳密にチェックするが、返り値に関してはそれほど厳密ではない。
unknown型
let userInput: unknown;
let userName: string;
userInput = 2;
userInput = "aaa";
userName = userInput; //Error!
上記でuserInputはunknownとされており何の型か分からないので、stringと決まっているuserNameには入れられない。
もしuserInputがany型であれば入れられてしまう。
unknownの場合は以下のように条件チェックが必要になる。
if (typeof userInput === "string") {
userName = userInput;
}
そのため、any型よりはunknown型の方が安全。
never型
エラー文を返す場合など、その関数が絶対に返り値を持たない場合はneverを使う方がメンテしやすい。
TSの型推論では返り値を返さない場合はvoid型になる。
never型で明示する方が可読性やメンテ的によき。
おわりに
今回は型定義の基本形を見た
他にもインターフェースとかジェネリクスとかあるけど、それは別記事でまとめた。
色々型定義の仕方とかあってめんどいなーと思ってたけど、やっぱ手を動かしたり人が書いたコード見てるうちに見慣れてくる。
型定義の仕方は他の言語でも概念的には同じ部分が多いと聞いたので、TypeScriptはこの機会にちゃんと学んでおこうと思った。
静的型付け言語ちゃんと扱えるようになりたいんじゃ。
自分が想像していた以上に便利だなと思ったので、頑張って習得する!
関連記事
インターフェースについてはこちら
コメント