ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 타입스크립트 강의 내용 정리 (2)
    et cetera/TIL 2021. 12. 23. 19:48
    반응형

    제네릭(Generic)

    function logText(text: string): string {
    	console.log(text);
    	return text;
    }
    
    function logNumber(num: number): number {
    	console.log(num);
    	return num;
    }
    
    logText('a');
    logNumber(10);

    유사한 함수를 두 개 선언하고 있다.

    이 경우, 코드를 줄일 수 있는 방법은 크게 union 타입을 활용하는 방법과 Generic을 사용하는 방법이 있다.

    // union type을 통한 해결
    function logText(text: string | number) {
    	console.log(text);
    	return text;
    }

    이 방법을 사용하면 함수 내부에서는 string과 number가 공통으로 가지는 속성만 사용할 수 있다.
    logText에서 반환되는 값 역시 string | number 타입이므로 split 같은 것들은 사용 불가능.

    // Generic을 통한 해결
    function logText<T>(text: T): T {
    	console.log(text);
    	return text;
    }
    
    // 인터페이스에 제네릭 선언하기
    interface DropdownItem<T> {
    	value: T;
    	selected: boolean;
    }

    인터페이스에 제네릭을 사용해서 선언하면 타입 코드를 줄일 수 있다는 장점이 있다.

     

    제네릭(Generic)의 타입 제한

    function logTextLength<T>(text: T): T {
    	console.log(text.length); // 에러
    	return text;
    }
    logTextLength('hi');

    타입스크립트 입장에서는 T에 어떤 타입이 들어올지 모르기 때문에 text가 length 속성을 가지고있는지 알 수가 없다.

    function logTextLength<T>(text: T[]): T[] {
    	console.log(text.length);
    	text.forEach(function(text) {
    		console.log(text);
    	});
    	return text;
    }
    logTextLength<string>(['hi', 'abc']);

    length 속성을 가지고 있을거라는 힌트를 배열을 통해 줄 수 있다.

     

    제네릭(Generic)의 타입 제한 2 - 정의된 타입 활용하기(확장)

    interface LengthType {
    	length: number;
    }
    
    function logTextLength<T extends LengthType>(text: T): T {
    	console.log(text.length);
    	return text;
    }

    요 제네릭은 LengthType의 하위 타입일 것이라고 제한해주는 것.
    (해당 제네릭에 들어올 타입은 length 속성을 무조건 가져야 한다)

     

    제네릭(Generic)의 타입 제한 3 - keyof

    interface ShoppingItem {
    	name: string;
    	price: number;
    	stock: number;
    }
    
    function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
    	return itemOption;
    }
    
    getShoppingItemOption('name');

    'ShoppingItem에 있는 속성 key들 중에 하나가 인자가 된다' 라는 의미.
    즉, T에는 name, price, stock를 포함하는 타입이 들어간다.

    매개변수가 ShoppingItem 인터페이스의 속성들만 가질 수 있게 제약하는 방법.

     

    Promise의 타입

    Promise의 타입은 기본적으로 제네릭을 사용하여 정의되어 있다.
    Promise가 fulfilled 되었을 때 반환될 값의 타입을 제네릭에다가 채워주면 된다.

    function fetchItems(): Promise<string[]> {
    	const items = ['a', 'b', 'c'];
    	return new Promise(function (resolve) {
    		resolve(items);
    	});
    }
    
    fetchItems();

    enum의 값들 중에서 하나를 갖게 하는 타입

    enum PhoneType {
    	Home = 'home',
    	Office = 'office',
    	Studio = 'studio',
    }
    
    findContactByPhone(phoneNumber: number, phoneType: PhoneType): Contact[] {
    	return this.contacts.filter(
    		contact => contact.phones[phoneType].num === phoneNumber
    	);
    }

    phoneType 인자에는 PhoneType.Home / PhoneType.Office / PhoneType.Studio 중 하나만 들어갈 수 있다.

     

    제네릭의 타입 추론

    interface Dropdown<T> {
    	value: T;
    	title: string;
    }
    interface DetailedDropdown<K> extends Dropdown<K> {
    	description: string;
    	tag: K;
    }
    
    const detailedItem: DetailedDropdown<number> = {
    	title: 'abc',
    	description: 'ab',
    	value: 10, // number로 추론
    	tag: 2 // number로 추론
    }

    DOM API의 타입 단언

    const div = document.querySelector('div');

    코드가 실행되는 시점에 이 DOM 이 존재하는지 확신하지 못한다.
    그래서 타입이 HTMLDivElement | null 이 됨.
    여기서 if문을 통해 타입을 좁혀주거나(null을 걸러주거나) 타입 단언을 사용해주면 된다.

    // if문
    const div = document.querySelector('div');
    if (div) {
    	console.log(div.innerText);
    }
    
    // 타입 단언
    const div = document.querySelector('div') as HTMLDivElement;
    console.log(div.innerText);

     타입 가드 함수

    interface Developer {
    	name: string;
    	skill: string;
    }
    
    interface Person {
    	name: string;
    	age: number;
    }
    
    function isDeveloper(target: Developer | Person): target is Developer {
    	return (target as Developer).skill !== undefined;
    }

    is 키워드를 사용해서 target 인자가 Developer인지 아닌지를 boolean 값으로 반환하는 함수

    원래 target은 Developer | Person이므로, 함수 내부에서는 둘의 공통 속성인 name만 사용할 수 있는데
    타입 가드를 통해서 if문으로 분기를 쳐주면 isDeveloper가 true일 때는 내부에서 skill을 사용할 수 있고
    isDeveloper가 false일 때는 그 내부에서 age를 사용할 수 있게된다.

    if (isDeveloper(someone)) {
    	console.log(tony.skill);
    } else {
    	console.log(tony.age);
    }

    타입 호환

    구조적 타이핑과 연관되는 개념.
    속성을 더 많이 가질수록 더 작은 타입이고, 속성을 더 적게 가질수록 더 큰 타입이다.
    구조적으로 더 작은 타입은 더 큰 타입을 지원하지 않는다.
    즉, 더 작은 타입의 변수에 더 큰 타입의 변수를 할당할 수 없다.

    interface Developer {
    	name: string;
    	skill: string;
    }
    
    interface Person {
    	name: string;
    }
    
    let developer: Developer;
    let person: Person;
    developer = person; // X
    person = developer; // O
    interface Developer {
    	name: string;
    	skill :string;
    }
    
    class Person {
    	name: string;
    	skill: string;
    }
    let developer: Developer;
    let person: Person;
    developer = new Person();

    클래스 타입끼리 비교 시에, static member나 constructor는 제외하고 속성만 비교한다.

    class Developer {
      name: string;
      constructor(name: string, skill: string) { }
    }
    
    class Person {
      name: string;
      constructor(age: number) { }
    }
    
    let developer: Developer;
    let person: Person;
    
    developer = person;  // O
    person = developer;  // O

    함수의 타입 호환

    let add = function(a: number) {
    	// ...
    }
    
    let sum = function(a: number, b: number) {
    	// ...
    }
    
    sum = add; // O
    add = sum; // X

     

    반응형

    댓글

Designed by Tistory.