TIL 75 | TS Advanced



李在勝の実戦反応プログラム整理の内容を読みました
タイプスクリプトの高度な機能には、JENIC、MAPDタイプ、および条件タイプがあります.

ジェニーリック


ジェニーリックはそのタイプの情報が動的に決定されるタイプです.同じルールを複数のタイプに適用できるため、タイプコードの作成時に発生する可能性のある重複コードを排除できます.例を挙げて、Genericを使用して重複コードを解決する方法を説明します.

再構築が必要なコード

function makeNumberArray(defaultValue: number, size: number): number[] {
  const arr: number[] = [];
  for (let i = 0; i < size; i++) {
    arr.push(defaultValue);
  }
  return arr;
}
function makeStringArray(defaultValue: string, size: number): string[] {
  const arr: string[] = [];
  for (let i = 0; i < size; i++) {
    arr.push(defaultValue);
  }
  return arr;
}
const arr1 = makeNumberArray(1, 10);
const arr2 = makeStringArray('empty', 10);
数値配列と文字列配列の関数をそれぞれ生成し,重複するコードを見ることができる.

関数リロードによる改善されたコード

function makeArray(defaultValue: number, size: number): number[];
function makeArray(defaultValue: string, size: number): string[];
function makeArray(defaultValue, size) {
  const arr = [];
  for (let i = 0; i < size; i++) {
    arr.push(defaultValue);
  }
  return arr;
}
数値と文字列だけが必要な場合は、上記のように改善できますが、問題は、タイプを追加するたびにコードを追加し、タイプが多ければ多いほどコードの可読性が悪くなることです.

ジェニーリックで解決したコード


ジェニーリックは<>記号で定義され、名前は自由に指定できます.
function makeArray<T>(defaultValue: T, size: number): T[] {
  const arr: T[] = [];
  for (let i = 0; i < size; i++) {
    arr.push(defaultValue);
  }
  return arr;
}
const arr1 = makeArray<number>(1, 10);
const arr2 = makeArray<string>('empty', 10);
const arr3 = makeArray(1, 10); // 첫 번째 매개변수를 알면 타입 T를 알 수 있다.
タイプTは、関数を使用する場合に入力されるため、どのタイプであるかは不明である.関数内部ではTのタイプ情報も利用できる.

Genericによるスタックの実装


ジュネーブはデータ型の多様性を与えるため,資料構造でよく用いられる.
class Stack<D> {
  private items: D[] = [];
  push(item: D) {
    this.items.push(item);
  }
  pop() {
    return this.items.pop();
  }
}

const numberStack = new Stack<number>();
numberStack.push(10);
const v1 = numberStack.pop();
const stringStack = new Stack<string>();
stringStack.push('a');
const v2 = stringStack.pop();

let myStack: Stack<number>;
myStack = numberStack;
myStack = stringStack; // 숫자 스택에 문자열 스택 할당 불가

extendsキーワードを使用してジェニーンリックタイプを制限する


