ホームページ  >  記事  >  ウェブフロントエンド  >  TypeScript をマスターする: extends の力を理解する

TypeScript をマスターする: extends の力を理解する

Barbara Streisand
Barbara Streisandオリジナル
2024-09-22 06:20:08618ブラウズ

Mastering TypeScript: Understanding the Power of extends

TypeScript の extends キーワードは、一種のスイス アーミー ナイフです。これは、継承、ジェネリック、条件型などの複数のコンテキストで使用されます。 extends を効果的に使用する方法を理解すると、より堅牢で再利用可能でタ​​イプセーフなコードを作成できます。

拡張を使用した継承

extends の主な用途の 1 つは継承であり、既存のものを基にして新しいインターフェイスやクラスを作成できます。

interface User {
  firstName: string;
  lastName: string;
  email: string;
}

interface StaffUser extends User {
  roles: string[];
  department: string;
}

const regularUser: User = {
  firstName: "John",
  lastName: "Doe",
  email: "john@example.com"
};

const staffMember: StaffUser = {
  firstName: "Jane",
  lastName: "Smith",
  email: "jane@company.com",
  roles: ["Manager", "Developer"],
  department: "Engineering"
};

この例では、StaffUser は User を拡張し、そのすべてのプロパティを継承し、新しいプロパティを追加します。これにより、より一般的な型に基づいて、より具体的な型を作成できるようになります。

クラスの継承

extends キーワードはクラスの継承にも使用されます:

class Animal {
  constructor(public name: string) {}

  makeSound(): void {
    console.log("Some generic animal sound");
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name);
  }

  makeSound(): void {
    console.log("Woof! Woof!");
  }

  fetch(): void {
    console.log(`${this.name} is fetching the ball!`);
  }
}

const myDog = new Dog("Buddy", "Golden Retriever");
myDog.makeSound(); // Output: Woof! Woof!
myDog.fetch(); // Output: Buddy is fetching the ball!

ここで、Dog は Animal を拡張し、そのプロパティとメソッドを継承し、独自のプロパティとメソッドも追加しています。

ジェネリックスの型制約

extends キーワードは、ジェネリック関数を使用する場合に非常に重要であり、ジェネリック関数またはクラスで使用できる型を制限できます。

interface Printable {
  print(): void;
}

function printObject<T extends Printable>(obj: T) {
  obj.print();
}

class Book implements Printable {
  print() {
    console.log("Printing a book.");
  }
}

class Magazine implements Printable {
  print() {
    console.log("Printing a magazine.");
  }
}

const myBook = new Book();
const myMagazine = new Magazine();

printObject(myBook);      // Output: Printing a book.
printObject(myMagazine);  // Output: Printing a magazine.
// printObject(42);       // Error, number doesn't have a 'print' method
  1. インターフェイス Printable: ここでは、Printable という名前のインターフェイスを定義します。このインターフェースは、それを実装するクラスが従う必要がある規約を宣言します。この規約では、Printable を実装するクラスは、引数をとらずに void を返す print という名前のメソッドを提供する必要があると指定されています。
  2. function printObject(obj: T): これは printObject という名前の汎用関数です。これは、型 T である obj という名前の単一の引数を受け取ります。型パラメータ T は、この関数の引数として使用できる Printable インターフェイスを拡張 (実装) する型に制限されます。
  3. クラス Book は Printable を実装し、クラス Magazine は Printable を実装します。 ここでは、Book と Magazine という 2 つのクラスを定義します。どちらも Printable インターフェイスを実装します。これは、これらのクラスが、Printable インターフェイスのコントラクトの要求に応じて print メソッドを提供する必要があることを意味します。
  4. const myBook = new Book(); const myMagazine = new Magazine();: Book クラスと Magazine クラスのインスタンスを作成します。
  5. printObject(myBook); and printObject(myMagazine);: Book と Magazine のインスタンスを使用して printObject 関数を呼び出します。 Book クラスと Magazine クラスはどちらも Printable インターフェイスを実装しているため、T extends Printable 型パラメーターの制約を満たします。関数内で、適切なクラスの print メソッドが呼び出され、期待どおりの出力が得られます。
  6. // printObject(42);: Printable インターフェイスを実装していない型 (数値 42 など) で printObject を呼び出そうとすると、TypeScript でエラーが発生します。これは、number に Printable インターフェイスで必要な print メソッドがないため、型制約が満たされていないためです。

要約すると、関数 printObject(obj: T) のコンテキストで extends キーワードを使用して、引数として使用される型 T が Printable インターフェイスによって定義された規約に準拠していることを確認します。これにより、print メソッドを持つ型のみが printObject 関数で使用できるようになり、関数の使用法に特定の動作と規約が適用されます。

条件付きタイプ

T extends U ? X : Y
  • Tはチェックされるタイプです
  • U は、T がチェックされる条件タイプです。
  • X は、T が U を拡張する (割り当て可能である) 場合に条件型が評価する型です。
  • Y は、T が U を拡張しない場合に条件型が評価する型です。
type ExtractNumber<T> = T extends number ? T : never;

type NumberOrNever = ExtractNumber<number>; // number
type StringOrNever = ExtractNumber<string>; // never

ここで、ExtractNumber 型は型パラメーター T を受け取ります。条件付き型は、T が数値型を拡張するかどうかをチェックします。存在する場合、型は T (数値型) に解決されます。そうでない場合、型は none に解決されます。

ユニオン型を使用した extends キーワード

ここで、式 A | を考えてみましょう。 B | C は A を拡張します。これは最初は直観に反しているように思えるかもしれませんが、TypeScript では、この条件は実際には false です。その理由は次のとおりです:

  1. TypeScript では、左側の共用体型で extends を使用する場合、次のように尋ねることと同じです: 「この共用体のすべての可能な型は右側の型に割り当て可能ですか?」
  2. つまり、A | B | C は A の質問を拡張します: 「A を A に割り当てることができ、かつ B を A に割り当てることができ、そして C を A に割り当てることができますか?」
  3. A は確かに A に代入できますが、B と C は (A のサブタイプでない限り) A に代入できない可能性があるため、全体的な結果は false になります。
type Fruit = "apple" | "banana" | "cherry";
type CitrusFruit = "lemon" | "orange";

type IsCitrus<T> = T extends CitrusFruit ? true : false;

type Test1 = IsCitrus<"lemon">; // true
type Test2 = IsCitrus<"apple">; // false
type Test3 = IsCitrus<Fruit>; // false

この例では、IsCitrus Fruit Union のすべてのフルーツが CitrusFruit であるわけではないため、これは false です。

ベストプラクティスとヒント

  • 意味のある関係には extends を使用します: 型間に明確な 「is-a」 関係がある場合にのみ継承を使用します。
  • 継承よりも合成を優先します: 多くの場合、合成 (インターフェイスと型の交差を使用) はクラスの継承よりも柔軟です。
  • 深い継承チェーンには注意してください: 深い継承により、コードの理解と保守が難しくなる可能性があります。
  • 柔軟な API に条件付きタイプを活用する: 条件付きタイプを extends とともに使用して、入力タイプに基づいて適応する API を作成します。
  • ジェネリックで extends を使用して、再利用可能な型安全な関数を作成します: これにより、型安全性を維持しながら、さまざまな型で動作する関数を作成できます

以上がTypeScript をマスターする: extends の力を理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。