69
68

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

まだTypeScriptのTypeGuard関数を手動で書いてるんですか!?

Last updated at Posted at 2021-01-22

結論

io-tsを使うと、TypeGuard関数1を手で書かなくて済む。

import * as T from 'io-ts'
`interface {}`の定義と自動導出されたUser-Defined Type Guardの使用
// interface
const objectX = T.type({
  x: T.number,
  y: T.string,
})

type ObjectX = T.TypeOf<typeof objectX>

/* Same as

interface ObjectX {
  x: number
  y: string
}

*/

const a: unknown = {
  x: 42,
  y: 'inu',
}

// ---------------------------------
// User-Defined Type Guardは自動的に導出される
// ---------------------------------
const isObjectX: (w: unknown) => w is ObjectX = objectX.is

if (isObjectX(a)) {
  console.log('a is an ObjectX.')
  console.log(a.x)
  console.log()
}

// a is an ObjectX.
// 42
//
和型`foo | bar`
// union
const unionX = T.union([
  T.number,
  T.string,
])

type UnionX = T.TypeOf<typeof unionX>

const b: unknown = 'neko'

if (unionX.is(b)) {
  console.log('b is a UnionX.')
  console.log()
}

// b is a UnionX.
//
積型`foo & bar`の定義とpartialプロパティ`nya?: Mew`
// partialを持つinterface
const objectY = T.intersection([
  T.type({x: T.number}),
  T.partial({y: T.string}),
])

type ObjectY = T.TypeOf<typeof objectY>

const c: unknown = { x: 42 }

if (objectY.is(c)) {
  console.log('c is an ObjectY.')
  console.log(c.x)
  console.log()
}

// c is an ObjectY.
// 42
//
おまけ: partialプロパティを定義するなら、`T.partial()`を使った方がいい

下記の用法の代わりに、上記の「積型foo & barの定義とpartialプロパティnya?: Mew」で使用した方法を使わないと、y: undefinedを明示的に書く必要がでてしまう。

:point_down: :x:

const objectYNotBetter = T.type({
  x: T.number,
  y: T.union([T.string, T.undefined]),
})

type ObjectYNotBetter = T.TypeOf<typeof objectYNotBetter>

if (!objectYNotBetter.is(c)) {
  console.log('c is not an ObjectYNotBetter, because c.y is omitted.')
}

const d: unknown = {
  x: 42,
  y: undefined,
}

if (objectYNotBetter.is(d)) {
  console.log('d is an ObjectYNotBetter.')
  console.log('We must define .y with undefined for kinds of objectYNotBetter.')
  console.log(d.y)
  console.log()
}

// c is not an ObjectYNotBetter, because c.y is omitted.
// d is an ObjectYNotBetter.
// We must define .y with undefined for kinds of objectYNotBetter.
// undefined
//

if (objectY.is(d)) {
  console.log('But, no way, d is an ObjectY.')
  console.log(d.y)
  console.log()
}

// But, no way, d is an ObjectY.
// undefined
//

概要

僕はもう疲れてしまいました。
あらわれるいくつもの型!! 無限にUser-Defined Type Guardを書かなければいけない!!!
オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラァ!!!

interface InuNekoMaid {
  maid: boolean

  wan: Bow
  nya?: Mew

  // ...
}

// User-Defined Type Guard
function isInuNecoMaid(x: any): x is InuNekoMaid {
  return (
    typeof x.maid === 'boolean' &&
    isBow(x.wan) &&
    (x.nya === undefined || isMew(nya))

    // && ...
  )
}

// これを定義する作業が型の数だけ続く。

型の種類は可算無限個あると、古事記にも書かれています。
つまり最悪な場合、我々は限りなくUser-Defined Type Guardを書かなければいけない :thinking:

それはいけませんね。
型に対するマナーがなっていません!
ありえない話!!!!🤬😡🤬🤬😡🤬

io-tsを使いましょう。

io-tsとは

io-tsとはType<A, O, I>という、**コーデック(codec)**と呼ばれるものを提供する、TypeScriptのライブラリです。

Type<A, O, I>は以下の形で、O, A, I型の間の関数をまとめたクラスです。

           decode関数
       I  ----------->  (A | Errors)

           encode関数
       A  ---------->  O
       |
is関数 |
       v
    (x is A)

そしてそのType<A, O, I>は、関数T.type()と、型関数T.TypeOf<>を用いて、自動的に生成されます。

interfaceX | YX & Yなどのような型への、decodeencodeis関数が自動で生成される、ということです。

ここで本稿の結論が出ました。
そう、is関数です!

// interface
const objectX = T.type({
  x: T.number,
  y: T.string,
})

type ObjectX = T.TypeOf<typeof objectX>

/* Same as

interface ObjectX {
  x: number
  y: string
}

*/

// これ!!
const isObjectX: (w: unknown) => w is ObjectX = objectX.is

まとめ

あとは結論のままに、各型を構築するだけです。

簡単!
最高!
やったー!!

おまけ: partialプロパティを定義するなら、T.partial()を使った方がいい

io-tsで

interface ObjectY {
  x: number
  y?: string  // partialプロパティ
}

を定義したいときは、以下のようにする :o:

const objectY = T.intersection([
  T.type({x: T.number}),
  T.partial({y: T.string}),
])

type ObjectY = T.TypeOf<typeof objectY>

以下のようにしない :x:

const objectYNotBetter = T.type({
  x: T.number,
  y: T.union([T.string, T.undefined]),
})

type ObjectYNotBetter = T.TypeOf<typeof objectYNotBetter>

前者は次のような値を表すが、

// Compile OK

const y1: ObjectY = {
  x: 10,
}

const y2: ObjectY = {
  x: 10,
  y: 20,
}

const y3: ObjectY = {
  x: 10,
  y: undefined,
}

後者は次のような値を表さない
.yを省略できない。)

// Compile error
const wrong: ObjectYNotBetter = { x: 10 }

おわり

まだTypeScriptのTypeGuard関数を手動で書いてるんですか!?

マナー違反ですよ!!!
むせかえるような怠惰のにおい!!!
ありえない話!!!!🤬🤬😡🤬😡🤬

TODO

  • Generics型をio-tsで書く場合の解説
  1. ここでの「TypeGuard関数」とはUser-Defined Type Guardのことを指します。以下、User-Defined Type Guardと呼称。

69
68
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
68

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?