Jieunny의 블로그

Section 6. 고급 타입 본문

CodeStates/TS 스터디

Section 6. 고급 타입

Jieunny 2023. 2. 21. 16:29

📣  강의 내용 정리

𝟭.  인터섹션 타입 (AND, OR이 아님!)

✔️ '&' : 두 가지 타입을 결합하는 방법(여러 개의 타입 정의를 하나로 합치기)

type Admin = {
    name: string;
    privileges: string[];
};

type Employee = {
    name: string;
    startDate: Date;
};

type ElevatedEmployee = Admin & Employee;
// 인터섹션 타입

const el: ElevatedEmployee = {
    name: 'Max',
    privileges: ['create-server'],
    startDate: new Date()
}

➰ 인터페이스 상속과 관련이 있다.
➰ 위 코드에서 type을 interface로 바꾸고 ElevatedEmployee 가 위에 두 인터페이스를 extends 하게 해주면 같은 결과가 나온다.
 기존 타입을 대체하지 않으면서 기존 타입에 새로운 필드를 추가하고 싶을 때
   Type A, Type B가 있을 때 A와 B를 인터섹션 하면 A타입이면서 B타입이라는 의미
 
✚ 유니언 타입

interface Person {
  name: string;
  age: number;
}
interface Developer {
  name: string;
  skill: string;
}
function introduce(someone: Person | Developer) {
  someone.name; // O 정상 동작
  someone.age; // X 타입 오류
  someone.skill; // X 타입 오류
}

 
✚ 유니언 타입 + 인터섹션 타입

type Combinable = string | number;
type Numeric = number | boolean;

type Universal = Combinable & Numeric; // number
// number만 겹치기 때문이다.

 

✔️ Intersection →  A & B : A타입이면서 B타입
✔️ Union → A | B : A타입, B 타입 둘 중 하나

 

𝟮.  타입가드에 대한 추가 정보

✔️ 타입가드 : 특정 속성이나 메소드를 사용하기 전에 그것이 존재하는지 확인하거나 타입을 사용하기 전에 이 타입으로 어떤 작업을 수행할 수 있는지를 확인하는 개념 또는 방식
✔️ 유니언 타입을 돕는다.

function add(a: Combinable, b: Combinable) {
    if(typeof a === 'string' || typeof b === 'string') {
    // 타입 가드 구문
        return a.toString() + b.toString();
    }
    return a + b;
}

➰ 유니온 타입이 지닌 유연성을 활용할 수 있게 해주며 런타임 시 코드가 정확하게 작동하게 한다.
➰ 객체의 경우 instanceof 나 in / 다른 타입들은 typeof 를 사용할 수 있다.
 

𝟯.  구별된 유니언

✔️ 구별된 유니언 : 타입가드를 쉽게 구현할 수 있게 해주는 유니언 타입으로 작업을 수행할 때 사용할 수 있는 패턴

➰ 실제 존재하는 속성을 사용해서 어떤 유형의 객체와 작업하고 있는지 알 수 있다.
 
➰ 둘 다 kind 속성(구별 속성)을 가지고 있고, 이 속성에 대해 타입 가드 스타일의 검사(===, !==) 또는 switch를 사용하면 TS가 특정한 리터럴을 가진 객체를 대상으로 한다는 것을 알아차리고 타입 좁히기를 실행해준다

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
type Shape = Square | Rectangle;
function area(s: Shape) {
    if (s.kind === "square") {
        // 이것으로 TypeScript가 `s`가 `Square`임을 알게 됨 ;)
        // 그러므로 `Square`의 멤버를 안전하게 사용할 수 있음 :)
        return s.size * s.size;
    }
    else {
        // `Square`가 아님? 그러면 TypeScript는
        // 이것이 `Rectangle`일 수 밖에 없음을 알게 됨 ;)
        // 그러므로 `Rectangle`의 멤버를 안전하게 사용할 수 있음 :)
        return s.width * s.height;
    }
}

📚 참고 :https://radlohead.gitbook.io/typescript-deep-dive/type-system/discriminated-unions
 

𝟰.  형 변환(Typecasting)

✔️ 형 변환 : 타입스크립트가 직접 감지하지 못하는 특정 타입의 값을 타입스크립트에 알려주는 역할

