🚀
一日一処: TypeScriptで値の型を判定するための処理
値の型
よく関数の返却で、any
の配列などを返却しないといけない場合もある。このとき、受け取り側は、適切な型として取り扱いたいが、内容を確認せずas
を用いて、型のキャスティングを行い、知らぬ存ぜぬで、プログラムを書く。非常に不適切な行いだ。
function getData(): any[] {
return [1, 'bob', 'japan']
}
type User = [id: number, name: string, country: string]
const data = getData() as User
data.forEach((v) => console.log(v))
// 1
// 'bob'
// 'Japan'
型を判定
少々処理が大変だが、簡単な話、判定を行う関数の返却値を少しだけ変えてあげることが大切だ。まずは、通常の判定を行う関数を作ってみる。
function getData(): any[] {
return [1, 'bob', 'japan']
}
function validate(data: any[]): boolean {
return typeof data[0] === 'number' &&
typeof data[1] === 'string' &&
typeof data[2] === 'string'
}
type User = [id: number, name: string, country: string]
const data = getData()
if (validate(data)) {
(data as User).forEach((v) => console.log(v))
// 1
// 'bob'
// 'Japan'
}
この様に、検証用の関数を追加し、適切であることを確認したあとに、真偽値によって、結果を返せば、文句なく、キャストすることができるだろう。それでも、as
を用いてることがあまり好ましくないように見える。
よって、最終的に、掲題の実現で、この様に書く。
function getData(): any[] {
return [1, 'bob', 'japan']
}
function validate<T extends any[]>(data: any[]): data is T {
return typeof data[0] === 'number' &&
typeof data[1] === 'string' &&
typeof data[2] === 'string'
}
type User = [id: number, name: string, country: string]
const data = getData()
if (validate<User>(data)) {
data.forEach((v) => console.log(v))
}
このように検証用の関数の返却値をdata is T
(type predicate)とすることで、検証後のdata
は自動的に型がUser
となる。これは、forEach
を使った例だが、オブジェクトを同様に用いた場合、キー名を適切に用いることができる(エディターによるサジェストもされる)。
これを更に、汎用的にしてみると、このような形になるだろうか。人によって、こだわりはあると思うが、キャストを行わずに、適切な検証を行ってほしい。
function getData(): any[] {
return [1, 'bob', 'japan']
}
function validate<T extends any[]>(data: any[], rule: string[]): data is T {
return data.length === rule.length
? data.every((_, i) => typeof data[i] === rule[i])
: false
}
type User = [id: number, name: string, country: string]
const rule = ['number', 'string', 'string']
const data = getData()
if (validate<User>(data, rule)) {
data.forEach((v) => console.log(v))
}
Discussion