타입스크립트 정리

typescript

작성일 : 2024.08.17

타입스크립트를 사용한 지 1년 정도가 되었는데 그동안 작성했던 타입스크립트 정리 글에 잘못된 부분들이 많아 이 게시글을 작성하며 다시 정리해 보려고 합니다.

TypeScript 란?

타입스크립트는 자바스크립트를 기반으로 한 정적 타입 언어입니다. 컴파일 시점에서 타입 오류를 확인할 수 있고 타입을 명시해 유지보수하기 쉽고 미리 오류를 잡을 수 있도록 도와줍니다.

타입

기본 타입

문자열(String): string
숫자(Number): number
불리언(Boolean): boolean
Null: null
Undefined: undefined
Symbol: symbol
BigInt: bigint

Array

배열은 타입[] Array<타입> 두가지 방법으로 선언할 수 있으며 | 유니언(Union)을 사용하면 여려개의 타입을 가질 수 있습니다.

let numbers: number[] = [1, 2, 3]; // 숫자만 가질 수 있습니다.

let strings: Array<string> = ["a", "b", "c"]; // 문자만 허용되는 배열 입니다.

let array: (string | number)[] = [1, 2, "A"]; // 숫자와 문자가 허용됩니다.

Tuple

튜플은 고정된 길이와 순서를 가지는 배열 입니다.

let user: [string, number] = ["Alice", 25];

Enum

열거형은 관련된 타입도 정의한 수 있습니다.

enum Direction {
    Up = 1,
    Down, // 2
    Left, // 3
    Right // 4
}
let move: Direction = Direction.Up; // 1

Object

객체의 구조에 대해 타입을 정의할 수 있습니다.

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

let user: User = {
    id: 1,
    name: "Alice",
    isActive: true,
};

Function

함수의 매개변수와 반환값에 대한 타입을 지정할 수 있습니다.

function add(a: number, b: number): number {
  return a + b;
}

const subtract = (a: number, b: number): number => {
  return a - b;
}

add(3, 5) // 8
add(3, "5") // Argument of type 'string' is not assignable to parameter of type 'number'.ts(2345)

any, unknown, void, never

any: 모든 타입을 허용합니다.
unknown: any와 같이 모든 타입에 할당할 수 있지만 타입 단언이 필요합니다.
void: 비어있다는 의미로 반환값이 없는 함수에 사용할 수 있습니다.
never: 절대 발생하지 않는 값을 의미하며 주로 오류를 던지는 함수등에 사용하며 값이 비어있다는 `void`와 혼동하면 안됩니다.

Generic

제네릭 타입은 타입을 매개변수처럼 받아 사용할 수 있습니다.

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

let output = identity<string>("Hello");

타입추론

타입스크립트는 명시적은 타입선언을 하지 않아도 타입에 대한 정보를 제공합니다.

let text = "text" 

text = 1 //Type 'number' is not assignable to type 'string'.ts(2322)

이 타입추론은 제네릭 타입에도 적용할 수 있습니다.

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

let num = Box(10);  // num은 number 타입
let str = Box("Hello, TypeScript!");  // str은 string 타입

타입 선언 방법

타입을 선언하는 방법으론 typeInterface가 있습니다.

type

변수처럼 type 키워드를 이용해 타입을 선언할 수 있습니다.

type IdType = number
type NameType = string
type UserType = {
  id: IdType;
  name: NameType;
  info: string[]
}

const user: UserType = {
  id: 1234;
  name: "김지훈"
  info: ["안", "녕"]
}

Interface

인터페이스는 객체의 타입을 정의하는데 큰 도움이 되며 readonly을 이용해 읽기 전용 속성을 정의하거나 extends를 이용해 확장할 수 도 있습니다.

interface User {
  name: string;
  age: number;
  isAdmin: boolean;

}

const user: User = {
  name: "John",
  age: 30,
  isAdmin: true,
};

  interface User {
    readonly id: number; // 읽기 전용 속성은 수정할 수 없습니다.
    name: string;
  }

  const user: User = { id: 1, name: "Charlie" };
  user.id = 3; // Cannot assign to 'id' because it is a read-only property.ts(2540)
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

const myDog: Dog = {
  name: "Buddy",
  breed: "Golden Retriever",
};

타입 연산자

keyof

key of는 인덱싱 가능한 타입의 속성 이름들을 유니온 타입으로 적용시킵니다.

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

type UserKeys = keyof User; // "id" | "name" | "age"

as

as를 사용하면 컴파일러에 명시적으로 타입을 알려줍니다.

const value: unknown = "Hello, TypeScript";
const str = value as string; // unknown → string으로 단언

function add(a: string | number, isNumber: boolean) {
  if (isNumber) {
    // Property 'toFixed' does not exist on type 'string | number'.
    // Property 'toFixed' does not exist on type 'string'.ts(2339)
    a.toFixed(2);

    (a as number).toFixed(2) // 이상 없음
  }
}

is

반환 타입을 좁혀주는 역할을 합니다.

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

const input: unknown = 123;
if (isNumber(input)) {
  console.log(input.toFixed(2)); // input이 number로 타입이 좁혀짐
}

in

특정 속성이 있는지 확인합니다.

type Cat = { meow: () => void };
type Dog = { bark: () => void };

function makeSound(animal: Cat | Dog) {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}

extends

인터페이스나 제네릭 타입에 확장할 수 있습니다.

function logLength<T extends { length: number }>(value: T): void { // length속성이 있는 변수만 허용함
  console.log(value.length);
}

logLength("Hello"); // string은 length 속성이 있음
logLength([1, 2, 3]); // 배열도 length 속성이 있음

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

interface Dog extends Animal {
  breed: string;
}

const myDog: Dog = {
  name: "Buddy",
  age: 3,
  breed: "Golden Retriever",
};

infer

조건부 타입과 함께 사용되어 타입을 추론합니다.

type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<string[]>;  // string
type B = ElementType<number>;    // number
type C = ElementType<boolean[]>; // boolean

& 와 |

&and를 의미합니다. |or을 의미합니다

type Name = { name: string };
type Age = { age: number };
type Person = Name & Age;

const person: Person = { name: "Alice", age: 30 };

type Status = "success" | "error" | "pending";

function handleStatus(status: Status) {
  if (status === "success") {
    console.log("Success!");
  } else if (status === "error") {
    console.log("Error!");
  }
}

? 옵셔널 프로퍼티

객체 타입에서 필수 속성이 아닌 선택적으로 지정합니다.

interface User {
  name: string;
  age?: number; // 옵셔널 프로퍼티
}

// age?는 number | undefined 으로 해석됩니다.

마치며

여기까지 타입스크립트의 문법과 타입의 종류에 대해서 정리했는데
다음 주에는 타입스크립트의 유틸리티 타입에 대해 정리해 보도록 하겠습니다.

타입스크립트 공식문서