📛

令和5年に知っているべきTypeScriptのnamespaceの知識

2023/05/05に公開3

TypeScriptにはnamespaceという構文が存在します。この構文はTypeScript初期からある独自構文の一つですが、現在では特殊な用途以外では使う理由が無いため、よく知らないという方も多いでしょう。

実際、一部のレアケースを除いてnamespaceを使う必要はありませんが、それでも知識としてあったほうが良いことが多少あります。この記事ではこの部分を解説します。

型に.でアクセスできるやつ

TypeScriptを使っていると.を使って型にアクセスする機会があるでしょう。例えばReact.FCなどです。

export const Page: React.FC = () => // ...

実は、親.型名のように.を使って型にアクセスできるのは、namespaceの機能です。上のコードでのReactは単なる型や単なる変数ではなくnamespaceなのです。

試しに、Foo.BarstringとなるようにFooを定義してみてください。これができる方法は2つしかありません。そのうちの1つがnamespaceです。次のようにFooを定義すればできます。

namespace Foo {
  export type Bar = string;
}

このように関連する型をまとめて提供したい場合はnamespaceを使うことができます。この構文の中では、まるでモジュールのようにexportを使うことができます(実際、namespaceは昔はmoduleという構文でした)。

namespaceを発生させるもう一つの方法

もう一つ、namespace構文を使わずにFoo.Barを定義する方法があります。それはimport *構文を使用することです。

Foo.ts
export type Bar = string;
import type * as Foo from "./Foo";

// これでFoo.Barがstringとなる

ECMAScript仕様ではimport *構文によって作られる変数(上の例のFoo)をモジュール名前空間オブジェクト (module namespace object) と呼びます。この変数はTypeScriptの型システム上ではnamespaceとして扱われるので、Foo.Barのように.で中の型にアクセスできます。

こちらを前提に置くと、namespaceというのは1つのファイルの中でネストしたモジュールを定義するものに見えてきます。実際、現在ECMAScriptにもネストしたモジュール定義を導入したいという動きもあるので、TypeScriptは期せずして10年くらい時代を先取りしていたことになりますね。

まとめ

TypeScriptでnamespaceを発生させる方法は、明示的にnamespace構文を使う方法と、import *構文を使う方法があります。

namespaceは、Foo.Barのように.でアクセスできる型を提供する場合に有用です。ただ、アプリケーションコードであればimport *で十分なのでnamespace構文を使う機会はないでしょう。ライブラリの型定義を書く場合にもしかしたらnamespaceを使いたくなるかもしれません。

他の用途では使わなくていいです。

GitHubで編集を提案

Discussion

nap5nap5

これ便利ですね。紹介ありがとうございます。
定数定義のハンドリングでも便利かなと思い、少しデモ作ってみました。

https://codesandbox.io/p/sandbox/vigorous-forest-7psv7z?file=%2Fsrc%2Ffeatures%2Fcomment%2Ftypes%2Fconst.ts

定義側

export namespace AppConst {
  export namespace PLAN_CATEGORY {
    export const ENTERPRISE = 'enterprise'
    export const TEAM = 'team'
    export const getList = () => {
      return [AppConst.PLAN_CATEGORY.ENTERPRISE, AppConst.PLAN_CATEGORY.TEAM]
    }
  }
  export namespace ACCOUNT_CATEGORY {
    export const OWNER = 'owner'
    export const MEMBER = 'member'
    export const getList = () => {
      return [AppConst.ACCOUNT_CATEGORY.OWNER, AppConst.ACCOUNT_CATEGORY.MEMBER]
    }
  }
  export const DEFALUT_TAX_RATE = 10
  export const IS_DEBUG = true
}

使用側

import { AppConst } from '@/features/comment/types/const'

const Demo = () => {
  console.log(AppConst.ACCOUNT_CATEGORY.getList())
  console.log(AppConst.PLAN_CATEGORY.getList())
  console.log(AppConst.DEFALUT_TAX_RATE)
  console.log(AppConst.IS_DEBUG)
  return (
    <main className='mx-auto mt-24 w-full max-w-[20rem]'>
      <section className='p-4 shadow-bebop'>
        <div>
          <h2>AppConst.ACCOUNT_CATEGORY</h2>
          <pre>
            <code>
              {JSON.stringify(AppConst.ACCOUNT_CATEGORY.getList(), null, 2)}
            </code>
          </pre>
        </div>
        <hr />
        <div>
          <h2>AppConst.PLAN_CATEGORY</h2>
          <pre>
            <code>
              {JSON.stringify(AppConst.PLAN_CATEGORY.getList(), null, 2)}
            </code>
          </pre>
        </div>
        <hr />
        <div>
          <h2>AppConst.DEFALUT_TAX_RATE</h2>
          <pre>
            <code>{JSON.stringify(AppConst.DEFALUT_TAX_RATE, null, 2)}</code>
          </pre>
        </div>
        <hr />
        <div>
          <h2>AppConst.IS_DEBUG</h2>
          <pre>
            <code>{JSON.stringify(AppConst.IS_DEBUG, null, 2)}</code>
          </pre>
        </div>
      </section>
    </main>
  )
}

export default Demo

簡単ですが、以上です。

uhyouhyo

ありがとうございます。しかし、定数を定義する目的でnamespaceを使うのは自分としてはあまり推奨しません。🥲
理由は、わざわざnamespaceという独自構文を使わなくても、普通のオブジェクトを使えばできるからです。記事中で「マジで使わないので今回は紹介しません」と書いたものです。
Foo.Barのようにアクセスできる型をTypeScriptで実現するためにnamespaceの概念が必要になるため、そこだけこの記事で紹介しました。

nap5nap5

なるほどです。namespaceの使い分けの基準が得られました。
お返事ありがとうございました。