any型
どんな値でも受け入れる型。
any型に代入したオブジェクトのプロパティ、メソッドは使用できる。
const anyStr: any = "apple";
// 5
console.log(anyStr.length);
any型のオブジェクトはどんな型にも代入できる。
any型はTypeScriptが型チェックを放棄した型だからできてしまう
const anyStr: any = "apple";
const bool: boolean = anyStr;
// apple string
console.log(bool, typeof bool);
unknown型
どんな値でも受け入れる型。any型と違うのは、unknown型に代入したオブジェクトのプロパティ、メソッドはどちらも使用できないこと。
const unknownStr: unknown = "apple";
// エラー:'unknownStr''は 'unknown' 型です。
console.log(unknownStr.length);
unknown型のオブジェクトは代入できない。
unknown型は型安全なany型と言われる
const unknownStr: unknown = "apple";
// エラー:型 'unknown' を型 'boolean' に割り当てることはできません。
const bool: boolean = unkonwnStr;
never型
never型は「値を持たない」型。
never型のオブジェクトには何も代入できない。any型でも代入できない。
ただしnever型を別の型に代入することはできる。
// ✕:string型をnever型オブジェクトに代入できない
// エラー:Type 'string' is not assignable to type 'never'.
const nev: never = "apple";
// 〇:never型をstring型に代入することはできる。
const nev = "apple" as never;
const foo: string = nev;
// string型として使える
console.log(foo.toUpperCase());
never型として生まれたら最後までneverのまま。
never型の用途
異常な関数の戻り値の型として使われる。
- 例外が必ず発生する関数の戻り値の型
- 無限ループの関数の戻り値の型
- Switch文などの網羅性のチェック
function throwError(): never {
throw new Error();
}
function forever(): never {
while (true) {} // 無限ループ
}
文字リテラルのユニオン型オブジェクトに対して、型で処理分岐する場合、分岐する型の指定が不足していたり、後から型を追加したときにエラーとして拾えるようにするために使う。
まずはnever型を使わず、網羅性が不足しているのソースコードを記載
type Fruit = "apple" | "orange" | "banana";
const fruit: Fruit = "banana";
checkFruits(fruit);
// Fruit型の文字リテラル型ごとに処理を分岐。
// "banana"型の分岐が無く、"その他"の処理にいってしまうがエラーにならないので、
// 気づけない。
function checkFruits(fruit: Fruit) {
switch (fruit) {
case "apple":
console.log("リンゴ");
break;
case "orange":
console.log("オレンジ");
break;
default: {
console.log("その他");
break;
}
}
そこで"banana"型の分岐が抜けていた場合にtypescriptがエラーを検知してくれるようにするため、defaultでnever型を使う。
type Fruit = "apple" | "orange" | "banana";
const fruit: Fruit = "banana";
checkFruits(fruit);;
function checkFruits(fruit: Fruit) {
switch (fruit) {
case "apple":
console.log("リンゴ");
break;
case "orange":
console.log("オレンジ");
break;
default: {
// "banana"型の分岐が無いのでエラーになる
// エラー:型 'string' を型 'never' に割り当てることはできません
const exhaustivenessCheck: never = fruit;
break;
}
}
}
type Fruit = "apple" | "orange" | "banana";
const fruit: Fruit = "banana";
checkFruits(fruit);;
function checkFruits(fruit: Fruit) {
switch (fruit) {
case "apple":
console.log("リンゴ");
break;
case "orange":
console.log("オレンジ");
break;
case "banana":
console.log("バナナ");
break;
default: {
// この時の"fruit"変数が"never型"になっている。
const exhaustivenessCheck: never = fruit;
break;
}
}
}
型ガード
if文/switch文とtypeof/instanceof/in演算子を使って、対象のオブジェクトの型を確認すること。
const val = "apple";
// ↓ここが型ガード
if (typeof val === 'string') {
// string型で確定なので安心してtoUpperCaseが使える
console.log(val.toUpperCase();
} else {
console.log('想定外の型');
}
型ガード関数
unknown型の変数に対して型ガードを行った後、そのunkonwn型の変数の型の変更まで行う。
ポイントは型ガード(型の絞り込み)を行う対象の引数がunkown型であること。
型ガードだけだと、対象の引数がunkown型の場合、後続の処理が実行できない。
const val: unknown = "apple";
if (simpleCheck(val)) {
// 型ガードはクリア!ただし、valはunknown型のままなので、
// toUpperCase関数が使えない(エラー:'val''は 'unknown' 型です。)
console.log(val.toUpperCase());
}
// 対象のunkonwn型オブジェクトの値の型stringかどうかだけをチェック
function simpleCheck(x: unknown): boolean {
return typeof x === "string";
}
const val: unknown = "apple";
if (typeGuardCheck(val)) {
// 型ガードにクリアし、valもstring型に変換されているので、
// toUpperCase関数が使える
console.log(val.toUpperCase());
}
// 戻り値の型注釈に"x is string"を指定
function typeGuardCheck(x: unknown): x is string {
return typeof x === "string";
}
型ガード関数の戻り値の型注釈であるx is string
という記述は型述語という。
引数xはstringであるとTypeScriptに解釈させている。
型のチェックはreturn typeof x === "string";でやっている、ということを忘れないように。
typescriptバージョン5.5以降では型述語の注釈無しでも型ガード関数と同じ扱いになる。
const val: unknown = "apple";
if (check(val)) {
// 型ガードにクリアし、valもstring型に変換されているので、
// toUpperCase関数が使える
console.log(val.toUpperCase());
}
// 戻り値の型注釈が無い場合、型ガード関数と同じ扱い。
function check(x: unknown) {
return typeof x === "string";
}
型定義ファイル
拡張子が**.d.ts**で型情報がつまったファイル。
declare
JavaScriptファイルに変数・関数・クラスを記述しただけでは、TypeScriptはその存在を知らず、Not Foundになる。
function hello(name) {
return "Hello, " + name;
}
// エラー:Cannot find name 'hello'.
hello("taro");
TypeScriptに存在知らせるために使うのがdeclare(アンビエント宣言)。
declareは**.d.ts**ファイルに記述する
declare function hello(name: string): string;
コンストラクトシグネチャ
コンストラクタの型を示している
declare const foo: new (arg: number) => unknown;
タプル型
配列の各要素の型や要素数を指定できる。
type tupleType = [string, number, boolean];
const tup: tupleType = ["apple", 10, true];
// エラー:ソースには 4 個の要素がありますが、ターゲットで使用できるのは 3 個のみです。
const tup2: tupleType = ["apple", 10, true, 'banana'];
ただしpush
で要素の追加はできる
type tupleType = [string, number, boolean];
const tup: tupleType = ["apple", 10, true];
tup.push("banana");
// [ 'apple', 10, true, 'banana' ]
console.log(tup);
// 4
console.log(tup.length);
// 数字リテラルで追加した要素番号を指定するとエラーになる
// エラー:長さ '3' のタプル型 'tupleType' にインデックス '3' の要素がありません。
console.log(tup[3]);
// ただ、lengthを使うとエラーにならない
// banana
console.log(tup[tup.length - 1]);
Classを定義すると、Classは型になる
class Person {
name: string;
constructor(initName: string) {
this.name = initName;
}
// "Person"をtypeとして使える
greeting(this: Pick<Person, "name">) {
console.log(this.name);
}
}
Classの省略記述
class Person {
// コンストラクタの引数にpublic演算子を入れると
// publicなnameプロパティの作成と初期化までやってくれる
constructor(public readonly name: string, private age: number) {}
showAge() {
console.log(this.age);
}
incrementAge() {
this.age += 1;
}
greeting(this: Pick<Person, "name">) {
console.log(this.name);
}
}
const ken = new Person("KEN", 20);
型推論を残して型のチェックをする
type Person = { name: string };
// {name: string}型
const ken = { name: "KEN" } satisfies Person;
// 型注釈
// Pserson型
const bob: Person = { name: "BOB" };
オブジェクトのキーに過不足が無いかチェックできる。しかも型推論で使える。
type Color = "red" | "green" | "blue";
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: "#0000ff",
} satisfies Record<Color, unknown>;
// redをnumber[]として使える
palette.red.forEach((x) => console.log(x));
const oldpalette: Record<Color, string|number[]> = {
red: [255, 0, 0],
green: "#00ff00",
blue: "#0000ff",
};
// エラー:redがnumber[]として利用できない
oldpalette.red.forEach((x) => console.log(x));