Skip to content

Latest commit

 

History

History
431 lines (351 loc) · 10.9 KB

제너릭.md

File metadata and controls

431 lines (351 loc) · 10.9 KB

목차

1. 제너릭을 사용하는 이유(any와 비교)
2. 기본 제너릭 타입 정의(function, interface, type, class)
3. 제너릭 제약조건, 타입매개변수 사용
4. 제너릭 유틸리티 타입
  4-1.	Partial<T> (<-> 용도에 따라 반대개념Required)
  4-2.	Required<T>
  4-3.	Readonly<T>
  4-4.	Record<K,T>
  4-5.	Pick<T,K> (<-> Omit) 인터페이스나 객체에서 제와하거나 해당하는 타입 생성
  4-6.	Omit<T,K>
  4-7.	Exclude<T,U> (<-> Extract) 유니언타입에서 주로 사용
  4-8.	Extract<T,U>
  4-9.	NonNullable<T>
  4-10.	Parameters<T>
  4-11.	ReturnType<T>
  4-12.	ConstructorParameters<T>
  4-13.	InstanceType<T>
  4-14.	ThisParameterType
  4-15.	OmitThisParameter
  4-16.	ThisType<T>

1. 제너릭을 사용하는 이유(any와 비교)

타입스크립트로 한가지 타입이 아니라 여러가지 타입을 받아야할 때 any를 사용할 경우 타입을 잃어버려요
// 원래 소스
const func = (message: string): string => {
    return message
}

console.log(func('1'));
// 여러가지 타입을 받아야 할 경우 -> any -> 타입 잃어버림
const funcAny = (message: any): any => {
    return(message);
}

// 뭘 넣든 호출시그니처 확인 시 any타입
console.log(funcAny(1)); // const funcAny: (message: any) => any
// T(타입변수) 사용해서 제네릭 타입 표현
const funcType = <T>(message: T):T => {
    return message;
};


console.log(funcType(1)); // const funcType: <1>(message: 1) => 1  (타입 매개변수를 생략: T는 1타입이 됨)

// 복잡한 타입유추 시에는 아래처럼 타입 명시 필요  (타입 매개변수를 명시적으로 지정)
console.log(funcType<number>(1)); // const funcType: <number>(message: number) => number
위의 예시는 설명을 위해 작성한 것일 뿐,
실제로 사용시에는 역추적 가능한 타입에 대해서는 굳이 타입명시를 하지 않습니다.

// ex.
const [data, setData] = useState<number>(0); // 클린코드 관점에서 별로에요
const [data, setData] = useState(0); // 역추적이 가능하기 때문에 굳이 명시하지 않아요

초기값이 없는 state에도 굳이 undefined를 명시하지않아요
const [data, setData] = useState();

3번 이상 재사용 할게 아니면 굳이 제네릭으로 타입추론 할 필요가 없다고 생각.
(제네릭을 잘못 사용하면 온통 제네릭 밭이 되어버려요)

2. 기본 제너릭 타입 정의(function, interface, type, class)

// 일반 함수
function funcType<T>(message:T) : T{
    return message;
}

// 익명함수
const funcType = function<T>(message: T):T {
    return message;
};

// 화살표함수
const funcType = <T>(message: T):T => {
    return message;
};
// interface
interface IResponse<T> {
    status: number;
    code: string;
    message: string;
    data: T;
}

type someData = { flag : boolean };

// 타입매개변수 someData로 지정
const response: IResponse<someData> = {
    status: 200,
    code: 'error.code.00000',
    message: '',
    data: someData
}
// type
type TResponse<T> = {
    status: number;
    code: string;
    message: string;
    data: T;
}

type someData = { flag : boolean };

// 타입매개변수 { flag : boolean } 으로 지정
const tResponse: TResponse<someData> = {
    status: 200,
    code: 'error.code.00000',
    message: '',
    data: someData
}
// class
class getnericClass<T> {
    type: T;
    add: (x: T, y: T) => T;

