15
6

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 3 years have passed since last update.

【TypeScript】Opaque型を用いたより強力な型構成

Posted at

概要

TypeScriptでは2つの型が構造的に同じであれば、互換性があると見なされます。
例えば下記のnamepasswordは互換性があると言えます。

type name = string
type password = string

つまり、ユーザーから見た場合意味的には全く異なる上記の2つの型もTypeScriptは同じものとして扱います。
しかし、passwordは8文字以上で大文字や小文字を含んでいなければならない等の条件を持った型を定義した場合どうすれば良いでしょうか?

上記のような例ではTypeScriptは型的な安全性を保証してはくれますが、namepasswordのような意味的に異なる情報の分別が出来ていません。
このようなケースでOpaque型を用いる事で、より強力な型構成をもたらす事が可能です。

Opaque型を使用しない例

まずは、Opaque型を用いない例を確認します。
下記の例ではnamepasswordもstring型として扱われています。

type User = {
  name: string
  password: string
}

const user: User = { name: "user1", password: "1234" }
console.log(user) // => { name: 'user1', password: '1234' }

Opaque型を使用した例

passwordは8文字以上の文字列である事を保証する型Password を定義します。
TypeScriptはデフォルトではOpaque型をサポートしていないので、intersection型を使用して、下記のようにPassword型は独自の型である事を明示的に定義していきます。

declare const validPassword: unique symbol;
type Password = string & { validPassword: never }

上記のコードに関しては下記の記事を読んで頂けると理解がより深まるかと思います。

公称型をTypeScriptで実現するための基礎

上記で定義したPassword型は通常の型と同様の使い方が可能です。

type User = {
  name: string
  password: Password
}

こうしてあげる事で、TypeScriptはpasswordstring型の値が渡されると、エラーを出すようになります。

const user1: User = { name: "test", password: "1234" }  // Error: Type 'string' is not assignable to type 'Password'.

次に、passwordが8文字以上である事を保証したいので、与えられた文字列が8文字以上であった場合、値をPassword型として返すvalidatePasswordを定義します。

const validatePassword = (input: string) => {
  if (input.length < 8) {
    throw new Error("パスワードは8文字以上で入力してください。")
  }
  return input as Password
}

User型のオブジェクトのpasswordにはPassword型の値を与えたいので、validatePasswordを使用して、下記のようにオブジェクトを定義する事が可能です。

パスワードが8文字以下の場合はしっかりとエラーを発生させてくれるようにもなりました。

const user1: User = { name: "user1", password: validatePassword("1234") }
// => Error: パスワードは8文字以上で入力してください。
const user2: User = { name: "user1", password: validatePassword("12345678") }
// => { name: "user1", password: "12345678" }

ここまでのコードをまとめると下記のようになります。

declare const validPassword: unique symbol;

type Password = string & { validPassword: never }

type User = {
  name: string
  password: Password
}

const validatePassword = (input: string) => {
  if (input.length < 8) {
    throw new Error("パスワードは8文字以上で入力してください。")
  }

  return input as Password
}

const user1: User = { name: "user1", password: validatePassword("12345678") }
const user2: User = { name: "user1", password: validatePassword("1234") }

参照

15
6
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
15
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?