ホームページ >ウェブフロントエンド >jsチュートリアル >すべての開発者が知っておくべき高度な typescript の概念

すべての開発者が知っておくべき高度な typescript の概念

DDD
DDDオリジナル
2024-11-26 07:18:10894ブラウズ

TypeScript は、タイプ セーフ性が強化されているため、JavaScript よりも好まれることが多い最新のプログラミング言語です。この記事では、TypeScript プログラミング スキルを磨くのに役立つ TypeScript の概念トップ 10 を紹介します。準備はできていますか? 始めましょう。

Top Advanced typescript concepts that Every Developer Should Know

1.ジェネリック : ジェネリックを使用すると、再利用可能な型を作成できます。これは、今日のデータだけでなく明日のデータも処理するのに役立ちます。
ジェネリックの例:
Typescript で引数を型として受け取る関数が必要になる場合があり、同じ型を返したい場合もあります。

function func<T>(args:T):T{
    return args;
}

2.型制約のあるジェネリック : 次に、文字列と整数のみを受け入れるように定義して型 T を制限しましょう:

function func<T extends string | number>(value: T): T {
    return value;
}

const stringValue = func("Hello"); // Works, T is string
const numberValue = func(42);      // Works, T is number

// const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'

3.汎用インターフェース:
インターフェイス ジェネリックは、さまざまな型で動作するオブジェクト、クラス、または関数のコントラクト (シェイプ) を定義する場合に便利です。これらを使用すると、構造の一貫性を保ちながら、さまざまなデータ型に適応できるブループリントを定義できます。

// Generic interface with type parameters T and U
interface Repository<T, U> {
    items: T[];           // Array of items of type T
    add(item: T): void;   // Function to add an item of type T
    getById(id: U): T | undefined; // Function to get an item by ID of type U
}

// Implementing the Repository interface for a User entity
interface User {
    id: number;
    name: string;
}

class UserRepository implements Repository<User, number> {
    items: User[] = [];

    add(item: User): void {
        this.items.push(item);
    }

     getById(idOrName: number | string): User | undefined {
        if (typeof idOrName === 'string') {
            // Search by name if idOrName is a string
            console.log('Searching by name:', idOrName);
            return this.items.find(user => user.name === idOrName);
        } else if (typeof idOrName === 'number') {
            // Search by id if idOrName is a number
            console.log('Searching by id:', idOrName);
            return this.items.find(user => user.id === idOrName);
        }
        return undefined; // Return undefined if no match found
    }
}

// Usage
const userRepo = new UserRepository();
userRepo.add({ id: 1, name: "Alice" });
userRepo.add({ id: 2, name: "Bob" });

const user1 = userRepo.getById(1);
const user2 = userRepo.getById("Bob");
console.log(user1); // Output: { id: 1, name: "Alice" }
console.log(user2); // Output: { id: 2, name: "Bob" }

4.Generic Classes:: クラス内のすべてのプロパティをジェネリック パラメーターで指定された型に準拠させる場合にこれを使用します。これにより、クラスのすべてのプロパティがクラスに渡された型と確実に一致するようにしながら、柔軟性が可能になります。

interface User {
    id: number;
    name: string;
    age: number;
}

class UserDetails<T extends User> {
    id: T['id'];
    name: T['name'];
    age: T['age'];

    constructor(user: T) {
        this.id = user.id;
        this.name = user.name;
        this.age = user.age;
    }