    constructor(type: T) {
        this.type = type;
        this.add = (x: T, y: T) => {
            if (typeof x === 'number') {
                return x + x;
            }
            else {
                return y;
            }
        }
    }
}

const myGenericClass = new getnericClass<number>(10);
const myGenericStringClass = new getnericClass<string>('');

3. 제너릭 제약조건

제너릭 제약조건을 사용아면 특정 타입만 요구할 수 있어서 안정성 올라감
interface IData{
    array : number[];
    length: number;
    data?: {id: string};
    ham?: number;
}

const returnLnegth = <T extends IData>(param: T): number =>{
    return param.length;
}

// array와 length는 반드시 만족하기만 하면 ok
console.log(returnLnegth({array: [1,2,3], length: 3, optionData: '1'}));

4. 제너릭 유틸리티 타입

// 4-1. Partial<T>

// Partial
type TPartial<T> = {
    // [P in T]는 mapped 타입, P는 속성이름(키) T는 가능한 유니언
    // 키밸류가 여러개 들어올꺼니까!
    [P in keyof T]? : T[P] 
}

type MyType = { a: string, b: number };

// 구현한거 사용
const func = (param : TPartial<MyType>) => {
    console.log(param.a, param.b);
}

// 유틸리티 함수 사용
const func2 = (param : Partial<MyType>) => {
    console.log(param.a, param.b);
}
// 4-2. Required<T>

// T의 모든속성 필수로 만듬
type TRequired<T> = {
    // [P in T]는 mapped 타입, P는 속성이름(키) T는 가능한 유니언
    // -? 구문은 선택적 속성 제거
    [P in keyof T]-? : T[P] 
}

type MyType = { a: string, b: number };

// 구현한거 사용
const func = (param : TRequired<MyType>) => {
    console.log(param.a, param.b);
}

// 유틸리티 함수 사용
const func2 = (param : Required<MyType>) => {
    console.log(param.a, param.b);
}
// 4-3.	Readonly<T>

// 모든 속성을 읽기 전용으로 만들어요

// ReadOnly
type TReadOnly<T> = {
    readonly [P in keyof T] : T[P]
}

type MyType = { a: string, b: number };

// 구현한거 사용
let param: TReadOnly<MyType> = { a: 'string', b: 0};
param.a = 'string2';

// 유틸리티 함수 사용
let para2: Readonly<MyType> = { a: 'string', b: 0};
param.a = 'string2';
// 4-4.	Record<T,K>

// T를 속성의 키로, K를 벨류로 사용
// 키인 T에 매칭하는 K가 구체적일 때 사용 (키값을 아는 경우에 주로 사용)
type TRecord<T extends keyof any, K> = {
    // 반복되니까 [P in T] mapped 타입으로 표현
    [P in T]: K
}

type User = {
    id: number;
    name: string;
}

// 구현한거 사용
const userArray: TRecord<number, User> = {
    0: {id: 0, name: 'gayoung'},
    1: {id: 1, name: 'kong'}
}


// 유틸리티 함수 사용
const userArray2: Record<number, User> = {
    0: {id: 0, name: 'gayoung'},
    1: {id: 1, name: 'kong'}
}

// 인덱스 시그니처 사용 
type TUser = {
    [id: number]: User;
}

const userArray3: TUser = {
     0: {id: 0, name: 'gayoung'},
    1: {id: 1, name: 'kong'}
}

// 인덱스 시그니처 객체의 이름과 속성을 미리 알 필요 없고 추가도 가능해서 훨신 유연함
type TPerson = {
    [key in string] : string | number;
}

const person1: TPerson = {
    name: 'gayoung',
    id: 27
}

person1.email = '[email protected]';
// 4-5.	Pick<T,K>

// T에서 K속성만 가지는 타입 생성
// K를 T의 keyof으로 만들어서 반복문으로 mapped하게 만들기
type TPick<T, K extends keyof T> = {
    [P in K]: T[K]
}


