TypeScriptで使える応用の型定義について、自分用のメモとしてまとめてみました!
参考になれば幸いです。
基本的な型定義はこちらをどうぞ↓
【解説】TypeScriptの型定義をまとめてみた【初心者向け】目次
【応用】TypeScriptで使えるその他の型定義【まとめてみた】
TypeScriptで使える応用的な型定義がこちら。
・インターセクション型
・Type guard(型を絞り込む)
・型でtypeofを使う
・タグ付きUnion型
・型アサーション(as)
・constアサーション
・非nullアサーション演算子
・インデックスシグネチャ
・インデックスアクセス型
・関数のオーバーロード
・Nullish Coalescing
・オプショナルプロパティ
順に見ていきましょう。
「AかつB」という指定ができるインターセクション型というのがあります。
下記の例のように「&」で指定すれば実装できます。
type Fruit = {
name: string;
}
type Drink = {
amount: number;
}
type FruitDrink = Fruit & Drink;
const orange: FruitDrink = {
name: 'みかん',
amount: 500,
}
「FruitDrink」には、「Fruit」と「Drink」のオブジェクト内容が合体して入るみたいな感じですね。
インターフェースでも使えるよ
実は「Type guard」という、型を絞り込む方法が3つ存在します。
- ①:typeof
- ②:in演算子
- ③:instanceof
typeofを使えば、変数や値のデータ型を確認することができます。
function testFunc(a:string | number) {
if (typeof a === 'string') {
console.log(a);
} else {
console.log('stringではありません');
}
};
testFunc('テストです'); // テストです
testFunc(5); // stringではありません
in演算子を使えば、オブジェクトに特定のプロパティが存在するか確認ができます。
const orange = { name: 'みかん', num: 5 };
console.log('name' in orange); // true
console.log('amount' in orange); // false
instanceofを使えば、オブジェクトが特定のクラスやコンストラクタのインスタンスか確認できます。
class Food {}
class Fruit extends Food {}
const orange = new Fruit();
console.log(orange instanceof Fruit); // true
console.log(orange instanceof Food); // true
console.log(orange instanceof Object); // true
console.log(orange instanceof Array); // false
実はtypeofを使えば、型を取得することができます。
下記のように使えば、型を取得しつつ代入することが可能です。
const orange = {
name: 'みかん',
num: 3,
};
type Fruit = typeof orange;
// type Fruit = {
// name: string;
// num: number;
// }
オブジェクトの型に識別子(タグ)を持たせて、そのタグによって型を絞り込む方法があります。
これをタグ付きUnion型といいます。
例してこんな感じ↓
この場合「name」プロパティをタグとしています。
interface Orange {
name: "みかん";
num: number;
}
interface Peach {
name: "もも";
num: number;
}
type Fruit = Orange | Peach;
function getArea(kind: Fruit): number {
if (kind.name === "みかん") {
return kind.num + 5;
} else if (kind.name === "もも") {
return kind.num + 3;
}
throw new Error("ありません");
}
const orangeFruit: Fruit = { name: "みかん", num: 5 };
console.log(getArea(orangeFruit)); // 10
タグ付きユニオンを使えば安全に複数の型を扱い、エラーの発生を減らすことができます。
コンパイラに「これは特定の型だ」と伝える方法が型アサーションになります。
ようは型推論を上書きする機能ですね。
使い方は下記のいずれかで、Reactと被らないようにasで書くのが良いとのこと。
変数名 as 型名
or
<型を指定>
使い方の例としてはこんな感じ↓
const value: string | number = 'おはようございます';
const lengthValue: number = (value as string).length; // "value"を文字列として扱う
const value: string | number = 'おはようございます';
const lengthValue: number = (<string>value).length; // "value"を文字列として扱う
const hogeElement = <HTMLLinkElement>document.getElementById('hoge');
ただ型アサーションを乱用すると型安全性が損なわちゃいます。
そのため型が間違えてないかなど慎重に確認して使いましょう。
リテラル型(文字列リテラルや数値リテラル)に特化した型を定義できるのが、constアサーションになります。
ようはリテラル型に上書きする感じですね。
const fruit = "apple" as const; // 型: "apple"
const nums = [10, 20] as const; // 型: readonly [10, 20]
const orange = {
name: 'みかん',
num: 3,
} as const;
// fruit = "orange"; // エラー: 型 "apple" に代入できない
// nums.push(30); // エラー: readonly プロパティ
// const orange = {
// readonly name: 'みかん',
// readonly num: 3,
// }
非nullアサーション演算子と呼ばれている「! 」を使えば、「null」もしくは「undefined」でないと示すことができます。
const button = document.querySelector('button')!; // `!`でnullではないと宣言
button.addEventListener('click', () => {
console.log('クリックしました');
});
ただ開発者が「この値は絶対にnull/undefinedではない」と言えるときにのみ使うべきですね。
まず前提で型のプロパティが固定されてると、後からオブジェクトに追加できませんよね↓
type Fruit = {
name: string;
num: number;
}
const orange: Fruit = {
name: 'みかん',
num: 5,
// size: '小さめ', 追加できない
};
ただ「インデックスシグネチャ」を使えば、後からプロパティの追加が可能です。
それがこちら↓
[任意のキー名: キーの型名]: 値の型名;
type Fruit = {
name: string;
[key: string]: string;
}
const orange: Fruit = {
name: 'みかん',
num: '5',
size: '小さめ',
};
ただ「インデックスシグネチャ」で使う型は、全て同じにする必要があります。
上記だと全てstringにしていますね。
指定しているプロパティの型情報を参照できるのが「インデックスアクセス型」と呼ばれます。
下記のようにすれば、型を取得しつつ代入することが可能です↓
interface Fruit {
name: string;
num: number;
detail : {
id: number;
size: string;
}
}
type numItem = Fruit['num']; // numItemにはnumber型が入る
type idItem = Fruit['detail']['size']; // idItemにはstring型が入る
type FruitInfo = Fruit['name' | 'num'] // FruitInfoにはstring | numberが入る
関数オーバーロードは、複数の異なる「引数」や「戻り値」の型がある関数を定義する機能です。
ようは引数が2つ(string|number)あるとして、
戻り値の値が変わる(string|number)ときに使えるものですね。
引数が両方stringなら戻り値はstringにしたい。
引数が片方string、片方numberなら戻り値はstringにしたいなどなど。
関数に対して複数の型を定義できるよ
書き方としてはこちら↓
// オーバーロード定義
function 関数名(引数1: 型A): 戻り値A;
function 関数名(引数2: 型B): 戻り値B;
// 実際の実装
function 関数名(実際の引数: 型A | 型B): 戻り値A | 戻り値B {
// 処理
}
例えば下記の場合、文字列1つまたは文字列配列を受け取る場合に適切な戻り値を返すようになっています。
function greet(name: string): string;
function greet(names: string[]): string[];
function greet(input: string | string[]): string | string[] {
if (typeof input === 'string') {
return `おはよう, ${input}`;
} else {
return input.map(name => `おはよう, ${name}`);
}
}
const singleGreeting = greet("田中さん"); // おはよう, 田中さん
const multipleGreetings = greet(["田中さん", "山田さん"]); // [ 'おはよう, 田中さん', 'おはよう, 山田さん' ]
Nullish Coalescing (??) は、null または undefined の場合にデフォルト値を提供するための演算子です。
|| 演算子とは異なり、false、0、” のような値を、 null や undefined とみなさいのが特徴です。
const name: string | null = null;
const defaultName = "Anonymous";
const displayName = name ?? defaultName;
console.log(displayName); // "Anonymous"
undefined と null のみが対象なのが注意点です。
プロパティに「?」をつけることで、なくても良いという指定が可能です。
下記の例だとFruitを適用させたorangeに対して、numがなくてもエラーがでません。
type Fruit = {
name: string;
num?: number;
}
const orange: Fruit = {
name: 'みかん',
}
このようにプロパティの有無がわからないときは、「?」をつければエラーを発生させずに使用できます。
インターフェースでも使えるよ
【応用】TypeScriptで使えるその他の型定義【まとめてみた】:まとめ
・インターセクション型
・Type guard(型を絞り込む)
・型でtypeofを使う
・タグ付きUnion型
・型アサーション(as)
・constアサーション
・非nullアサーション演算子
・インデックスシグネチャ
・インデックスアクセス型
・関数のオーバーロード
・Nullish Coalescing
・オプショナルプロパティ
参考にしてみてね!