# 型の拡大
例
let num = 5 // number型
let greet = 'Hello' // string型 TypeScriptでは、letで宣言されたものには、再代入される可能性があることを想定し、汎用的な型に拡大して推論する。
const での変数宣言と型推論
const PI = 3.14 // リテラル型(3.14) constで宣言されたPIは具体的なリテラル3.14として型付けされ、その型はnumber型に格納されない。
以下のように、変数PIをletで宣言された変数numに代入したとしても、型が拡大されるので注意。
let で宣言された変数にリテラル型の変数を代入
const PI = 3.14 // リテラル型(3.14)
let num = PI // number型 上記では、変数numに3.14型のPIを代入しているが、このPIをletで宣言されたnumに代入すると、numはnumber型に拡大された推論になる。この拡大は、型注釈を使って明確な型を指定することで防ぐことができる。
型注釈による型の拡大の防止
const PI: 3.14 = 3.14 // リテラル型(3.14)
let num = PI // リテラル型(3.14)。 型が拡大されない。配列の型の拡大
const fruits = ['apple', 'banana', 'cherry'] // string[]型
const primitives = [1, 'hello', true] // (number | string | boolean)[]型 上記の例では、変数fruitsには文字列の配列が割り当てられていて、TypeScriptはこれを具体的なリテラル型のTuple型ではなくstring型の配列として型を拡大して推論する。一方で、変数primitivesのように配列の要素に異なる型が混在する場合は、TypeScriptはすべての型と互換性のある型を推論する。この場合、すべての型をメンバーに持つユニオン型となる。
any 型への拡大
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型と推論される。これにより、これらの変数にはどんな型の値も代入できるようになる。また、空の配列で初期化されたlistもany[]型となり、数値や文字列を含む任意の型の要素を配列に追加することが可能。TypeScriptはこれらの変数を極めて柔軟に扱うことを許容しているため、これらの変数に対しては型安全性がが失われる。
これらの変数は、変数が宣言されたスコープから出ると型推論によって型が決まる。
any 型と変数のスコープ
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内で初期化されずに宣言された変数xはany型に拡大されるが、関数の戻り値として関数スコープを離れるとstring型になる。これは、TypeScriptによって関数fn1の戻り値の型がstring型と型推論された結果。
関数fn2では空の配列listにany[]型が推論されるが、関数の外でこの配列を受け取ると、(string | number)[]型として扱われ、string型かnumber型の値のみを配列に追加できるようになる。