typescript

2.28 ~ 2.32

케케_ 2024. 10. 20. 20:10

목차

 

2.28 원시 자료형에도 브랜딩 기법을 사요할 수 있다 

: string, number 같은 원시 자료형 타입도 더 세밀하게 구분 가능

 

킬로미터를 마일로 바꾸는 함수 코드

function kmToMile (km : number) {
    return km * 0.62;
}
const mile = kmToMile(3);
  • 문제 : 함수에 넣는 '3'이라는 숫자가 마일인지, 킬로미터인지 알 수 없음
    • 킬로미터, 마일 타입 존재 X

-> 브랜딩 기법으로 구체적 타입 지정

type Brand<T,B> = T & {__brand : B};
type KM = Brand<number, 'km'>;
type Mile = Brand<number, 'mile'>;

function kmToMile (km : KM) {
    return km * 0.62 as Mile;
}
const km = 3 as KM;
const mile = kmToMile(km);
const mile2 = 5 as Mile;
kmToMile(mile2);    //오류 - 매개변수로 KM 타입만 들어갈 수 있기 때문
  • Brand 타입 : number 타입에 각자의 브랜드 속성 추가해 서로 구별
    • 원래 자료형 + 새로운 자료형 
    • KM : number & {__brand: 'km'}
    • Mile : number & {__brand: 'mile'}
  • KM, Mile 타입
    • as 강제 변환 필수 (기존 타입이 아니기 때문)
    • 대신 한번만 하면됨

 

 

2.29 배운 것을 바탕으로 타입을 만들어보자

: 유용한 타입 만들기

 

 

2.29.1 판단하는 타입 만들기

: 특정 타입이 무슨 타입인지 알기

 -> 해당 타입을 컨디셔널 타입으로 제거 및 추리기 가능

(any는 안쓰는게 좋음)

 

IsNever

: never인지 판단

type IsNever<T> = [T] extends [never] ? true : false;
  • 배열 사용 -> T에 never를 넣을 때, 분배법칙 발생을 막기 위해
1. 제네릭과 유니언이 만나면 분배법칙이 실행됩니다. never는 그 자체로 유니언이라서 분배법칙이 일어나나, never는 "공집합"이라서 분배할 게 없습니다. never는 그냥 never입니다. boolean같은 것이나 true | false로 쪼개지는 것이죠.
2. 분배법칙에서는 never extends string 자체가 실행되지 않습니다. 그래서 그냥 never입니다.
3. 그냥 편하게 never extends X 는 무조건 never라고 생각하셔도 됩니다.(never 자리가 제네릭인 경우)

https://www.inflearn.com/community/questions/1102922?focusComment=303009

 

제네릭에 never가 들어올 때 분배법칙 - 인프런 | 커뮤니티 질문&답변

누구나 함께하는 인프런 커뮤니티. 모르면 묻고, 해답을 찾아보세요.

www.inflearn.com

 

 

IsAny

: any 타입인지 판단

 

type IsAny<T> = string extends (number & T) ? true : false;
  • string과 numberdms 절대 겹칠 수 없음 -> extend 불가
  • 예외 : any
    • number & any -> any
    • string은 any를 extends 가능

-> T 가 any일 경우에만 true

 

 

IsArray

T extends unknown[] ? true : false

일 경우 never, any, readonly [] 타입을 걸르지 못함

 

type IsNever<T> = [T] extends [never] ? true : false;
type IsAny<T> = string extends (number & T) ? true : false;
type IsArray<T> = IsNever<T> extends true
    ? false
    : T extends readonly unknown[]
    ? IsAny<T> extends true
        ? false
        : true
    :false;
  • IsNever<T> extends true
    • IsArray<never> 가 never 되는 것을 막기 위함
  • IsAny<T> extends true
    • IsArray < any > 가 boolean이 되는 것을 막기 위함
  • T extends readonly unknown[]
    • IsArray < readonly [] > 가 false 되는 것을 막기 위함

 

IsTuple

: 배열에서 튜플만 판단

: 튜플이 아닌 배열 타입은 false

type IsNever<T> = [T] extends [never] ? true : false;

type IsTuple<T> = IsNever<T> extends true
    ? false
    : T extends readonly unknown[]
        ? number extends T ["length"]
            ? false
            : true
        : false;
  • 배열과 튜플의 차이점
    • 튜플 : 길이 고정 ex) 1,2,3 (숫자로 고정)
    • 배열을 lenth할 경우 -> number 타입이 나옴
  • number extends T["length"]는 false (튜플)
    • 위 코드에서 any 걸러짐
    • any["length"] -> any : number extends any -> true

 

 

