「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > TypeScript をマスターする: extends の力を理解する

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

2024 年 11 月 6 日に公開
ブラウズ:998

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: "[email protected]"
};

const staffMember: StaffUser = {
  firstName: "Jane",
  lastName: "Smith",
  email: "[email protected]",
  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(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);および 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 extends number ? T : never;

type NumberOrNever = ExtractNumber; // number
type StringOrNever = ExtractNumber; // never

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

に解決されます。

ユニオン型を使用した 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 extends CitrusFruit ? true : false;

type Test1 = IsCitrus; // true
type Test2 = IsCitrus; // false
type Test3 = IsCitrus; // false

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

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

  • 意味のある関係に拡張を使用する: 型間に明確な 「is-a」 関係がある場合にのみ継承を使用します。
  • 継承よりも合成を優先する: 多くの場合、合成 (インターフェイスと型の交差を使用) はクラスの継承よりも柔軟です。
  • 深い継承チェーンには注意してください: 深い継承により、コードの理解と保守が難しくなる可能性があります。
  • 条件付きタイプを柔軟な API に活用する: 条件付きタイプを extends とともに使用して、入力タイプに基づいて適応する API を作成します。
  • 再利用可能なタイプセーフな関数を作成するには、ジェネリックスで extends を使用します: これにより、タイプ セーフを維持しながら、さまざまな型で動作する関数を作成できます
リリースステートメント この記事は次の場所に転載されています: https://dev.to/hasanm95/mastering-typescript- Understanding-the-power-of-extends-1n2o?1 侵害がある場合は、[email protected] に連絡して削除してください。
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3