Table of Contents
概要
TypeScript では2つの型が構造的に同じであれば、互換性があると見なされます。
例えば下記のname
とpassword
は互換性があると言えます。
type name = string
type password = string
つまり、ユーザーから見た場合意味的には全く異なる上記の 2 つの型も TypeScript は同じものとして扱います。
しかし、password
は 8 文字以上で大文字や小文字を含んでいなければならない等の条件を持った型を定義した場合どうすれば良いでしょうか?
上記のような例では TypeScript は型的な安全性を保証してはくれますが、name
とpassword
のような意味的に異なる情報の分別が出来ていません。
このようなケースでOpaque型
を用いる事で、より強力な型構成をもたらす事が可能です。
Opaque 型を使用しない例
まずは、Opaque 型を用いない例を確認します。
下記の例ではname
とpassword
も 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 }
上記のコードに関しては下記の記事を読んで頂けると理解がより深まるかと思います。
上記で定義したPassword型
は通常の型と同様の使い方が可能です。
type User = {
name: string
password: Password
}
こうしてあげる事で、TypeScript はpassword
にstring型
の値が渡されると、エラーを出すようになります。
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') }