Typescript プリミティブな値のストリングリテラル型を作ってみる

17410 ワード

Typescript プリミティブな値のストリングリテラル型を作ってみる

背景

TypeScript では、プリミティブな値の型として string number boolean などがあり、
"hoge" 3 false などを型で区別することができるが

では、これらが文字列の場合("hoge" "3" "false")は型システムを使って区別をつけることができるのかと疑問に思い、型が作れないか試してみることにした。

プリミティブな値とは

サバイバルTypescriptでも解説があるが、基本的にプリミティブ型とは以下の通り

  • boolean
  • number
  • string
  • undefined
  • null
  • symbol
  • bigint

他の型

その他、プリミティブではないが、TypeScript には以下の型もあるので、ついでに試してみる。

  • object
  • any
  • unknown
  • never

プリミティブな値の文字列型

Number

例えば数値の文字列型を定義するなら、以下のように書く

type NumberString = `${number}`

この型をいろんな文字列に当てはめてみると・・

const num1: NumberString = "123" // ok
const num2: NumberString = "-123.456" // ok
const num3: NumberString = "0x3d4f" // ok
const num4: NumberString = "0b1011" // ok
const num5: NumberString = "0o237" // ok
const num6: NumberString = "123e-1" // ok

const numx1: NumberString = "abc" // Type '"abc"' is not assignable to type '`${number}`'.(2322)
const numx2: NumberString = "NaN" // 同様のエラー
const numx3: NumberString = "Infinity" // 同上
const numx4: NumberString = "0x12fg" // 同上
const numx5: NumberString = "0b1234" // 同上

"NaN""Infinity" は予想外だったが、基本的に数値にパース可能な文字列全般を示す型として定義できた。

Boolean, null, undefined

Numberの場合と同様に書いてみると、 "true" | "false" のようなストリングリテラルに変換された

type BooleanString = `${boolean}` // "true" | "false"

ちなみに、同様に true false それぞれの型も同様の結果となった

type TrueString = `${true}` // "true"
type FalseString = `${false}` // "false"

null undefined についても同様の結果になった

type NullString = `${null}` // "null"
type UndefinedString = `${undefined}` // "undefined"

おそらく、これらは実際に値が文字列化したときの結果が型となってるのではないかと思われる

const trueStr = `${true}` // "true"
const nullStr = `${null}` // "null"
const undefinedStr = `${undefined}` // "undefined"

BigInt

同様の型を作ってみた

type BigIntString = `${bigint}`

いろいろ試してみると、以下のようになった

const bigint1: BigIntString = "123" // ok
const bigint2: BigIntString = "123.456" // error
const bigint3: BigIntString = "-123" // ok
const bigint4: BigIntString = "-123.456" // error
const bigint5: BigIntString = "0x3d4f" // ok
const bigint6: BigIntString = "0b1011" // ok
const bigint7: BigIntString = "0o237" // ok
const bigint8: BigIntString = "123e-1" // error
const bigint9: BigIntString = "456e+10" // error
const bigint10: BigIntString = "123n" // error

見たところ、 整数の文字列がほぼ通っているようだった。

しかし、 456e+10, 123n がエラーとなるのは腑に落ちない。 (TypeScript側の不具合?)

不思議な挙動はあれど、整数のみを受け付ける文字列型ができた。これは応用性ありそう

symbol, object

予想はしていたが、これらの型はstringでの表現が不可能であり、以下のように型を作っても、当然ながらエラーとなった

type SymbolString = `${symbol}` // Type 'symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'
type ObjectString = `${object}` // Type 'object' is not assignable to type 'string | number | bigint | boolean | null | undefined'

string にパース可能な primitive type は 上記のエラーの文言の通り決まっており、これ以外の型ではダメなようだ。

any, unknown, never

せっかくなので試してみた。

type AnyString = `${any}` // `${any}`
type UnknownString = `${unknown}` // Type 'unknown' is not assignable to type 'string | number | bigint | boolean | null | undefined'
type NeverString = `${never}` // never

any については `${any}` 型となった。が、これって実質 string だよね・・

unknown は string にパース不可能なためエラーとなった

never については、予想通り never 型になった。

まとめ

いろいろ試してみたが、BigIntString は予想外だった。

これらについては以下の応用が聞くかも

応用例

何らかのAPIやモジュールで、string number, number が不一致な状態を正すメソッドを作るとか

type NumberString = `${number}`
type NumberParsable = number | Number | NumberString

declare function parseNumber(likeNumber: NumberParsable): number;