IsUnion

type IsUnion<T, U=T> = IsNever<T> extends true
    ? false
    : T extends T
        ? [U] extends [T]
            ? false
            : true
        : false;
  • T extends T : 분배법칙을 만들기 위함
  •  

 

 

2.29.2 집합 관련 타입 만들기

: ts의 타입은 집합의 원리를 충실하게 따름

: 다양한 집합의 연산, 특성을 타입으로 나타내보기

 

 

차집합 만들기

type Diff<A,B> = Omit<A & B, keyof B>;
type R1 = Diff<{name : string, age: number}, {name : string, married: boolean}> // {age : number}
  • Omit : 특정 객체에서 지정한 속성 제거
    • Omit<A & B, keyof B>
    • {name : string, age: number, married: boolean} - name | married = age

 

대칭 차집합 (합집함 - 교집합)

type SymDiff<A,B> = Omit<A & B, keyof (A|B)>;
// type R1 = {age : number, married: boolean}
type R1 = SymDiff<{name : string, age: number}, {name : string, married: boolean}>

 

위의 차집합과 대칭 차집합은 객체에만 적용 가능 (유니언에는 적용 X)

 

 

유니언에서 대칭 차집합 적용하기

type SymDiffUnion<A,B> = Exclude<A | B, A & B>;
type R3 = SymDiffUnion<1|2|3, 2|3|4>    //1|4
  • Exclude : 어떤 타입 (A | B)에서 다른 타입 (A & B) 제거하는 타입

 

부분집합 알아보기

type IsSubset <A,B> = A extends B ? true : false;
type R1 = IsSubset<string, string | number>;    //true
type R2 = IsSubset<{name:string, age:number}, {name:string}>;   //true
type R3 = IsSubset<symbol, unknown> //true

 

 

두 타입이 동일한지 확인

 - 두 집합이 서로의 부분 집합이면 동일한 집합임

type Equal<A, B> = A extends B ? B extends A ? true : false :false;

 

위 코드는 허점 존재

type Equal<A, B> = A extends B ? B extends A ? true : false :false;
type R1 = Equal<boolean, true | false>  //booelan
type R2 = Equal<never, never>           //never
  • booelan, never는 유니언으로 분배법칙 발생

 

(해결)

type Equal<A, B> = [A] extends [B] ? [B] extends [A] ? true : false :false;
type R1 = Equal<boolean, true | false>  //true
type R2 = Equal<never, never>           //true

 

하지만 any는 구별하기 못함

type R3 = Equal<any, 1>             //true
type R4 = Equal<[any], [number]>    //true

 

(해결)

type Equal2<X,Y>
    = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
    ? true : false

 

2.30 타입스크립트의 에러 코드로 검색하자

: 타입스크립트의 에러 메세지 끝에는 항상 숫자 존재

 

  • 2345 -> 'TS2345'로 검색하면 해결 가능

숫자 = 에러 유형

-> 에러 발생 시 해결에 도움을 줌

 

 

2.31 함수에 기능을 추가하는 데코레이터 함수가 있다

: 5.0에서 데코레이터 함수가 정식 추가됨

: 데코레이터 = 클래스의 기능을 증강하는 함수

: 여러 함수에서 공통으로 수행되는 부분을 데이코레이터로 만들면 유용함

 

코드 반복 예

class A {
    eat() {
        console.log('start');
        console.log('eat');
        console.log('end');
    }

    work() {
        console.log('start');
        console.log('work');
        console.log('end');
    }

    sleap() {
        console.log('start');
        console.log('sleep');
        console.log('end');
    }
}
  • start와 end를 로깅하는 코드가 중복

 

데코레이터로 해결

function startAndEnd(originalMethod: any, context : any) {
    function replacementMethod(this: any, ...args: any[]) {
        console.log('start');
        const result = originalMethod.call(this , ...args);
        console.log('end');
        return result;
    }
    return replacementMethod;
}


class A {
    @startAndEnd
    eat() {
        console.log('eat');
    }

    @startAndEnd
    work() {
        console.log('work');
    }

    @startAndEnd
    sleap() {
        console.log('sleep');
    }
}
  • originalMethod : eat, work, sleap 같은 기존 메소드
    • 이 메서드가 replacementMethod(대체 메서드) 로 바뀜
  • replacementMethod에 따라 기존 메서드의 호출 전후로 start, end가 로깅됨

 

