TypeScriptの応用的な使い方学習メモ。
下記記事の続きになります。
【TypeScript】入門してみたまとめ ✍🏻
&
を用いてインターセクション型の定義
インターセクション型:
交差型。AかつBのような使い方。ユニオン型はAまたはBのような使い方だが、つまりその逆。
// typeで書いた場合
type Engineer = {
name: string;
role: string;
};
type Blogger = {
name: string;
follower: number;
};
// type Engineer、Bloggerが合併した型。nameもroleもfollowerもないとだめ。
type EngineerBlogger = Engineer & Blogger;
const quill: EngineerBlogger = {
name: 'Quill',
role: 'front-end',
follower: 1000
}
// interfaceで書いた場合
interface Engineer {
name: string;
role: string;
}
interface Blogger {
name: string;
follower: number;
}
interface EngineerBlogger extends Engineer, Blogger {}
const quill: EngineerBlogger = {
name: "Quill",
role: "front-end",
follower: 1000,
};
Type Guard
条件文を使って、型を絞り込む方法のことをType Guardという。
型ガードを使用することによってifのブロックで特定の型に絞りこむことができます。
例1)
7つの型の結果を返すtypeof
を使う場合。
function toUpperCase(x: string | number) {
if(typeof x === 'string') {
return x.toLocaleUpperCase()
}
return '';
}
例2)
そのプロパティの有無がわかるin
を使う場合。
interface Engineer {
name: string;
role: string;
}
interface Blogger {
name: string;
follower: number;
}
type NomadWorker = Engineer | Blogger;
function describeProfile(nomadWorker: NomadWorker) {
if('role' in nomadWorker) { // roleプロパティを持つオブジェクトだったらtrue
console.log(nomadWorker.role)
}
}
例3)
classの場合に使用できるinstanceof
を使う場合。
class Dog {
speak() {
console.log("bow-wow");
}
}
class Bird {
speak() {
console.log("tweet-tweet");
}
fly() {
console.log("flutter");
}
}
type Pet = Dog | Bird;
function havePet(pet: Pet) {
pet.speak();
if (pet instanceof Bird) { // Birdクラスから生成されたインスタンスかどうか
pet.fly();
}
}
havePet(new Bird()); // → tweet-tweet flutter
タグ付きUnionを使って型を絞り込む方法
判別可能なユニオン型は、タグ付きユニオン(tagged union)や直和型と呼ぶこともあります。
class Dog {
kind: "dog" = "dog"; // リテラル型で追加
speak() {
console.log("bow-wow");
}
}
class Bird {
kind: "bird" = "bird";
speak() {
console.log("tweet-tweet");
}
fly() {
console.log("flutter");
}
}
type Pet = Dog | Bird;
function havePet(pet: Pet) {
pet.speak();
switch (pet.kind) { // kindを起点に条件分岐追加
case "bird":
pet.fly();
}
}
havePet(new Bird()); // → tweet-tweet flutter
型アサーションとは
手動で型を上書きする方法。
例えばDOM操作で下記のように取得する際に、型推論はHTMLElement | null
になる。
しかしHTMLElement
は抽象的なinterfaceで、今回の例でいうと本来inputタグにはvalueにアクセスできるはずだがinputタグだと正常に認識できずアクセスしようとするとエラーになる。
もう少し具体的なinterface、今回のinputタグで言うとHTMLInputElement
として認識させることでvalueへアクセスできるようになる。
※HTMLInputElement
はHTMLElement
を継承したもの
だめでした。getElementById
はnullを返す可能性もあるからとのこと。
そこで型アサーションを使って無理矢理、HTMLInputElement
って言い切る。
例1)
const input = <HTMLInputElement>document.getElementById('input')
例2)
const input = document.getElementById("input") as HTMLInputElement;
上記のどちらかの型アサーションをすることでHTMLInputElement
型にすることでvalueへアクセス可能になる。
jsxなどを考慮すると、基本的にはas
を使った型アサーションでよさそう。
!
(non-null assertion operator)の使用方法
!
を使うと絶対にnullじゃないと言い切る文法になる。
例)
const input = document.getElementById("input")
hoverしてみると型推論によってHTMLElement | null
であることがわかる。
インデックスシグネシャ
オブジェクトのプロパティを追加できる。
後述する注意点にある通り、存在しないプロパティにアクセスしようとしてもエラーを吐かなくなり、使用すべき場面は限られそう。
インデックスシグネチャ(Index Signatures、インデックス型)とは、インデックス(添字)を利用してオブジェクトのプロパティ(キー)の型を定義する機能のことをいいます。
【TypeScript】インデックスシグネチャ(Index Signatures)の概要と利用方法
通常、interfaceに存在しないプロパティは追加できない
// interface定義
interface Designer {
name: string
}
const designer: Designer = {
name: 'Quill',
role: 'web' // → エラー!
}
インデックスシグネチャを使用し、追加可能に。
// interface定義
interface Designer {
name: string;
[index: string]: string; // インデックスシグネチャ追加
}
const designer: Designer = {
name: "Quill",
role: "web", // → いくつでも追加できるようになる
aaa: 'aaa',
iii: 'iii',
...
};
型エイリアス(interface)によるオブジェクト型の型注釈によって、オブジェクトから1つ1つキーと値の型を定義するのは面倒..。
インデックスシグネチャを使うことで、オブジェクトがより多くのキーを含む可能性があることをTypescriptに伝える事ができる。
注意
-
インデックスシグネチャを使用した場合、他の全てのvalueの型もインデックスシグネチャの型にしないといけない
interface Designer { name: number; // 型をnumberに変更 [index: string]: string; } const designer: Designer = { name: 1, role: "web", };
-
[index: string]: string;
のinterfaceのプロパティ型部分をstringにした場合、そのinterfaceを使った変数のプロパティの型はstringでもnumberでもいいが、逆はできないinterface Designer { name: string; [index: string]: string; // → プロパティの型はstring } const designer: Designer = { name: "Quill", 1: "web", // → プロパティの型はstringだがnumberでもok };
interface Designer { name: string; [index: number]: string; // → プロパティの型はnumber } const designer: Designer = { name: "Quill", role: "web", // → エラー!プロパティの型はnumberのみ };
-
存在しないプロパティにもエラーを吐かないようになる
関数のオーバーロード
関数の戻り値の型を正しくTypescriptに認識させる方法。
要するに、異なる引数や戻り値のパターンがいくつかある関数をオーバーロード関数と言います。
例えば、引数がstringなら大文字に変換して返し、数値の場合はそのまま返す下記関数があったとする。
function toUpperCase(x: string | number) {
if (typeof x === "string") {
return x.toUpperCase();
}
return x;
}
// 定数upperHelloに返ってくる値を代入する。
const upperHello = toUpperCase('hello')
upperHelloをhoverして型を確認すると、string | number
のまま..。
この時点で、stringなのに正しく認識できていない。
この場合正しく認識させる方法として、前述した型アサーションが使える。
const upperHello = toUpperCase('hello') as string;
ただ、この関数を実行するたび、型アサーションしたり、引数がstringかnumberによって返ってくる型も決まっているのにこの方法はよくなさそう。そんな時に関数のオーバーロード
オーバーロード関数は、関数シグネチャと実装の2つの部分に分けて書く。
関数シグネチャとは、どのような引数を取るか、どのような戻り値を返すかといった関数の型のことです。
function toUpperCase(x: string): string; // 関数シグネチャ
function toUpperCase(x: number): number; // 関数シグネチャ
function toUpperCase(x: string | number) {
if (typeof x === "string") {
return x.toUpperCase();
}
return x;
}
const upperHello = toUpperCase('hello');
関数シグネチャ(関数の型)をパターンの数だけ記述しておくと、型アサーションとかしなくても引数に応じた型を認識してくれるようになる。
Optional Chainingの使い方
参照先がnullやundefinedだった場合、エラーを吐くが、?.
をつけるとエラーを吐かせずundefined
を返すようになる
JavaScriptではnullやundefinedのプロパティを参照するとエラーが発生します。
オプショナルチェーン?.
は、オブジェクトのプロパティが存在しない場合でも、エラーを起こさずにプロパティを参照できる安全な方法です。
interface DownloadedDate {
id: number;
// ?をつけるとあってもなくてもいいプロパティになる
user?: {
name?: {
first: string;
last: string;
};
};
}
const downloadedData: DownloadedDate = {
id: 1,
};
console.log(downloadedData.user.name); // エラー!オブジェクトは 'undefined' である可能性があります。
オプショナルチェーン?.
を追記するとエラーを吐かなくなり、参照先がundefinedかnullだった場合、undefinedを返し、何かデータがあったらそれを返すようになる。
console.log(downloadedData.user?.name); // '?'追記
Nullish Coalescingの使い方
??
の左辺がnullかundefinedなら右の値を返す。
||
や三項演算子みたいだが、それらと違って、Falsyな値かどうかではなく、nullかundefinedの時のみ右の値を返すことに注意。
左の値がnullまたはundefinedのときに右の値を返します。そうでない場合は左の値を返します。
サバイバルTypescript:Null合体 (nullish coalescing operator)
例) undefinedが返されるコード
interface DownloadedDate {
id: number;
user?: {
name?: {
first: string;
last: string;
};
};
}
const downloadedData: DownloadedDate = {
id: 1,
};
console.log(downloadedData.user?.name);
const userData = downloadedData.user // 追加
console.log(userData); // → undefined
例) Nullish Coalescing追加してundefinedかnullの場合は、右辺の値を返す
interface DownloadedDate {
id: number;
user?: {
name?: {
first: string;
last: string;
};
};
}
const downloadedData: DownloadedDate = {
id: 1,
};
console.log(downloadedData.user?.name);
const userData = downloadedData.user ?? "no-user"; // Nullish Coalescing追加
console.log(userData); // → no-user
LookUp型を使って、オブジェクトのメンバーの型を取得
オブジェクト型に対してプロパティ名でアクセスするようなものを型レベルにしたようなもの。
interface DownloadedDate {
id: number;
user: {
name?: {
first: string;
last: string;
};
};
}
type id = DownloadedDate["id"] // → type id = number
or文や、階層の書き方もある
type id = DownloadedDate["id" | "user"]
type user = DownloadedDate["user"]["name"]
型の中でtypeof
を使って、値の型を取得する
typeof 値
で値の型を取得できる。
TypeScriptのtypeofは変数から型を抽出する型演算子です
const peter = {
name: "Peter",
age: 38,
};
type PeterType = typeof peter;