Skip to content

Instantly share code, notes, and snippets.

@himanoa

himanoa/unch1.md Secret

Last active September 16, 2021 01:06
Show Gist options
  • Save himanoa/4196ef714152151844c4e20573fa34a2 to your computer and use it in GitHub Desktop.
Save himanoa/4196ef714152151844c4e20573fa34a2 to your computer and use it in GitHub Desktop.

TypeScriptの is について

詳細は 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
}

戻り値の型定義に ${引数名} is $型名 を書いた場合戻り値の型は何になるの?

戻り値の実装として期待される型は boolean になりますが、普通にbooleanを戻り値として指定するのとは別の効力があります

${引数名} is $型名 の効力

例.

次のようなコードを

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だとおもいこんで使えます。

x is T形式の宣言は戻り値の型検査が行われない とはどういうことさね?

先ほどの例では

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)
}

↑のようなコードはコンパイル通っちゃって、型チェックを踏み倒すことに成功するので厳密には型検査が行われてないよ!っていう話です

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment