본문 바로가기

나만의 모음집

[모음집] 타입스크립트 모음집

반응형

오늘의 명언

 

해당 포스트 목적

타입스크립트와 관련해서 수집한 내용을 모두 정리하기 위한 모음집으로 사용하기 위해 작성되었다. 이는 수정사항이나 추가 내용이 존재하면 새로 갱신되어 업로드  할 예정이다.

 

유니온 타입과 인터섹션 타입

type 키워드를 사용할 때 주로 활용되는 부수적인 타입으로 유니온 타입과 인터섹션 타입이 있다. 이는 자바스립트에서 OR 연산자나 AND 연산자 역할을 하는 각각의 ||, && 과 유사한 역할을 한다. 

 

유니온 타입( A | B) | 둘 중 하나라도 만족하는 경우 타입 검사를 통과 시킴

유니온 타입은 두 가지의 타입이 존재하면 그 중 하나의 타입과 일치하는 경우 타입 검사를 통과시키고자 할 때 사용한다. 즉, 아래와 같이 사용할 수 있다.

type Value = number | string;

const value :Value = 5;
const value :Value = 'fsds';

 

그러나 number 나 string 과 같이 명시된 타입이 아닌 null, undefined 등의 타입 값을 넣으려고 하면 에러가 발생한다.

 

인터섹션 타입( A & B) | 두 타입 모두 만족하는 경우만 타입 검사를 통과 시킴

유니온 타입과 다른게 인터섹션 타입은 지정한 두 타입을 모두 만족해야 검사를 통과할 수 있다. 즉, 아래와 같이 작성하는 경우 두 번째 케이스의 경우에는 name 이 number 타입이므로 에러를 발생시킨다.

type Value = {age:number} & {name: string};

const value :Value = {age: 15, name: 'Jhon'}; // 통과
const value :Value = {age: 15, name: 500}; // 에러

 

여기서 알아두고 갈 점은 인터섹션 타입의 경우 각각 명시한 타입을 서로 병합한다는 점이다. 앞서 type Value = {age:number} & {name: string}; 이렇게 선언한 타입의 경우는 {age: number, name: string} 과 동일하다.

타입스크립트의 내장타입

설명 없음. 맛보기 코드 스니펫

// 기본 타입
let num: number = 10;
let str: string = "Hello";
let bool: boolean = true;
let symbol: symbol = Symbol('uniqueKey');
let n: null = null;
let u: undefined;

// 객체 타입
let person: { name: string; age: number } = { name: 'John Doe', age: 30 };

// 배열 타입
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ['Alice', 'Bob', 'Charlie'];

// 튜플 타입
let point: [number, number] = [10, 20];

// 유니언 타입
let value: number | string = 10; // 또는 'Hello'

// 인터섹션 타입
let user: { name: string; age: number; email: string } & { isAdmin: boolean } = {
  name: 'Jane Doe',
  age: 35,
  email: 'jane.doe@example.com',
  isAdmin: true
};

// 제네릭 타입
function identity<T>(value: T): T {
  return value;
}

let numIdentity = identity(10); // numIdentity는 number 타입
let strIdentity = identity('Hello'); // strIdentity는 string 타입

// 도구 타입
type PartialUser = Partial<{ name: string; age: number; email: string }>; // 일부 속성만 정의 가능한 타입

 

타입 주장(Type Assertion) | 타입 추론이 애해모호한데 개발자가 해당 변수나 값의 타입을 확신할 때 사용( ex. value as string : value 은 string 타입임 확신함!)

타입 주장은  타입스크립트의 추론 기능에 의해 any 타입으로 추론이 되거나, 개발자가 의도하지 않은 타입으로 유추가 되는 경우 개발자가 해당 값이나 변수의 타입을 확신할 때 이를 타입스크립트 컴파일러에게 확신을 주장하는 방법이다.

 

예를 들어, 아래 value 은 any 타입을 가지고 있지만, value 이 string 타입임을 확신할 때 value as string 이라 입력하면 타입스크립트 컴파일러는 해당 value이 string 타입이라고 믿어줄게 하며 넘어가준다.

let value: any = "Hello, TypeScript!";
let length: number = (value as string).length;

 

이 방식의 문제점은 개발자가 확신하지 못하는 경우에도 as 를 남발할 때 타입스크립트는 이 또한 허용한다는 점이다. 그래서 잘못된 방식으로 남용하게 되면 any 타입과 다를바 없으므로 꼭 필요할 때만 사용하는게 좋다.

 

Type 과 Interface 