1️⃣ 변환하고자 하는 요소 앞이나 타입스크립트에 타입을 알려주고자 하는 위치 앞에 무언가를 추가하는 방법

const userInputElement = <HTMLInputElement>document.getElementById('user-input');

➰ <>다음에 오는 것이 무엇이든 타입스크립트는 HTMLInputElement라는 것을 알게 된다.
 
2️⃣ 형 변환하고자 하는 타입 다음에 무언가를 추가하는 방법

const userInputElement = document.getElementById('user-input')! as HTMLInputElement;
// 형 변환의 경우 null이 아님을 알려주는 !를 쓸 수 없다.
// !와 형 변환을 동시에 쓰게 되면 이것이 null이 되지 않는다는 걸 한 번 더 알려주게 되는 셈이다.
const userInputElement = document.getElementById('user-input') as HTMLInputElement;
if(userInputElement) {
    (userInputElement as HTMLInputElement).value = 'Hi there!';
}
// 대신 이런식으로 쓸 수 있다.

 

𝟱.  덱스 속성

✔️ 인덱스 타입 : 속성이 정해져 있지 않고 동적으로 처리해야 할 경우 사용할 수 있다.

interface Props {
    [key: string]: string;
}
// 키를 문자열로 받고, 타입은 string으로 받겠다 라는 의미

➰ 대괄호로 시작해서 키를 정의할 수 있고, 키에 대응하는 타입을 정의할 수 있다.

const p: Props = { 
    a: "d",
    b: "3"
};

p['a'] // 'd'
p[1] // 3

➰ value의 타입을 문자열로 정했기 때문에 숫자를 입력하면 에러가 난다.
📚 참고 : https://velog.io/@yhg0337/chap.8-%ED%83%80%EC%9E%85%EB%B3%84%EC%B9%AD-Index-Type
 

𝟲.  함수 오버로드

✔️ 함수 오버로드 : 동일한 함수에 대해 여러 함수 시그니처를 정의할 수 있는 기능

function add(a: number): number;
function add(a: number, b: number): number);
function add(a: Combinable, b: Combinable) {
     ...
 }

➰ 타입스크립트가 자체적으로 반환 타입을 알 수 없는 경우에 유용하다.
➰ 함수가 지원할 수 있는 다양한 조합에 대해 어떤 것이 반환되는지 명확히 알 수 있다.
 

𝟳.  옵셔널 체이닝

✔️ 옵셔널 체이닝 : 프로퍼티 타입이 null 또는 undefined 인 경우 프로퍼티 명 뒤에  '?'를 추가한다.

interface ICompanyInfo {
  name: string | null | undefined;
}

let obj: ICompanyInfo = {
  name: null
};

console.log(`obj.name?.length : ${obj.name?.length}`);

➰ 이 경우 name의 값이 null이어도 에러가 나지않고 코드가 정상적으로 작동한다.
➰ 참조 값이 nullish(null과 undefined)하다면 에러가 아닌 undefined를 반환한다.

➕'?'를 사용하는 또 다른 경우
✔️ 선택적 프로퍼티
➰ 프로퍼티의 값이 필수가 아닌 경우 사용한다.

interface ICompanyInfo {
  name: string;
  chairman?: string;
}

let obj: ICompanyInfo = {
  name: 'FaceBook'
};

➰ 이 경우, chairman 값을 생략해도 에러가 나지 않는다.
 

𝟴.  Null 병합

✔️ '??' : null 병합 연산자
➰ null 이거나 undefined라면 폴백을 사용해야 한다는 의미
➰ null 이나 undefined를 매끄럽게 처리하는데 도움이 되는 기능
➰ 여러 피연산자 중 그 값이 '확정되어 있는' 변수를 찾을 수 있다.
➰ '??'는 우선순위가 낮으므로 사용할 때는 '()'를 함께 사용하는 게 좋다.

const userInput = undefined;
coonst storedData = userInput ?? 'DEFAULT';

console.log(storedData); // DEFAULT

a ?? b
// a가 null도 아니고 undefined도 아니면 a, 그 외의 경우는 b

 
✔️ '||'와 다른 점

let height = 0;

alert(height || 100); // 100
alert(height ?? 100); // 0

➰ ??는 첫 번째 정의된(defined) 값을 반환한다.


💡  새로 알게 된 점

1. 타입가드, 선택적 체이닝, 널 병합에 대한 내용