paint-brush
TypeScript における判別共用体の威力@tokenvolt
242 測定値

TypeScript における判別共用体の威力

Oleksandr Khrustalov4m2024/10/05
Read on Terminal Reader

長すぎる; 読むには

この記事では、TypeScrips の判別共用体の基本について概説します。私は開発で判別共用体を頻繁に使用するので、その適用方法について特定の例を見ていきます。
featured image - TypeScript における判別共用体の威力
Oleksandr Khrustalov HackerNoon profile picture

問題を述べる

Typescript は最近成長し、人気が高まっているため、ますます多くの JavaScript 開発者が型の安全性を評価しています。Typescript が提供する機能のリストは膨大で、圧倒されるかもしれません。そこで、この記事では、理解しやすく、実用的な効果のある機能の 1 つに焦点を当てます。


例から始めましょう。多くのユーザー ロールを持つアプリケーションを開発していると想像してください。アプリケーションがさまざまなユーザーによって使用されるのはよくあることですよね。ここでは正確なロールはそれほど重要ではありませんが、 adminconsumerguestであるとします。Typescript では、次のようにしてこれらのロールを持つユーザーを宣言できます。


 type Admin = {} type Consumer = {} type Guest = {}


ここで、各ユーザー ロールが持つ属性のセットについて考えてみましょう。通常、これらはemailfirstNamelastNameなどです。しかし、待ってください。 Guestユーザーにはおそらくこれらがありません (結局はゲストです)。そのため、今のところこのタイプは空のままにしておきましょう。


 type Admin = { firstName: string lastName: string email: string } type Consumer = { firstName: string lastName: string email: string } type Guest = {}


アプリケーションのユーザーは 1 つのロールのみを持つことができます。これを型で表現する方法は、 union型を使用することです。


 type User = Admin | Consumer | Guest


管理者は独自の能力を持つことで有名ですが、私たちのアプリケーションでは、管理者は消費者を招待することができます。管理者が送信できる招待状の数を示すフィールドを追加しましょう。


 type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number // <-- added }


もっと面白くして実際のアプリケーションに近づけるために、 Consumerタイプ専用のプロパティを追加してみましょう。


 type Consumer = { firstName: string lastName: string email: string premium: boolean // <-- added }


これは非常に単純な例ですが、実際には、ユーザーが数十の異なるプロパティを持つ可能性があり、特定のプロパティにアクセスする必要がある場合、コードベースがかなり複雑になります。


 const doSomethingBasedOnRole = (user: User) => { // how do you check here that user is really an admin if (user) { // ...and do something with the `numberOfInvitesLeft` property? } }


1 つの選択肢は、プロパティの存在を確認することです。


 const doSomethingBasedOnRole = (user: User) => { if (user && user.numberOfInvitesLeft) { // safely access `numberOfInvitesLeft` property } }


しかし、これは面倒でスケーラブルなソリューションではありません。`numberOfInvitesLeft` がオプションのプロパティになった場合はどうすればよいでしょうか?

識別されたユニオンタイプの導入

ここで、判別されたユニオン タイプが役立ちます。ロールを示す追加のフィールドをすべてのユーザー タイプに配置する必要があります。


 type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number role: "admin" // <-- added } type Consumer = { firstName: string lastName: string email: string role: "consumer" // <-- added } type Guest = { role: "guest" // <-- added }


特定の文字列を型として指定している点に注意してください。これは文字列リテラル型と呼ばれます。これにより、ネイティブ JS 言語演算子 ( switch caseifelseを使用して役割を区別できるようになります。


 const user: Admin = { firstName: "John", lastName: "Smith", email: "[email protected]", numberOfInvitesLeft: 3, role: "admin", } const doSomethingBasedOnRole = (user: User) => { if (user.role === "admin") { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } }


同じことが switch case ステートメントにも当てはまります。


 // ... const doSomethingBasedOnRole = (user: User) => { switch (user.role) { case "admin": { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } case "consumer": { // do something with a `Consumer` user // if you try to call `user.numberOfInvitesLeft` here, TS compiler errors in // // "Property 'numberOfInvitesLeft' does not exist on type 'Consumer'." // } } }


判別共用体型の利点は、型チェックが特定のユーザーに関連しているかどうかわからないアドホック プロパティではなく、明示的なロール プロパティに基づいているため明らかです。