보통 유형 구분없이 함수, 변수, 튜플, 유니온, 교차, 리터럴 등등 타입을 지정할 때 Type 키워드, 객체나 함수의 타입을 명시할 떄 Interface 를 주로 사용한다. 이 둘은 모두 기능적으로 타입을 지정할 때 사용하지만, 차이점이 존재한다. 

 

Type | 모든 유형에 타입 지정이 가능하지만, 확장이 유연하지 못하다.

어떤 유형이든 타입 지정 가능

우선 type 은  아래와 같이 어떤 유형의 값이든 타입 지정이 가능하다.

type MyEnumType = "A" | "B" | "C";
type MyTupleType = [number, string, boolean];
type MyUnionType = string | number;
type MyLiteralType = "Hello World";

 

extends 를 통한 확장이 불가능하고, & 을 통한 결합은 가능함

하지만, type의 단점은 interface 와 다르게 extends 를 통한 확장이 불가능하다는 점이다. 이것이 불편한 이유는 아래와 같이 MyType2 를 확장하려면 기존 MyType1 에 & (교차 연산자)를 사용하여 별도의 타입을 선언해주어야 한다. 만일 복잡한 구조의 객체라면 지정하기도 까다롭겠지만, 관리나 추가적인 확장도 꺼려질 수도 있다.

type MyType1 = number;
// MyType1을 확장하려면 새로운 type을 선언해야 합니다.
type MyType2 = MyType1 & {
  name: string;
};

 

중복 선언 불가능

또한 type 은 중복으로 타입 선언이 불가능하다. 즉, 아래와 같이 작성한다면 동일한 타입명으로 사용하지 못하게 오류가 발생한다.

type MyType = number;
type MyType = string; // 오류 발생

 

Interface | extends 를 통한 확장이 용이하지만 객체 타입에 한해서만 타입 지정이 가능하다

extends 를 통한 타입 확장이 쉬우나 객체로 취급되는 함수나 객체 리터럴 등에만 적용 가능

inteface 는 type 과 다르게 extends 를 사용해서 타입을 손쉽게 확장할 수 있다. 그러나 타입이 객체로 인정되는 객체 리터럴이나 함수에서만 사용 가능하다는 제한점이 있다.

interface Person {
  name: string;
  age: number;
}

interface Student extends Person {
  studentId: number;
  major: string;
}

 

동일한 이름의 인터페이스가 있으면 서로 병합해줌

그럼에도 interface 의 가장 좋은 점은 type 과는 다르게 동일한 인터페이스명으로 생성하여도 에러가 발생하지 않고, 두 인터페이스 간에 타입을 병합한다는 점이다. 이로 인해, extends 말고도 유연한 타입 확장이 가능하다

interface Point {
  x: number;
  y: number;
}

interface Point { // 이전 선언과 병합됩니다.
  z: number;
}

 

타입 가드 | 런타임에 변수 타입을 확인 후 해당 타입으로 좁히기 위한 방법

아래 예시와 같이 any 타입으로 어떤 유형의 값이든 전달받을 수 있는 value가 있다. 하지만, 함수 내부적으로 별도의 타입을 체크하여 아무 값이나 반환되지 못하도록 막을 수 있으며, 서로 다른 로직을 처리하게 할 수 있다. 이렇게 광범위한 타입을 몇 가지로 줄여나가기 위한 방법 타입가드라고 부른다.

function isNumber(value: any): value is number {
    return typeof value === "number";
}

function process(value: any) {
    if (isNumber(value)) {
        return value * 2;
    } else {
        return "Not a number!";
    }
}

 

타입 별칭 | 기존에 선언된 타입에 새로운 이름을 부여하는 것

아래 코드에서 x는 number 타입을 지정하고, y도 number 타입을 지정하였다. 하지만 이들 타입에  Point 라고 이름을 명명하여 이를 distance 함수의 매개변수에 재사용하고 있는데, 이 때 Point 와 같은 기존 타입의 명칭을 대체하는 것을 타입 별칭이라 한다.

type Point = { x: number; y: number };

function distance(p1: Point, p2: Point): number {
    return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}

 

제네릭 타입 |  타입 정의 없이 동적으로 타입을 지정할 수 있는 타입

함수에서 사용 예시

아래의 경우에는 타입을 명시하지 않아도 전달되는 인자에서 타입을 유추하여 T에 담긴다.

function reverseArray<T>(array: T[]): T[] {
    return array.reverse();
}

const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = reverseArray(numbers); // [5, 4, 3, 2, 1]