    // Method to get user details
    getUserDetails(): string {
        return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`;
    }

    // Method to update user name
    updateName(newName: string): void {
        this.name = newName;
    }

    // Method to update user age
    updateAge(newAge: number): void {
        this.age = newAge;
    }
}

// Using the UserDetails class with a User type
const user: User = { id: 1, name: "Alice", age: 30 };
const userDetails = new UserDetails(user);

console.log(userDetails.getUserDetails());  // Output: "User: Alice, ID: 1, Age: 30"

// Updating user details
userDetails.updateName("Bob");
userDetails.updateAge(35);

console.log(userDetails.getUserDetails());  // Output: "User: Bob, ID: 1, Age: 35"
console.log(new UserDetails("30"));  // Error: "This will throw error" 

5.型パラメータを渡された型に制約する: パラメータの型を他の渡されたパラメータに依存させたい場合があります。ややこしいように聞こえるので、以下の例を見てみましょう。

function getProperty<Type>(obj: Type, key: keyof Type) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a");  // Valid
getProperty(x, "d");  // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.

6.条件型 : 多くの場合、型を 1 つまたは別の型にしたいと考えます。このような状況では、条件型を使用します。
簡単な例は次のとおりです:

function func(param:number|boolean){
return param;
}
console.log(func(2)) //Output: 2 will be printed
console.log(func("True")) //Error: boolean cannot be passed as argument

少し複雑な例:

type HasProperty<T, K extends keyof T> = K extends "age" ? "Has Age" : "Has Name";

interface User {
  name: string;
  age: number;
}

let test1: HasProperty<User, "age">;  // "Has Age"
let test2: HasProperty<User, "name">; // "Has Name"
let test3: HasProperty<User, "email">; // Error: Type '"email"' is not assignable to parameter of type '"age" | "name"'.

6.Intersection タイプ: これらのタイプは、複数のタイプを 1 つに結合して、特定のタイプが他のさまざまなタイプからプロパティと動作を継承できるようにする場合に便利です。
この興味深い例を見てみましょう:

// Defining the types for each area of well-being

interface MentalWellness {
  mindfulnessPractice: boolean;
  stressLevel: number; // Scale of 1 to 10
}

interface PhysicalWellness {
  exerciseFrequency: string; // e.g., "daily", "weekly"
  sleepDuration: number; // in hours
}

interface Productivity {
  tasksCompleted: number;
  focusLevel: number; // Scale of 1 to 10
}

// Combining all three areas into a single type using intersection types
type HealthyBody = MentalWellness & PhysicalWellness & Productivity;

// Example of a person with a balanced healthy body
const person: HealthyBody = {
  mindfulnessPractice: true,
  stressLevel: 4,
  exerciseFrequency: "daily",
  sleepDuration: 7,
  tasksCompleted: 15,
  focusLevel: 8
};

// Displaying the information
console.log(person);

7.infer キーワード: infer キーワードは、特定の型を条件付きで決定する場合に便利です。条件が満たされた場合、その型からサブタイプを抽出できます。
これは一般的な構文です:

type ConditionalType<T> = T extends SomeType ? InferredType : OtherType;

この例:

type ReturnTypeOfPromise<T> = T extends Promise<infer U> ? U : number;

type Result = ReturnTypeOfPromise<Promise<string>>;  // Result is 'string'
type ErrorResult = ReturnTypeOfPromise<number>;      // ErrorResult is 'never'

const result: Result = "Hello";
console.log(typeof result); // Output: 'string'

8.Type Variance : この概念は、サブタイプとスーパータイプが互いにどのように関連しているかを説明します。
これらには 2 つのタイプがあります:
共分散: スーパータイプが予期される場所でサブタイプを使用できます。
この例を見てみましょう:

function func<T>(args:T):T{
    return args;
}

上記の例では、Car は Vehicle クラスからプロパティを継承しているため、サブタイプがスーパータイプが持つすべてのプロパティを持つことになるため、スーパータイプが期待されるサブタイプにそれを割り当てることは絶対に有効です。
反変性: これは共分散の反対です。サブタイプが期待される場所ではスーパータイプを使用します。

function func<T extends string | number>(value: T): T {
    return value;
}

const stringValue = func("Hello"); // Works, T is string
const numberValue = func(42);      // Works, T is number

// const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'

反変性を使用する場合、エラーが発生する可能性があるため、サブタイプに固有のプロパティまたはメソッドにアクセスしないように注意する必要があります。

9.リフレクション: この概念には、実行時に変数の型を決定することが含まれます。 TypeScript は主にコンパイル時の型チェックに重点を置いていますが、TypeScript 演算子を利用して実行時に型を検査することもできます。
typeof 演算子: typeof 演算子を使用して、実行時に変数の型を見つけることができます

// Generic interface with type parameters T and U
interface Repository<T, U> {
    items: T[];           // Array of items of type T
    add(item: T): void;   // Function to add an item of type T
    getById(id: U): T | undefined; // Function to get an item by ID of type U
}

// Implementing the Repository interface for a User entity
interface User {
    id: number;
    name: string;
}

class UserRepository implements Repository<User, number> {
    items: User[] = [];

    add(item: User): void {
        this.items.push(item);
    }

     getById(idOrName: number | string): User | undefined {
        if (typeof idOrName === 'string') {
            // Search by name if idOrName is a string
            console.log('Searching by name:', idOrName);
            return this.items.find(user => user.name === idOrName);
        } else if (typeof idOrName === 'number') {
            // Search by id if idOrName is a number
            console.log('Searching by id:', idOrName);
            return this.items.find(user => user.id === idOrName);
        }
        return undefined; // Return undefined if no match found
    }
}

// Usage
const userRepo = new UserRepository();
userRepo.add({ id: 1, name: "Alice" });
userRepo.add({ id: 2, name: "Bob" });

const user1 = userRepo.getById(1);
const user2 = userRepo.getById("Bob");
console.log(user1); // Output: { id: 1, name: "Alice" }
console.log(user2); // Output: { id: 2, name: "Bob" }

instanceof 演算子:instanceof 演算子は、オブジェクトがクラスまたは特定の型のインスタンスであるかどうかを確認するために使用できます。

interface User {
    id: number;
    name: string;
    age: number;
}

class UserDetails<T extends User> {
    id: T['id'];
    name: T['name'];
    age: T['age'];

    constructor(user: T) {
        this.id = user.id;
        this.name = user.name;
        this.age = user.age;
    }

    // Method to get user details
    getUserDetails(): string {
        return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`;
    }

