TypeScriptの学習を進めていると、初学者の多くが「おや?」と首を傾げる不思議な文法に出会います。それが今回お話しする「Declaration Merging(宣言の結合)」、なかでも「関数と名前空間のドッキング(結合)」です。
「関数は関数、オブジェクトはオブジェクトでしょ? なんで一緒にするの?」
そう思いますよね。大丈夫ですよ、最初は誰だって頭がこんがらがります。
でも、このテクニック、実は実務の現場では「おお、これ考えた人天才だな!」と膝を打つようなクールなデザインパターンなんです。今回は、身近な例えを交えながら、この魔法のような仕組みを一緒に解き明かしていきましょう!
—
1. 身近な例え:多機能な「レトロカメラ」を思い浮かべてみて
突然ですが、みなさんは「写ルンです」のような使い捨てカメラや、ちょっとレトロなインスタントカメラを触ったことはありますか?
あれって、基本の機能は「シャッターを押して写真を撮る」というただ一つのシンプルな動作(=関数)ですよね。
でも、カメラのボディ(ガワ)をよく見ると、
- 「今のフィルムの残り枚数」が窓から見える
- 「フラッシュの充電状態」を示すランプがついている
- 「特殊なレンズフィルター」をカチャッと装着できる
みたいに、「写真を撮るという単体の道具」でありながら、周りに便利なオマケ(プロパティや機能)がたくさんくっついている状態になっています。
TypeScriptの「関数と名前空間の結合」は、まさにこれと同じです。
「基本は一発の関数としてパッと使えるけれど、その裏側にちょっとした設定や便利機能(静的メソッド)をポケットいっぱいに詰め込んでいる」という、欲張りでスマートな表現方法なんです。
—
2. まずはコードを見てみよう!「お買い物計算」の例
百聞は一見に如かず。実際にコードを見てみましょう。
例えば、「商品の税込み価格を計算する関数」を作りたいとします。でも、ついでに「現在の消費税率をどこからでも確認・変更できる機能」もこの関数にくっつけておきたい、そんなシチュエーションを想像してください。
// ① まずは普通の「関数」を定義します
function calculateTax(price: number): number {
// 名前空間の中に定義した税率(taxRate)を使って計算するよ
return Math.floor(price (1 + calculateTax.taxRate));
}
// ② 次に、関数名と「全く同じ名前」で名前空間(namespace)を定義します
namespace calculateTax {
// この関数専用の「おまけ(プロパティ)」をここに持たせられます
export let taxRate: number = 0.10; // 初期値は10%
// ついでに、税率を一発で変更する便利な関数も入れちゃおう
export function setRate(newRate: number): void {
taxRate = newRate;
console.log(`消費税率が ${newRate 100}% に変更されました!`);
}
}
たったこれだけです!
えっ、「関数」と「namespace」が同じ名前で共存している……? 魔法のようですが、これがTypeScriptが誇る「宣言の結合」のパワーです。
—
3. どうやって使うの? 実際に動かしてみよう
じゃあ、さっき作ったこの「二刀流の関数」を実際に使ってみましょう。エディタを叩くワクワク感を味わってくださいね。
// 1. 普通に関数として使う(お買い物のお会計!)
const penPrice = 150;
console.log(`ペン(税込み): ${calculateTax(penPrice)}円`);
// 出力: ペン(税込み): 165円 (10%で計算されるよ)
// 2. なんだって? 軽減税率が導入された?
// そんな時は、くっついた名前空間のメソッド経由で税率を変更!
calculateTax.setRate(0.08);
// 出力: 消費税率が 8% に変更されました!
// 3. もう一度同じ関数で計算してみる
const notebookPrice = 300;
console.log(`ノート(税込み): ${calculateTax(notebookPrice)}円`);
// 出力: ノート(税込み): 324円 (ちゃんと新しい8%で計算されている!)
// 4. 今の税率を直接覗き見することもできちゃいます
console.log(`現在の設定税率は ${calculateTax.taxRate 100}% です`);
// 出力: 現在の設定税率は 8% です
すごくないですか?
`calculateTax` という一つの名前でありながら、
- `calculateTax(150)` と書けば「関数」として働き、
- `calculateTax.taxRate` や `calculateTax.setRate()` と書けば「オブジェクト(名前空間)」として働く。
これが、関数と名前空間の結合がもたらす開発体験の気持ちよさです。
—
4. どんなときに使うの? 実務でのリアルなユースケース
「面白い仕組みなのは分かったけど、いつ使うの?」って思いますよね。
実はこれ、身の回りの便利なライブラリでもよく使われています。
1. 状態を持つロジック関数
今回の例のように、内部に「設定値(キャッシュやデフォルト値)」を持ちつつ、メインの処理は関数としてシンプルに呼び出したいとき。
2. ユーティリティ関数とサブ機能のセット
例えば、あるバリデーション(入力チェック)関数を用意しつつ、その名前空間の中に「よくある正規表現パターン」などをまとめてぶら下げておきたいとき。
無理やり使う必要はありません。「あ、ここは関数だけど、ちょっとした定数や補助関数も一緒にセットで持ち歩きたいな」と感じたときに、この引き出しをスッと取り出せれば、あなたも立派なTypeScript使いです!
—
5. つまずきやすいポイント&そっとアドバイス
初学者のうちによくあるのが、「順番の罠」です。
TypeScriptでは、関数と名前空間を結合するとき、書き順に少しだけお作法があります。基本的には、先に「関数」を書き、そのあとに「同名の名前空間」を定義するのが安全です(逆だとエラーになることがあります)。
もしエディタで赤波線(エラー)が出たら、
- スペルミスはないか?
- `export` をつけ忘れて名前空間の中身を外から触れなくしていないか?
この2つを優しく見直してあげてくださいね。
最初は難しく感じるかもしれませんが、実際に手を動かして「おっ、関数なのにプロが生えてる!」と感動できれば、もうあなたのものです。
焦らず、自分のペースでTypeScriptの世界を楽しんでいきましょう!

コメント