Skip to content

# 型の拡大

ts
let num = 5 // number型
let greet = 'Hello' // string型

TypeScriptでは、letで宣言されたものには、再代入される可能性があることを想定し、汎用的な型に拡大して推論する。

const での変数宣言と型推論

ts
const PI = 3.14 // リテラル型(3.14)

constで宣言されたPIは具体的なリテラル3.14として型付けされ、その型はnumber型に格納されない。

 以下のように、変数PIletで宣言された変数numに代入したとしても、型が拡大されるので注意。

let で宣言された変数にリテラル型の変数を代入

ts
const PI = 3.14 // リテラル型(3.14)

let num = PI // number型

 上記では、変数num3.14型のPIを代入しているが、このPIletで宣言されたnumに代入すると、numnumber型に拡大された推論になる。この拡大は、型注釈を使って明確な型を指定することで防ぐことができる。

型注釈による型の拡大の防止

ts
const PI: 3.14 = 3.14 // リテラル型(3.14)

let num = PI // リテラル型(3.14)。 型が拡大されない。

配列の型の拡大

ts
const fruits = ['apple', 'banana', 'cherry'] // string[]型

const primitives = [1, 'hello', true] // (number | string | boolean)[]型

 上記の例では、変数fruitsには文字列の配列が割り当てられていて、TypeScriptはこれを具体的なリテラル型のTuple型ではなくstring型の配列として型を拡大して推論する。一方で、変数primitivesのように配列の要素に異なる型が混在する場合は、TypeScriptはすべての型と互換性のある型を推論する。この場合、すべての型をメンバーに持つユニオン型となる。

any 型への拡大

ts
let x = null // any型
x = 123
x = 'abc'

// 暗黙的にundefinedで初期化
let y
y = 456
y = 'xyz'

// 空の配列で初期化
let list = [] // any[]型
list.push(1)
list.push('hello')

 上記の例では、nullで初期化された変数xと、値の指定なしに宣言(暗黙的にundefinedで初期化)された変数yは、TypeScriptによってany型と推論される。これにより、これらの変数にはどんな型の値も代入できるようになる。また、空の配列で初期化されたlistany[]型となり、数値や文字列を含む任意の型の要素を配列に追加することが可能。TypeScriptはこれらの変数を極めて柔軟に扱うことを許容しているため、これらの変数に対しては型安全性がが失われる。

 これらの変数は、変数が宣言されたスコープから出ると型推論によって型が決まる。

any 型と変数のスコープ

ts
function fn1() {
  let x // any型
  x = 123
  x = 'abc'
  return x
}

const x = fn1() // string型
// x = 1 // NG

function fn2() {
  let list = [] // any[]型
  list.push(1)
  list.push('hello')
  return list
}

const list = fn2() // (string | number)[]
// list.push(true) // NG

 上記の例では、関数fn1内で初期化されずに宣言された変数xany型に拡大されるが、関数の戻り値として関数スコープを離れるとstring型になる。これは、TypeScriptによって関数fn1の戻り値の型がstring型と型推論された結果。

 関数fn2では空の配列listany[]型が推論されるが、関数の外でこの配列を受け取ると、(string | number)[]型として扱われ、string型かnumber型の値のみを配列に追加できるようになる。