SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

JavaScriptの限界を超える!TypeScript初めの一歩

【TypeScriptの基礎を学ぶ】コードの表現力を高めよう!型ガードとジェネリクスによるデータ操作術

JavaScriptの限界を超える!TypeScript初めの一歩 第3回

第2章 コードの表現力を高める応用的な型

 実際の開発で型を付けていくと、「記述が煩雑だ」と感じたり、特定のケースで「どのように型を表現すればよいかわからない」といった課題に直面したりします。そうした時のヒントになるTypeScriptの各種機能を紹介していきます。

ジェネリクス

 ジェネリクスは、総称型と呼ばれるものです。型が出てくるプログラミング言語では、関数の引数に型を書きます。しかし、型だけが違う似た関数が大量に作られるというデメリットもあります。

 ジェネリクスはこうした不便を解消してくれます。関数の引数で型を決め打ちせず、どの型なのかの情報を渡して関数を実行できます。

 それではTypeScriptのジェネリクスの例を見ていきましょう。初期値がn個連続している配列を作るコードを考えてみます。初期値の型は不明です。まずは、ジェネリクスを使わずに、any型で作ってみます。

function initFillArrayAny(val: any, repeat: number): any[] {
    return new Array(repeat).fill(val);
}

let numbersAny: number[] = initFillArrayAny(123, 4);
let stringsAny: string[] = initFillArrayAny("hoge", 4);
let invalidAny: string[] = initFillArrayAny(123, 4);
console.log(invalidAny[0].split(''));   // エラーが起きる

 initFillArrayAny関数では、引数valにどんな型が入るのか分からないために、any型としています。

 このコードは、TypeScriptではエラーが表示されません。しかしJavaScriptにして実行すると、最後のinvalidAny[0].split('')でエラーが起きます。

 initFillArrayAny(123, 4)で生成される配列は、数値が連続する[123, 123, 123, 123]ですが、変数invalidAnyに付けた: string[]の型注釈でエラーを検出できていません。もっと厳密に型を検査する必要があります。

 次は、型を指定した関数を複数作ったケースです。

function initFillArrayString(val: string, repeat: number): string[] {
    return new Array(repeat).fill(val);
}
function initFillArrayNumber(val: number, repeat: number): number[] {
    return new Array(repeat).fill(val);
}

let numbersNumber: number[] = initFillArrayNumber(123, 4);
let stringsString: string[] = initFillArrayString("hoge", 4);
let invalidString: string[] = initFillArrayString(123, 4);  // エラーになる

 こうすると、initFillArrayString(123, 4)でエラーが検出されます。実行時にinvalidAny[0].split('')でエラーが起きることを防げます。

 ただ、このように書くと、型ごとに関数を書く必要があり煩雑です。この手間を減らすのがジェネリクスです。型自体を変数にして型変数名を記述してコードをまとめます。例を示します。

function initFillArray<T>(val: T, repeat: number): T[] {
    return new Array(repeat).fill(val);
}

let numbers: number[] = initFillArray(123, 4);
let strings: string[] = initFillArray("hoge", 4);
let invalid: string[] = initFillArray(123, 4);  // エラーになる

 上のコードでは、最後の行のinvalidでエラーが出ます。エラーの内容はType 'number[]' is not assignable to type 'string[]'.(型 'number[]' は型 'string[]' に代入できません。)です。

 関数の名前のあとに<T>と書くことで、型変数Tが使えます。<T>は型変数名の定義です。Tは慣習的に使われる文字です。for文のiのようなものです。

 ここでは、引数valの型Tを、返り値でT[]のように利用します。引数valの型がnumberなら、返り値の型はnumber[]になります。引数valの型がstringなら、返り値の型はstring[]になります。

 上記のinitFillArray関数を使うコードは、次のようにも書けます。

let numbers2: number[] = initFillArray<number>(123, 4);
let strings2: string[] = initFillArray<string>("hoge", 4);
let invalid2: string[] = initFillArray<string>(123, 4); // エラーになる

 この場合は、最後の行の123でエラーが出ます。エラーの内容はArgument of type 'number' is not assignable to parameter of type 'string'.('number' 型の引数は 'string' 型のパラメータに割り当てることができません。)です。こちらの方が、より早い段階でエラーを検出できています。

 <number><string>は型引数と呼ばれるものです。これらを省略すると、型引数の型推論が行われます。

 また、型変数に代入可能な型はextendsを利用して限定できます。例を示します。

