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