    // Method to update user name
    updateName(newName: string): void {
        this.name = newName;
    }

    // Method to update user age
    updateAge(newAge: number): void {
        this.age = newAge;
    }
}

// Using the UserDetails class with a User type
const user: User = { id: 1, name: "Alice", age: 30 };
const userDetails = new UserDetails(user);

console.log(userDetails.getUserDetails());  // Output: "User: Alice, ID: 1, Age: 30"

// Updating user details
userDetails.updateName("Bob");
userDetails.updateAge(35);

console.log(userDetails.getUserDetails());  // Output: "User: Bob, ID: 1, Age: 35"
console.log(new UserDetails("30"));  // Error: "This will throw error" 

サードパーティのライブラリを使用して、実行時に型を決定できます。

10.依存関係の注入: 依存関係の注入は、コンポーネントで実際にコードを作成したり管理したりせずに、コンポーネントにコードを取り込むことができるパターンです。ライブラリを使用しているように見えるかもしれませんが、CDN や API を介してインストールまたはインポートする必要がないため、異なります。

一見すると、どちらもコードの再利用が可能であるため、再利用を目的とした関数の使用と似ているように思えるかもしれません。ただし、コンポーネント内で関数を直接使用すると、コンポーネント間の密結合が生じる可能性があります。これは、関数またはそのロジックを変更すると、それが使用されるすべての場所に影響を与える可能性があることを意味します。

Dependency Injection は、依存関係の作成をそれを使用するコンポーネントから切り離すことでこの問題を解決し、コードの保守性とテスト性を高めます。

依存性注入なしの例

function getProperty<Type>(obj: Type, key: keyof Type) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a");  // Valid
getProperty(x, "d");  // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.

依存性注入を使用した例

function func(param:number|boolean){
return param;
}
console.log(func(2)) //Output: 2 will be printed
console.log(func("True")) //Error: boolean cannot be passed as argument

密結合シナリオでは、今日 MentalWellness クラスにストレスレベル属性があり、明日それを別の属性に変更することにした場合、それが使用されているすべての場所を更新する必要があります。これにより、リファクタリングやメンテナンスに多くの課題が生じる可能性があります。

ただし、依存関係の注入とインターフェイスの使用により、この問題を回避できます。コンストラクターを介して依存関係 (MentalWellness サービスなど) を渡すことにより、特定の実装の詳細 (stressLevel 属性など) がインターフェイスの背後に抽象化されます。これは、インターフェイスが同じである限り、属性またはクラスを変更しても、依存クラスを変更する必要がないことを意味します。このアプローチにより、コンポーネントを密結合せずに実行時に必要なものを注入できるため、コードが疎結合になり、保守性が向上し、テストが容易になります。

以上がすべての開発者が知っておくべき高度な typescript の概念の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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