TypeScriptのデフォルト型パラメータの使い方と例を徹底解説!初心者でも理解できるGenericsの基本
生徒
「先生、TypeScriptのジェネリクスで“デフォルト型パラメータ”っていうのを見たんですが、どういうものなんですか?」
先生
「いいところに気がつきましたね。ジェネリクスのデフォルト型パラメータは、型引数を指定しなかったときに自動的に使われる“初期設定の型”のことです。」
生徒
「なるほど…でも、どうしてそんな仕組みが必要なんですか?」
先生
「毎回すべての型を指定するのは面倒ですよね。デフォルト型パラメータを使うと、より柔軟で書きやすいコードにできるんです。これから実際の使い方を見てみましょう。」
1. ジェネリクス(Generics)とは?
まず、ジェネリクス(Generics)とは、TypeScriptで「型を後から指定できる」仕組みのことです。プログラムを書くときに、変数や関数の中で使う型をその場で固定せず、呼び出すときに自由に決められるようになります。例えば、数値でも文字列でも使える関数を作るときにとても便利です。
以下はジェネリックを使った簡単な例です。
function identity<T>(value: T): T {
return value;
}
console.log(identity<number>(100)); // 数値を渡す
console.log(identity<string>("Hello")); // 文字列を渡す
このように、<T>という「型パラメータ」を使うことで、型を汎用的に扱うことができます。TはType(型)の略で、呼び出し時にnumberやstringなどに置き換わります。
2. デフォルト型パラメータとは?
デフォルト型パラメータ(default type parameter)とは、型引数が指定されなかったときに自動で使われる「初期値の型」のことです。関数の引数に初期値を設定できるのと似た考え方です。
例えば、次のようなジェネリック関数があります。
function getValue<T = string>(value: T): T {
return value;
}
この関数では、型パラメータTのデフォルト型としてstringが設定されています。つまり、型を指定しないで呼び出した場合は、自動的にstring型が使われます。
実際の呼び出し例を見てみましょう。
console.log(getValue("こんにちは")); // 型指定なし → string型
console.log(getValue<number>(123)); // 明示的にnumber型を指定
こんにちは
123
このように、型を指定しなくても問題なく動作します。型を指定しなかった場合、デフォルトのstringが使われる仕組みになっています。
3. デフォルト型パラメータのメリット
デフォルト型パラメータを使うメリットは、コードをより簡潔で柔軟に書ける点です。特にライブラリ開発や汎用的な関数を作るときに便利です。
例えば、WebアプリなどでAPIからデータを取得する関数を作る場合を考えましょう。
function fetchData<T = any>(url: string): Promise<T> {
return fetch(url).then(response => response.json());
}
この関数では、デフォルトでany型を設定しています。これにより、型を指定しなくても柔軟に使えますが、必要に応じて自分で型を指定することも可能です。
// 型指定なし
fetchData("https://example.com/data");
// 型指定あり
fetchData<{ id: number; name: string }>("https://example.com/users");
このように、型を指定しなくても動作しますが、後から明確な型を与えることもできるため、初心者にも扱いやすい構文です。
4. クラスでのデフォルト型パラメータの使い方
デフォルト型パラメータは、関数だけでなくクラスでも使うことができます。例えば、データを管理するクラスを作るときに活用できます。
class Box<T = string> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const stringBox = new Box("TypeScript"); // デフォルト型string
const numberBox = new Box<number>(2025); // number型を指定
上記のように、T = stringと指定することで、型引数を省略した場合は自動的にstring型が適用されます。
5. デフォルト型パラメータと制約(extends)の組み合わせ
デフォルト型パラメータは、extendsキーワードと組み合わせることもできます。これは、「型に制限をかけつつ、デフォルト型も設定する」という使い方です。
interface User {
name: string;
age: number;
}
function getUserInfo<T extends User = User>(user: T): string {
return `名前: ${user.name}, 年齢: ${user.age}`;
}
このように、デフォルト型を指定しておくことで、型を省略しても安全に動作します。さらに、型を拡張したオブジェクトを渡すことも可能です。
getUserInfo({ name: "太郎", age: 25 });
getUserInfo<{ name: string; age: number; hobby: string }>({
name: "花子",
age: 22,
hobby: "読書"
});
名前: 太郎, 年齢: 25
名前: 花子, 年齢: 22
このように、デフォルト型と型制約を併用すると、型の安全性と柔軟性を両立できます。
6. デフォルト型パラメータを使ってコードをシンプルに
TypeScriptのデフォルト型パラメータは、ジェネリクスをより便利に使うための機能です。型を省略できることで、コードの見通しが良くなり、初心者でも扱いやすい設計が可能になります。
- 型を指定しない場合に使われる「初期設定の型」を指定できる
- 関数・クラスの両方で利用可能
- 制約(extends)と組み合わせるとより安全な設計ができる
ジェネリクスに慣れてくると、TypeScriptの型システムの強力さが実感できるでしょう。最初は難しそうに感じても、ひとつずつ理解すれば確実にマスターできます。
まとめ
デフォルト型パラメータがもたらす型の柔軟性と表現力
この記事を通して、TypeScriptにおけるデフォルト型パラメータの仕組みと、その使いどころについて丁寧に確認してきました。ジェネリクスは「型を後から決められる」という大きな特徴を持っていますが、そこにデフォルト型を設定することで、型推論の恩恵をそのまま受けつつ、必要な場面だけで明示的に型を指定するという柔軟な使い方が可能になります。これにより、コード全体がすっきり整理され、読みやすく、拡張性の高いプログラム設計がしやすくなります。
特に、<T = string> のようにデフォルト型をつけることで、「型を指定するほどではないが型安全は保ちたい」という場面で非常に役立つことがわかりました。何も指定しないと any 型になってしまうケースでも、デフォルト型パラメータをうまく設定しておくことで、無意識のミスを減らし、堅牢なコードを維持できます。この設計は、規模が大きくなるアプリケーションや、複数人で開発するケースで特に効果を発揮します。
また、関数だけではなくクラスにもデフォルト型を持たせることで、意図しない型の混入や、型定義そのものの重複を避けることができました。たとえば、Box クラスの例では、型を省略すると string が使われる仕組みになっており、ちょっとした文字列を扱う用途であれば、その記述を省略しながらスッキリとしたコードが書けました。一方で、数値やオブジェクトなど別の型を扱いたいときも、<number> と明示するだけで簡単に切り替えられる点が魅力でした。
さらに、デフォルト型パラメータと型制約(extends)を組み合わせた例では、最低限守るべき構造を決めながら、デフォルトで安全な型を提供するという、実用性の高い書き方を学びました。型制約は、その型が持つべき特徴を定義するもので、デフォルト型と組み合わせることでより賢い型設計が可能になります。これは API のレスポンス型など、現実的な開発場面で特に威力を発揮します。
また、TypeScript の理解を深めるうえで重要なのは、「デフォルト型パラメータ=初心者向けの簡易機能」ではなく、「型設計を効率化する高度な手法のひとつ」であるという点です。コードの読みやすさや安全性、保守のしやすさに関わる大切なデザインパターンとして、積極的に使っていく価値があります。
応用しやすいデフォルト型パラメータのサンプルコード
記事で学んだ内容を踏まえて、より実践的なサンプルコードを用意しました。ジェネリクスにデフォルト型を適用しつつ、柔軟に型指定を切り替えられる例です。
interface ResponseData {
status: number;
message: string;
}
class ApiResult<T = ResponseData> {
constructor(private data: T) {}
get formatted(): string {
if ("status" in this.data && "message" in this.data) {
return `結果: ${this.data.status} - ${this.data.message}`;
}
return JSON.stringify(this.data);
}
get raw(): T {
return this.data;
}
}
const defaultResult = new ApiResult({ status: 200, message: "成功" });
console.log(defaultResult.formatted);
const userResult = new ApiResult<{ name: string; age: number }>({
name: "太郎",
age: 20
});
console.log(userResult.raw);
この例では、ApiResultクラスのデフォルト型パラメータとして ResponseData を設定しています。そのため、型を明示しない場合は自動的に ResponseData を使い、より複雑な構造を扱いたいときには型引数で自由に切り替えられるようになっています。これにより、初心者にも扱いやすく、かつ実務にも耐えられる柔軟な設計を実現できます。
生徒
「先生、デフォルト型パラメータって思ったより使いやすくてびっくりしました!型を指定しなくても動くし、必要なときだけ型を変えられるのが便利ですね。」
先生
「そうですね。ジェネリクスの柔軟性を保ちつつ、簡単に使えるように工夫された仕組みなんですよ。特にライブラリ開発ではよく使われています。」
生徒
「制約と組み合わせた例も面白かったです。必要なプロパティだけを必ず持たせながら、デフォルト型で迷わず使える状態にできるなんて、実務で役に立ちそうです。」
先生
「それは良い視点ですね。型の安全性と使いやすさのバランスを取るために、デフォルト型パラメータは非常に重要な役割を持っています。」
生徒
「今回の学びを活かして、これからはジェネリクスを使うときにデフォルト型も意識して設計してみます!」
先生
「その意欲が一番大事ですよ。実際に使いながら覚えることで、より深く TypeScript の型システムを理解できるようになります。」