詳細は https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates を読んでください。これで理解できればここから下を読む必要はないです。
TypeScript中に表われる ${引数名} is $型名
というシンタックスについての解説です
${引数名} is $型名
という構文は関数の戻り値の型定義で使うことができます
type Foo = string
type Bar = number
function isFoo(foo: Foo | Bar): foo is Foo {
return foo
}
戻り値の実装として期待される型は boolean
になりますが、普通にbooleanを戻り値として指定するのとは別の効力があります
例.
次のようなコードを
type User = TwitterUser | FacebookUser
type TwitterUser = {
provider: 'twitter',
id: string,
screenName: string
}
type FacebookUser = {
provider: 'facebook'
id: string,
firstName: string,
lastName: string
}
const groupByProvider = (users: ReadonlyArray<User>): [TwitterUser[], FacebookUser[]] => {
const twitterUsers: TwitterUser[] = []
const facebookUsers: FacebookUser[] = []
for(const user of users) {
if(user.provider === 'twitter') { twitterUsers.push(user) }
if(user.provider === 'facebook') { facebookUsers.push(user) }
}
return [twitterUsers, facebookUsers]
}
みたいなコードを次のようにリファクタリングするとしましょう
type User = TwitterUser | FacebookUser
type TwitterUser = {
provider: 'twitter',
id: string,
screenName: string
}
type FacebookUser = {
provider: 'facebook'
id: string,
firstName: string,
lastName: string
}
const isTwitterUser = (user: User): boolean => {
return user.provider === 'twitter'
}
const isFacebookUser = (user: User): boolean => {
return user.provider === 'facebook'
}
const groupByProvider = (users: ReadonlyArray<User>): [TwitterUser[], FacebookUser[]] => {
const twitterUsers: TwitterUser[] = []
const facebookUsers: FacebookUser[] = []
for(const user of users) {
if(isTwitterUser(user)) { twitterUsers.push(user) }
if(isFacebookUser(user)) { facebookUsers.push(user) }
}
return [twitterUsers, facebookUsers]
}
しかしこれはうまくいきません。何故ならコンパイラからは、isTwitterUser(user)
がただのbooleanを返す関数で内部でproviderをきっちりチェックしているかに関心がないためです。
これをなんとかするのが 引数名 is T
で これを次のように使うことによって
const isTwitterUser = (user: User): user is TwitterUser => {
return user.provider === 'twitter'
}
isTwitterUser(user)
の戻り値が true
ということが保証できるブロックのみuserの型をTwitterUserだとおもいこんで使えます。
先ほどの例では
const isTwitterUser = (user: User): user is TwitterUser => {
return user.provider === 'twitter'
}
だったコードの内部実装が変更されて
const isTwitterUser = (user: User): user is TwitterUser => {
return true
}
みたいな実装になったとしても
const twitterUser: TwitterUser[] = []
if(isTwitterUser(user)) {
twitterUsers.push(user)
}
↑のようなコードはコンパイル通っちゃって、型チェックを踏み倒すことに成功するので厳密には型検査が行われてないよ!っていう話です