위 레코레이터를 더 제대로 타이핑하면

function startAndEnd<This, Args extends any[], Return>(
    originalMethod: (this : This, ...args : Args) => Return,
    context : ClassMethodDecoratorContext<This, (this: This, ...args : Args) => Return>) {
    function replacementMethod(this: This, ...args: Args) {
        console.log('start');
        const result = originalMethod.call(this , ...args);
        console.log('end');
        return result;
    }
    return replacementMethod;
}
  • This, Args, Return 타입 매개변수 : 기존 메서드의 this, 매개변수, 반환값
    • 그대로 대체 메서드에 적용
  • context : 데코레이터의 정보를 갖고 있는 매개변수
    • startAndEnd 데코레이터는 클래스 메서드를 장식 -> context는 ClassMethodDecoratorContext가 됨

 

context는 다양한 종류가 있는데, 어떤 문법을 장식하냐에 따라 context의 타입을 교체하면 됨

 

 

 

 

2.32 앰비언트 선언도 선언 병합이 된다

: 타입스크립트에서 남의 라이브러리를 사용할 경우, 그 라이브러리가 js라면 직접 타이핑해야 함

: 앰비언트 선언 = 이 경우 사용하는 것

: declare 예약어 사용

 

declare namespace NS {
    const v : string;
};
declare enum Enum {
    ADMIN = 1
}
declare function func(parma : number) : string;
declare const variable :  number;
declare class C {
    constructor(p1: string, p2 : string);
};

new C (func (variable), NS.v);
  • 코드 구현부 X
    • func, class C, 변수 variable : 타입만 존재
  • 그럼에도 new C (func (variable), NS.v)와 같이 값으로 사용 가능
    • 외부 파일에 실제 값이 존재한다고 믿음
    • 실행 시 외부 값에 값이 없으면 에러
  • namespace와 enum 을 declare로 선언하는 이유
    • 내부 멤버의 구현 생략 가능
    • enum을 declare하면 js로 변환할때 실제 코드가 생성되지 않음
      • declare 쓰는 경우 이미 다른 곳에 실제 값이 있다고 생각하기 떄문

 

인터페이스와 타입 별칭도 declare로 선언 가능

declare interface Int {}
declare type T = number;
  • 하지만 인터페이스와 타입 별칭은 declare로 선언하지 않아도 동일하게 작용 -> 굳이 사용 X

 

타입스크립트에서 선언이 생성하는 개체

유형 네임스페이스 타입
네임스페이스 O   O
클래스   O O
enum   O O
인터페이스   O  
타입 별칭   O  
함수     O
변수     O
  • 타입 선언 시 사용될 수 있는 용도 정리

 

같은 이름으로 다른 선언과 병합가능 여부

병합 가능 여부 네임스페이스 클래스 enum 인터페이스 타입 별칭 함수 변수
네임스페이스 O O O O O O O
클래스 O X X O X O X
enum O X O X X X X
인터페이스 O O X O X O O
타입 별칭 O X X X X O O
함수 O O X O O O X
변수 O X X O O X X
  • 인터페이스, 네임스페이스 병합이나 함수 오버로딩을 제외하고는 같은 이름으로 선언은 피하기

 

선언 병합을 활용하면 좋은 경우

declare class A {
    constructor(name : string);
}

function A(name :string) {
    return new A(name);
}

new A('zerocho');
A('zerocho');
  • 클래스가 있을 때 new를 붙이지 않아도 되는 코드
    • class A : 앰비언트 선언
    • function A : 일반 선언
  • 클래스와 함수는 병합됨 (표 참고)
    • declare로 앰비언트 선언한 타입과 일반 선언한 타입끼리도 병합됨

 

 

선언 병합의 다른 경우

function Ex() {
    return 'hello';
}

namespace Ex{
    export const a = 'world';
    export type B = number;
}

Ex();   //hello
Ex.a;    //world
const b : Ex.B = 123;
  • js의 함수는 객체 -> 속성 추가 가능
  • 함수와 네임스페이스는 병합 가능해 위의 코드 에러 X

'typescript' 카테고리의 다른 글

lib.es5.d.ts 분석하기 - 3.7 ~ 3.11  (0) 2024.11.10
lib.es5.d.ts 분석하기 - 3.1 ~ 3.6  (2) 2024.11.03
2.23 ~ 2.27  (0) 2024.10.13
2.18 ~ 2.22  (1) 2024.10.06
타입스크립트 2.13 ~ 2.17  (0) 2024.09.28