Mapped型での再マッピング
次に紹介するのは、Mapped型(Mapped Type)の新しい仕組みです。
Mapped型とは
Mapped型は、既存の型をもとにオブジェクトリテラル型を生成する仕組みです。例えば、リスト11のBlankSpaceAndPositionを使って、リスト12のような型定義をしたとします。このBlankSpaceConfig型のオブジェクトは、MarginTop、MarginBottom、…、PaddingEndの全プロパティを含むオブジェクトとなり、それぞれのプロパティ値はnumber型となります。
type BlankSpaceConfig = {
[key in BlankSpaceAndPosition]: number;
};
このinの右側には、文字列を列挙したものならなんでもかまいませんので、よく使うのは既存のオブジェクトのプロパティ名をそのまま利用する場合です。例えば、リスト1で登場したPersonalBaseData型(プロパティとしてid、name、phoneが存在するオブジェクト型)を元に同じプロパティを含むオブジェクト型を定義する場合、リスト13のコードとなります。keyofキーワードで、既存のオブジェクトのキーを取り出しているところがポイントです。
type PersonalData = {
[key in keyof PersonalBaseData]: PersonalBaseData[key];
};
このPersonalData型のオブジェクトは、PersonalBaseData型と同一のプロパティ、すなわち、id、name、phoneが含まれている必要があります。
この仕組みは、既存のオブジェクトと同一プロパティのオブジェクトリテラル型を定義したい場合に便利であり、当然リスト14のようにジェネリクスと組み合わせることができます。リスト13ではPersonalBaseDataと記述した部分を、(1)にあるように、ジェネリクスの型定義(T)に置き換わっただけです。
type CopyObject<T> = {
[key in keyof T]: T[key]; // (1)
}
const jiro: CopyObject<PersonalBaseData> = { // (2)
id: 6675,
name: "鈴木二郎",
phone: "09087654321"
};
このようなCopyObject型のオブジェクトを定義する場合は、(2)にあるように、型指定のところでジェネリクスとして指定した型と同一のプロパティが含まれている必要があります。
Mapped型のプロパティ名が加工可能になった
これまでは、このMapped型のプロパティ名の部分は、元のオブジェクトのプロパティ名がそのまま利用されていました。せいぜい可能だったのは、readonlyやオプション(?)をつけたり外したりできるぐらいでした。これが、バージョン4.1で加工が可能となりました。その際、前節のテンプレート文字列型が活躍します。例えば、リスト15のようなコードです。
type CopyObjectU<T> = {
[key in keyof T as `${Capitalize<string & key>}`]: T[key]; // (1)
}
リスト15のポイントは、(1)にあるasキーワードです。このasに続けて、元となるオブジェクトから抽出したプロパティ名(key)の加工処理を記述することで、加工されたプロパティ名のオブジェクト型が定義されます。これを、公式ドキュメントでは「キーの再マッピング(Key Remapping)」と呼んでいます。
リスト15では、前節で紹介したCapitalizeを使って、プロパティ名の先頭文字を大文字に変換する加工を施しています。ただし、Capitalizeには文字列をジェネリクスとして指定する必要がある一方で、keyが必ずしも文字列とは限りません。そこで、「string &」を記述してstring型を結合しています。このようにして定義されたCopyObjectUを使って、リスト14の(2)と同様にCopyObjectU<PersonalBaseData>型のオブジェクトを定義する場合、id、name、phoneではなく、Id、Name、Phoneの各プロパティが含まれている必要があります。
インデックスシグネチャの新しいキー
型システムに関する新機能を紹介する本稿も、残すところ3項目です。ここから紹介するのは、こぢんまりとしながらも地味に便利な変更点です。まず紹介するのは、前節とも関連する内容で、インデックスシグネチャの新しいキーに関する内容です。
インデックスシグネチャは、そのキー部分として指定できるデータ型がstringかnumberに限定されていました。これが、バージョン4.4で、Symbol型とテンプレート文字列型が追加されました。例えば、テンプレート文字列を利用する場合、リスト16のような定義になります。このNoList型のオブジェクトのプロパティでは、No3453のようなNoから始まるプロパティ名以外はエラーとなるようになります。
interface NoList {
[key: `No${number}`]: string;
}
catchブロック変数でunknownが可能
次に紹介するのは、バージョン4.0で導入されたcatchブロック変数でunknownが可能になったことです。例えば、リスト17のようなコードです。
try {
:
}
catch(error: unknown) { // (1)
if(error instanceof FetchError) { // (2)
error.showMsg(); // (3)
}
}
リスト17の(1)でcatchしたエラー変数(error)は、これまでany型となっていました。そのために、存在しないはずのプロパティやメソッドへのアクセスが可能となってしまっていました。これを、(1)のようにunknown型と指定できるように、バージョン4.0で変更されています。そのため、(2)のようにその型を調べた上で、(3)のようにその型固有のメソッドを呼び出すなどが可能となりました。
returnのない関数の戻り値
最後に紹介するのは、関数の戻り値の型に関する新機能です。TypeScriptでは関数の戻り値の型を記述できます。そして、戻り値がない関数、すなわち、returnを記述しない関数の場合は、void、または、anyを指定します。もし、returnのない関数で戻り値の型を指定しない場合は、型推論によってvoidとして扱われます。
ここで、undefinedを戻り値の型とする関数の型定義を行ったとします。例えば、リスト18のような関数があるとします。
function doProcess(name: string, func: (data: string[]) => undefined): void {
:
}
このdoProcess()関数の第2引数には、戻り値の型がundefinedの関数を渡す必要があります。この引数に、リスト19のようにreturn文のない関数を渡したとします。
doProcess(
"ループ処理",
(data: string[]): undefined => {
for(const str of data) {
:
}
// return;
}
);
先述のように、本来なら、returnのない関数を記述する場合、その戻り値の型はvoidかanyでないとダメでした。そのため、リスト19のコードはエラーとなります。これを避けるために、コメントアウト行のreturn;のような、無意味なreturn文を記述する必要がありました。これが、バージョン5.1で修正され、returnを記述しない関数の戻り値の型にundefined型が追加され、結果、リスト19のコードもエラーとならないようになりました。
まとめ
TypeScriptのバージョン5.2までに導入された新機能をテーマごとに紹介する本連載の第3回目はいかがでしたでしょうか。
今回は、型システムに関するアップデートを紹介しました。次回は、型の絞り込みに関する新しい仕組みを紹介します。