const strings = ['apple', 'banana', 'orange'];
const reversedStrings = reverseArray(strings); // ['orange', 'banana', 'apple']

 

명시적으로 타입을 지정하려면 아래와 같이 <> 를 활용한다. 아래 예시에 따르면 <string> 이 <T> 에 전달되어 T 는 string 타입으로 유추된다.

function identity<T>(arg: T): T {
    return arg;
}

let result = identity<string>("Hello, TypeScript!");

 

참고로 제네릭의 경우도 타입 별칭을 적용할 수 있다.

type User<T> = {
  name: T
}

 

함수에서  제네릭 타입의 허용 속성을 제한하려면 | extends

extends 를 통해 타입을 상속해주면 T 는 해당 타입 외에의 값들은 받지 못하게 된다. 예를 들어, 제한된타입이 {age: string} 이라면 T 는 age 가 string 인 경우만 처리할 수 있고, 일치하지 않으면 타입 불일치 에러를 띄운다.

function 함수이름<T extends 제한된타입>(매개변수: T): 반환타입 {
    // 함수 본문
}

 

클래스에서 사용 예시

클래스의 인스턴스를 생성하고, new Box<타입>( ); 을 지정하는 경우 지정한 타입에 해당하는 값만 클래스 내부에 전달할 수 있다. 또한, 아래 예시 처럼 <K, V> 와 같이 여러개의 제네릭 타입 매개변수 지정도 가능하다.

class KeyValuePair<K, V> {
    private key: K;
    private value: V;

    constructor(key: K, value: V) {
        this.key = key;
        this.value = value;
    }

    getKey(): K {
        return this.key;
    }

    getValue(): V {
        return this.value;
    }
}

// 여러 개의 다른 타입을 가진 KeyValuePair 인스턴스 생성
const numberPair = new KeyValuePair<string, number>('age', 30);
const stringPair = new KeyValuePair<string, string>('name', 'John');
const booleanPair = new KeyValuePair<string, boolean>('isValid', true);
const objectPair = new KeyValuePair<string, object>('data', { key: 'value' });

 

유틸리티 타입

Partial<Type> |  정의된 속성을 모두 선택적(옵셔널)으로 만드는 타입

// 모든 속성이 선택적인 Partial<User> 타입
type PartialUser = Partial<User>;

const partialUser: PartialUser = {}; // 유효
const partialUser2: PartialUser = { name: 'John' }; // 유효

 

파셜 타입은 아래와 같이 지정한 것과 같다

interface Car {
    make?: string; // string | undefined
    model?: string; // string | undefined
    year?: number; // number | undefined
}

 

Required<Type> | 타입이 지정한 모든 속성을 필수로( 누락을 허용하지 않는다)

interface Car {
    make?: string;
    model?: string;
    year?: number;
}

// 모든 속성이 필수인 Required<Car> 타입
type RequiredCar = Required<Car>;

const requiredCar: RequiredCar = { make: 'Toyota', model: 'Camry', year: 2024 }; // 유효
const requiredCar2: RequiredCar = { make: 'Honda' }; // 오류: model 및 year가 필요합니다.

Readonly<Type> |  타입이 적용된 모든 속성을 읽기 전용으로 

interface Point {
    x: number;
    y: number;
}

// 모든 속성이 읽기 전용인 Readonly<Point> 타입
type ReadonlyPoint = Readonly<Point>;

const point: ReadonlyPoint = { x: 10, y: 20 };
point.x = 5; // 오류: 읽기 전용입니다.

Record<Keys, Type> | 관련성 있는 키-값 구조를 이루는 데이터를  사전 형태로 시각화하고자 할 때 사용

type Weekday = 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday';

// 각 요일에 대한 정보를 담는 레코드 타입
type WeekdaySchedule = Record<Weekday, string>;

const schedule: WeekdaySchedule = {
    Monday: 'Gym in the evening',
    Tuesday: 'Dinner with friends',
    Wednesday: 'Work late',
    Thursday: 'Movie night',
    Friday: 'TGIF! Party time'
};

Pick<Type, Keys>  |  정의된 타입 중 일부 타입만 선택적으로 사용할 때 적용

interface Person {
    name: string;
    age: number;
    address: string;
}

// 이름과 나이만 선택하는 새로운 타입
type BasicInfo = Pick<Person, 'name' | 'age'>;

const basicInfo: BasicInfo = { name: 'Alice', age: 30 }; // 유효
const basicInfo2: BasicInfo = { name: 'Bob', address: '123 Main St' }; // 오류: address가 없습니다.

 

반응형