interface IResponse<T>{
    code: number;
    message: string;
    data: T
}

type c = TPick<IResponse<{flag: boolean}>, 'data'>;
type b = Pick<IResponse<{flag: boolean}>, 'data'>; // { data: {flag: boolean } }
// 4-6. Omit<T,K>

// T에서 K 제외한 타입 생성
type TOmit<T, K> = Pick<T, Exclude<keyof T, K>>;

interface IResponse<T>{
    code: number;
    message: string;
    data: T
}

type c = TOmit<IResponse<{flag: boolean}>, 'data'>;
type b = Omit<IResponse<{flag: boolean}>, 'data'>; // {code: number; message: string;}
// 4-7. Exclude<T,U>

// T에서 유니언 타입 U에 해당하는 속성 제외

type TExclude<T, U> = T extends U ? never : T; 

interface IResponse<T>{
    code: number;
    message: string;
    data: T
}

type c = TExclude<IResponse<{flag: boolean}>, {code: number} | {message: string}>; // never
type b = Exclude<IResponse<{flag: boolean}>, {code: number} | {message: string}>; // never

// type Exclude<T, U> = T extends U ? never : T U가 T에 확장형이면 없애고, 아닌것만 리턴
type T0 = Exclude<"a" | "b" | "c", "a">;  // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
Omit과 Exclude는 동일한 결과를 반환하지만, 과정상 Omit은 특정 속성을 제거한 타입 반환
Exclude는 특정 Union Type을 제외한 나머지 Union Type을 제거함

=> Omit은 객체나 인터페이스에서, Exclude는 유니언에서
=> Pick은 객체나 인터페이스에서, Extract은 유니언에서
// 4-8.	Extract<T,U>

// T에서 U에 해당하는 속성들만 추출
type Extract<T, U> = T extends U ? T : never;

type T0 = Extract<"a" | "b" | "c", "a">;  // "a"
type T1 = Extract<"a" | "b" | "c", "a" | "b" | "F">; // "a" | "b"
// 4-9.	NonNullable<T>

// null과 undefined 제외타입 생성 ({}와 같은의미)

type TNonNullable<T> = T extends {} ? T : never; // type NonNullable<T> = T & {} 이렇게 쓴다 

type b = TNonNullable<string | number | undefined>; // string | number
type a = NonNullable<string | number | undefined>;  // string | number
// 함수타입 T 매개변수와 타입을 튜플로 추출
// 4-10.	Parameters<T>

// 함수타입 T의 리턴 추출
// 4-11. ReturnType<T>

const func = (a: number, b: number) :string => {

  return a.toString() + b.toString();
}

// 직접 구현
// infer로 동적으로 타입 유추 가능
type TParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type a0 = TParameters<typeof func>; // {a: number, b:number}

type TResponse<T extends (...args: any) => any> = T extends (...args: infer P) => infer R ? R : never;
type b0 = TResponse<typeof func>; // returnType string;

// 타입스크립트에서 제공하는 함수 Parameters,ReturnType
type a = Parameters<typeof func>; // {a: number, b:number}
type b = ReturnType<typeof func>; // returnType string;
// 4-12. ConstructorParameters<T>

// 클래스타입 생성자 T의 매개변수들을 튜플로 추출

class person { 
    name : string;
    age: number;
    constructor (name:string, age:number) {
        this.name = name;
        this.age = age;
    }
}

type TConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ?  P : never;

// 직접구현
type a0 = TConstructorParameters<typeof person>;

// 타입스크립트에서 제공하는 함수 ConstructorParameters
type a = ConstructorParameters<typeof person>; // [name: string, age: number]
// 4-13. InstanceType<T>

// 생성자 함수타입 T의 인스턴스 타입 추출 
// 4-14. ThisParameterType
// 4-15. OmitThisParameter
// 4-16. ThisType<T>