Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@k-penguin-sato

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

概要

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

参照

3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
k-penguin-sato
最近はGoとTypeScriptを頑張ってます。 https://dev.to/ksato1995

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?