JENERICタイプは任意のタイプを入力できますが、ライブラリ内のAPI(REACTなど)は入力可能な値の範囲を制限します.extendsキーワードを使用すると、入力可能なタイプを制限できます.
function identity<T extends number | string>(p1: T): T {
  return p1;
}
identity(1);
identity('a');
identity([]); // 타입 에러
JENNERIC Tのタイプは数字と文字列に制限されているため、配列の割り当てを試みるとタイプエラーが発生します.
  • KoreanインタフェースはPersonが拡張したものである.したがって,KoreanタイプはPersonタイプに割り当てることができる.
  • ジェニーンリックTはPersonに割り当てられるタイプでなければならない.ジェニーンリックKはPersonの属性名でなければなりません.
  • keyofキーワードは、インタフェース内のすべての属性名をUnionタイプとして作成します.*
  • p 1,p 2はPersonに割り当てることができるので、タイプエラーは発生しません.
  • interface Product {
      name: string;
      age: number;
    }
    const p1: Product = {
      name: '시계',
      price: 1000,
    };
    const p2: Product = {
      name: '자전거',
      price: 2000,
    };
    function swapProperty<T extends Person, K extends keyof Person>(
      p1: T,
      p2: T,
      name: K
    ): void {
      const temp = p1[name];
      p1[name] = p2[name];
      p2[name] = temp;
      console.log(p1, p2);
    }
    swapProperty(p1, p2, 'name'); // 타입 에러
    製品はPersonに割り当てられないため、タイプエラーが発生します.

    マップタイプ(Map Type)


    mapdタイプを使用すると、いくつかのルールを使用して新しいインタフェースを作成できます.マッピングタイプは、インタフェース内のすべてのアトリビュートを選択アトリビュートまたは読み取り専用アトリビュートに設定するために使用します.
    interface Person { // 맵드 타입의 입력으로 사용될 인터페이스
      name: string;
      age: number;
    }
    interface PersonOptional { // Person에 맵드 타입을 적용해 만들 예
      name?: string;
      age?: number;
    }
    interface PersonReadOnly {
      readonly name: string;
      readonly age: number;
    }
    マッピングタイプは、inキーワード定義を使用します.

    2つのアトリビュートを非タイプにするマッピングタイプ。

    type T1 = { [K in 'prop1' | 'prop2']: boolean };
    // { prop1: boolean; prop2: boolean; }
    inキーワードの右側には、文字列のunionタイプがあります.

    マッピングタイプを使用して、インタフェース内のすべてのアトリビュートを非タイプ(None Type)アトリビュートと選択(Select)アトリビュートにします。

    type MakeBoolean<T> = { [P in keyof T]?: boolean };
    const pMap: MakeBoolean<Person> = {};
    pMap.name = true;
    pMap.age = false;

    PartialとReadonly埋め込み


    タイプスクリプト内蔵タイプPartialとReadonlyはmapdタイプです.
    // 인터페이스에서 특정 속성의 타입을 추출할 때 사용되는 문법, 맵드 타입에서 많이 쓰인다.
    type T1 = Person['name']; // string
    //인터페이스의 모든 속성을 읽기 전용으로 만들어주는 맵드 타입이다.
    // T[P]는 인터페이스의 T에 있는 속성 P의 타입을 그대로 사용하겠다는 의미이다.
    type Readonly<T> = { readonly [P in keyof T]: T[P] };
    // 인터페이스의 모든 속성을 선택 속성으로 만들어주는 맵드 타입이다.
    type Partial<T> = { [P in keyof T]?: T[P] };
    
    type T2 = Partial<Person>;
    type T3 = Readonly<Person>;

    Pick内蔵タイプ


    タイプスクリプト内蔵タイプPickは、インタフェースから必要なプロパティを抽出するためにのみ使用されます.
    type Pick<T, K extends keyof T> = { [P in K]: T[P] };
    interface Person {
      name: string;
      age: number;
      language: string;
    }
    type T1 = Pick<Person, 'name' | 'language'>;
    // type T1 = { name: string; language: string }

    内蔵タイプの記録


    Recordは、入力したすべてのプロパティを同じタイプに作成するマッピングタイプです.
    type Record<K extends string, T> = { [P in K]: T };
    type T1 = Record<'p1' | 'p2', Person>;
    // type T1 = { p1: Person, p2: Person }
    Kは文字列のサブタイプです.Kとして入力されたすべての文字列を属性名とし、Rを各属性のタイプとします.

    列挙タイプとマッピングタイプ


    mapdタイプを使用すると、列挙タイプの利用率が向上します.
    enum Fruit {
      Apple,
      Banana,
      Orange,
    }
    const FRUIT_PRICE = {
      [Fruit.Apple]: 1000,
      [Fruit.Banana]: 1500,
      [Fruit.Orange]: 2000,
    }
    「Freit列挙型」タイプに新しいフルーツを追加すると、「FRUIT PRICE」に新しいフルーツの価格情報を追加するのはよくありますが、「Freit列挙型」タイプにフルーツを追加して価格情報を忘れてもエラーは発生しません.下の図に示すように、FRUIT PRICEオブジェクトがFruitのすべての要素をプロパティとして持っていることを確認します.
    enum Fruit {
      Apple,
      Banana,
      Orange,
    }
    const FRUIT_PRICE: { [key in Fruit]: number } { // 타입 에러
      [Fruit.Apple]: 1000,
      [Fruit.Banana]: 1500,
    }
    // Orange 속성을 추가해야 타입 에러가 사라진다.

    条件タイプ


    条件付きタイプは、入力されたJENERICタイプに応じてタイプを決定できる機能です.
    // T extends U ? X : Y
    type IsStringType<T> = T extends string ? 'yes' : 'no';
    type T1 = IsStringType<string>; // 'yes'
    type T2 = IsStringType<number>; // 'no'
    条件付きタイプの基本構造.入力されたJENICタイプTがタイプUのサブタイプであれば、タイプXを使用し、そうでなければタイプYを使用する.
    条件付きタイプでUnionタイプを使用すると、多くの有用なユーティリティタイプを作成できます.
    type T1 = IsStringType<string | number>; // 'yes' | 'no'
    type T2 = IsStringType<string> | IsStringType<number>;
    // 조건부 타입에 유니온 타입이 입력되면 하나씩 검사해서 타입을 결정하고 결과는 유니온 타입으로 만들어 진다.
    // 결과적으로 T1과 T2는 같은 타입이다.

    Exclude、Extract埋め込み


    タイプスクリプトに組み込まれたExclude、Extractタイプは、条件付きで作成できます.
    type T1 = number | string | never; // string | number
    type Exclude<T, U> = T extends U ? never | T;
    type T2 = Exclude<1 | 3 | 5 | 7, 1 | 5 | 9>; // 3 | 7
    type T3 = Exclude<string | numebr | (() => void), Function>; // string | number;
    type Extract<T, U> = T extends U ? T : never;
    type T4 = Extract<1 | 3 | 5 | 7, 1 | 5 | 9>; // 1 | 5
    Unionタイプのneverタイプは削除されます.これは条件タイプでよく使われる機能です.Excludeタイプは、Uのサブタイプを削除できるユーティリティタイプですが、ExtractはExcludeとは逆です.

    ReturnType埋め込み


    条件タイプによって作成されたReturnTypeは、関数の戻りタイプを抽出します.
    // 입력된 타입 T가 함수면 함수의 반환 타입이 사용되고, 그렇지 않으면 any 타입이 사용된다.
    type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
    type T1 = ReturnType<() => string>; // string
    function f1(s: string): number {
      return s.length;
    }
    type T2 = ReturnType<typeof f1>; // number
    informateキーワードを用いてタイプ推定を行った.informiキーワードのおかげで、関数の戻りタイプをRという変数に含めることができます.条件タイプを定義する場合、informateキーワードはextendsキーワードの後ろに使用されます.以下のように重ねて使用することもできます.
    // 타입 T가 U의 배열이면 U가 사용된다.
    type Unpacked<T> = T extends (infer U)[]
    	? U
    	// 함수면 반환 타입이 사용된다.
    	: T extends (...args: any[]) => infer U
    		? U
    		// 프로미스면 프로미스에 입력된 제네릭 타입이 사용된다.
    		: T extends Promise<infer U> ? U : T;
    // 아무것도 만족하지 않으면 자기 자신이 된다. 
    type T0 = Unpacked<string>; // string
    type T1 = Unpacked<string[]>; // string
    type T2 = Unpacked<() => string>; // string
    type T3 = Unpacked<Promise<string>>; // string
    // Promise<string>의 배열이므로 Promise<string>이 된다.
    type T4 = Unpacked<Promise<string>[]>; // Promise<string>
    type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string

    条件付き直接作成ユーティリティタイプ


    条件タイプを使用して、いくつかのユーティリティタイプを作成します.
    // 타입 T에서 값이 문자열인 모든 속성의 이름을 유니온 타입으로 만들어 주는 유틸리티 타입이다. 
    type StringProperyNames<T> = {
      [K in keyof T]: T[K] extends String ? K : never
    // [keyof T]는 인터페이스에서 모든 속성의 타입을 유니온으로 추출한다. 이때 never 타입은 제거된다.
    }[keyof T];
    // StringProperties는 인터페이스에서 문자열인 모든 속성을 추출하는 유틸리티 타입이다.
    type StringProperties<T> = Pick<T, StringPropertyNames<T>>;
    interface Person {
      name: string;
      age: number;
      nation: string;
    }
    type T1 = StringPropertyNames<Person>; // 'name' | 'nation'
    type T2 = StringProperties<Person>; // { name: string, nation: string; }

    一部のプロパティのみを削除するユーティリティタイプ

    // 인터페이스 T에서 입력된 속성 이름 U를 제거한다.
    type Omit<T, U extends keyof T> = Pick<T, Exclude<keyof T, U>>;
    interface Person {
      name: string;
      age: number;
      nation: string;
    }
    type T1 = Omit<Person, 'nation' | 'age'>;
    const p: T1 = {
      // Person에서 nation, age 속성을 제거했으므로 타입 T1 에는 name 속성만 남는다.
      name: 'mike',
    }

    インタフェースを上書きするユーティリティのタイプ

    // 인터페이스 T에 인터페이스 U를 덮어쓴다.
    type Overwrite<T, U> = { [P in Exclude<keyof T, keyof U>]: T[P] } & U;
    interface Person {
      name: string;
      age: number;
    }
    type T1 = Overwrite<Person, { age: string; nation: string }>;
    const p: T1 = {
      name: 'mike',
      // age 속성의 타입은 문자열로 변경됐고, nation 속성은 새로 추가됐다.
      age: '23',
      nation: 'korea',
    }