【入門編】 Declaration Merging: 名前空間と関数の結合 – TypeScript実践ガイド

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の世界を楽しんでいきましょう!

コメント

タイトルとURLをコピーしました