function initFillArraySN<T extends string | number>(val: T, repeat: number): T[] {
    return new Array(repeat).fill(val);
}

let numbersSN: number[] = initFillArraySN(123, 4);
let stringsSN: string[] = initFillArraySN("hoge", 4);
let booleansSN: boolean[] = initFillArraySN(true, 4);   // エラーになる

 ここではstringnumberに限定しています。そのため最後のようにbooleanを引数に書くとエラーが発生します。

 ジェネリクスには他にも応用的な機能がありますが、まずは基本を理解し、開発で不便を感じた際に、必要に応じて学習を進めていくとよいでしょう。

Promiseの型指定

 TypeScriptでPromiseを利用するときは少し注意が必要です。

 JavaScriptで頻繁に利用される、Promiseを返す関数を例に見ていきましょう。この例は、TypeScriptではエラーが出ます。

function waitAndGet(time: number): Promise {
    return new Promise(resolve => {
        setTimeout(() => {
            let n: number = Math.trunc(Math.random() * 100);
            resolve(n);
        }, time);
    });
}

 VSCode上でPromiseに赤線が引かれてエラーが検出されます。なぜでしょうか。

 警告が出ているPromiseにマウスカーソルを重ねてエラーの内容を見てみます。Generic type 'Promise<T>' requires 1 type argument(s).(ジェネリック型 'Promise<T>' には 1 つの型引数が必要です。)と書いてあります。

 次に「Ctrl」を押しながら、このPromiseをクリックします。interface Promise<T> { ~ }と表示されます。TypeScriptでは、このPromiseはインターフェースで、宣言する時に型引数を要求していることが分かります。

 そこで次のように直すと問題が解決します。

function waitAndGet(time: number): Promise<number> {
    return new Promise(resolve => {
        setTimeout(() => {
            let n: number = Math.trunc(Math.random() * 100);
            resolve(n);
        }, time);
    });
}

 Promise<number>のようにresolve()で送る値の型を書きます。ここではnumberなので<number>と書きます。

 この関数を使う例も示します。

(async function() {
    let attack: number = await waitAndGet(1000);
    console.log({attack});
    let defense: number = await waitAndGet(1000);
    console.log({defense});
})();

型アサーション

 次に型アサーションを紹介します。型推論を上書きする機能です。asを利用して、「~としてあつかう」というプログラマーの意図をTypeScriptのコンパイラに伝えます。

 例を示します。

let obj = JSON.parse('{"name": "Tom"}');
let val: string | number = obj.name;
let len: number = val.length;   // エラーになる

 上のコードは、TypeScriptではエラーが出ます。JSON.parseany型を返します。そのためobjnameany型です。val: string | numberに代入した状態では、valの型はstring | numberになります。そのため、val.lengthのように文字列のプロパティを得ようとするとエラーが出ます。

 本来なら、JSON.parseで得たあとに、型ガードで型を絞り込むべきですが、今回は例なので割愛します。

 プログラマーの視点からすると、上のコードのvalが文字列であることが明確です。そこでasを利用して次のように書くことができます。

let obj = JSON.parse('{"name": "Tom"}');
let val: string | number = obj.name;
let len: number = (val as string).length;

 変数valstringとしてあつかうと書いているわけです。このように型推論を上書きすることで、エラーを避けることができます。

 型アサーションにはもう一つ書き方があり、<>を利用して次のように書くこともできます。

let obj = JSON.parse('{"name": "Tom"}');
let val: string | number = obj.name;
let len: number = (<string>val).length;

 この型アサーションは、キャスト(型の変換)とは違います。コンパイルした時に除去されるTypeScriptの型にまつわる機能です。

 また、型アサーションで行えるのは可能性の絞り込みです。無理矢理の読み替えは行えません。次のようなプログラムはエラーになります。

let num: number = 123;
let len4: number = (num as string).length;  // エラーになる

次のページ
第3章 まとめと次回予告

この記事は参考になりましたか?

JavaScriptの限界を超える!TypeScript初めの一歩連載記事一覧

もっと読む

この記事の著者

柳井 政和(ヤナイ マサカズ)

クロノス・クラウン合同会社 代表社員http://crocro.com/オンラインソフトを多数公開。プログラムを書いたり、ゲームを作ったり、記事を執筆したり、マンガを描いたり、小説を書いたりしています。「めもりーくりーなー」でオンラインソフト大賞に入賞。最近は、小説家デビューして小説も書いています(『裏切りのプログラム』他)。面白いことなら何でもOKのさすらいの企画屋です。 

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/21597 2025/09/05 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー