2025-07-20 13:32

Status:

Tags: 타입스크립트 리액트

타입스크립트 치트 시트

타입(Type)

추가 설명

  • 타입(alias)은 코드의 가독성과 재사용성을 높여주며, 복잡한 구조를 간단하게 표현할 수 있습니다.
  • 타입은 유니온, 인터섹션, 매핑, 조건부 등 다양한 조합이 가능하여, 복잡한 데이터 구조를 유연하게 설계할 수 있습니다.
  • 타입은 런타임에 영향을 주지 않으며, 오직 컴파일 타임에만 사용됩니다.
  • 타입스크립트의 타입 시스템은 점진적으로 적용할 수 있어, 기존 JavaScript 코드에 점진적으로 타입을 추가할 수 있습니다.
  • 타입 별칭은 복잡한 타입을 한 번에 정의하고, 여러 곳에서 재사용할 때 매우 유용합니다.
  • 타입스크립트의 타입은 실제 코드 실행에는 영향을 주지 않으므로, 타입 관련 코드는 빌드 시 제거됩니다.

주요 포인트 (Key Points)

  • 전체 이름은 **“type alias”**이며, 타입 리터럴에 이름을 제공하는 데 사용됩니다.
  • 인터페이스보다 더 풍부한 타입 시스템 기능을 지원합니다.

타입 (Type) vs 인터페이스 (Interface)

  • 인터페이스는 객체 모양만 설명할 수 있습니다.
  • 인터페이스는 확장될 수 있으며, 여러 번 선언 가능
  • 인터페이스 비교 검사는 더 빠를 수 있습니다.

타입을 변수처럼 생각하세요.(Thick of Types Like Variables)

  • 다른 스코프에서 동일한 이름의 변수를 생성할 수 있는 것처럼, 타입도 유사한 의미를 가집니다. (Much like how you can create variables with the same name in different scopes, a type has similar semantics.)

유틸리티 타입으로 빌드하세요.

  • TypeScript에는 타입 시스템에서 일반적인 작업을 돕는 많은 전역 타입이 포함되어 있습니다. 사이트에서 확인하세요.

객체 리터럴 구문 (Object Literal Syntax)

type JSONResponse = {
  version: number;  // 필드
  /** In bytes */  // 첨부 문서
  payloadSize: number;  // 
  outOfStock?: boolean; // 선택적 //
  update: (retryTimes: number) => void; // 화살표 함수 필드 //
  update(retryTimes: number): void;     // 함수 //
  (): JSONResponse;                     // 타입은 호출 가능 //
  [key: string]: number;                // 임의 인덱스 허용 //
  new (s: string): JSONResponse;        // new 가능 //
  readonly body: string;                // 읽기 전용 속성 //
}
// 공간 절약을 위해 간결하게 작성(Terser for saving space), 자세한 내용은 Interface Cheat Sheet 참고, 'static'을 제외한 모든 것이 일치합니다.

원시 타입 (Primitive Type)

type SanitizedInput = string;
type MissingNo = 404;

객체 리터럴 타입 (Object Literal Type)

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

튜플 타입 (Tuple Type)

  • 튜플은 특정 인덱스에서 알려진 타입을 가진 특별한 배열입니다.
type Data = [
  location: Location,
  timestamp: string
];

유니온 타입 (Union Type)

  • 여러 옵션 중 하나인 타입을 설명합니다. 예를 들어, 알려진 문자열 목록입니다.
type Size = "small" | "medium" | "large";

교차 타입 (Intersection Types)

  • 타입을 병합/확장하는 방법입니다.
type Location = { x: number } & { y: number };
// { x: number, y: number }

타입 인덱싱 (Type Indexing)

  • 타입에서 하위 집합을 추출하고 이름을 지정하는 방법입니다.
type Response = { data: { ... } };
type Data = Response["data"];
// { ... }

타입을 값에서 추출하기 (Type from Value)

  • 기존 JavaScript 런타임 값에서 typeof 연산자를 사용하여 타입을 재사용합니다.
const data = { ... };
type Data = typeof data;

함수 반환값에서 타입 추출 (Type from Func Return)

  • 함수의 반환 값을 타입으로 재사용합니다.
function createFixtures() { ... }
type Fixtures = ReturnType<typeof createFixtures>;
function test(fixture: Fixtures) {}

모듈에서 타입 추출 (Type from Module)

  • 모듈에서 타입을 재사용합니다.
const data: import("./data").data;

매핑된 타입 (Mapped Types)

  • 타입 시스템의 map 문과 유사하게 작동하여 입력 타입이 새 타입의 구조를 변경할 수 있습니다.
type Artist = { name: string, bio: string };
 
type Subscriber<Type> = {
  // 타입 제네릭 파라미터 "Type"의 각 필드를 순회합니다. (Loop through each field in the type generic parameter “Type”)
  // 원래 타입을 파라미터로 받는 함수 타입으로 설정합니다. (Sets type as a function with original type as param)
  [Property in keyof Type]: (newValue: Type[Property]) => void;
};
type ArtistSub = Subscriber<Artist>;
// { name: (nv: string) => void, 
// bio: (nv: string) => void }

조건부 타입 (Conditional Types)

  • 타입 시스템 내에서 “if 문”처럼 작동합니다. 제네릭을 통해 생성되며, 일반적으로 타입 유니온의 옵션 수를 줄이는 데 사용됩니다.
type HasFourLegs<Animal> = Animal extends { legs: 4 } ? Animal : never;
type Animals = Bird | Dog | Ant | Wolf;
type FourLegs = HasFourLegs<Animals>;
// Dog | Wolf

템플릿 유니온 타입 (Template Union Types)

  • 템플릿 문자열을 사용하여 타입 시스템 내에서 텍스트를 결합하고 조작할 수 있습니다.
type SupportedLangs = "en" | "pt" | "zh";
type FooterLocaleIDs = "header" | "footer";
 
type AllLocaleIDs = `${SupportedLangs}_${FooterLocaleIDs}_id`;
// "en_header_id" | "en_footer_id"
// "pt_header_id" | "pt_footer_id"
// "zh_header_id" | "zh_footer_id"

클래스(Class)

추가 설명

  • 클래스는 객체지향 프로그래밍(OOP)의 핵심 개념으로, 데이터(필드)와 동작(메서드)을 하나의 단위로 묶어줍니다.
  • 타입스크립트 클래스는 JavaScript의 클래스 문법을 확장하여, 타입 안전성과 코드 자동완성 등 개발 생산성을 높여줍니다.
  • 클래스의 private, protected, public 접근 제어자는 코드의 캡슐화(encapsulation)를 강화합니다.
  • 추상 클래스(abstract class)는 공통 동작을 정의하고, 하위 클래스에서 구체적으로 구현하도록 강제합니다.
  • 데코레이터(Decorator)는 클래스와 멤버에 메타데이터를 추가하거나 동작을 확장할 수 있는 고급 기능입니다.
  • 제네릭 클래스는 다양한 타입의 데이터를 처리할 수 있어, 재사용성과 타입 안전성을 동시에 제공합니다.
  • 클래스는 타입과 값의 두 가지 역할을 하므로, 타입스크립트에서 클래스 이름은 타입 선언과 값(생성자 함수) 모두로 동작합니다.

주요 포인트 (Key Points)

  • 타입스크립트 클래스는 ES2015 JavaScript 클래스에 몇 가지 타입 전용 확장을 추가하며, 런타임 추가 사항이 하나 또는 두 개 있습니다.

클래스 생성 (Creating a Class Instance)

  • 클래스 인스턴스를 생성할 때는 new 키워드를 사용합니다.
class ABC {...}
const abc = new ABC();

private vs #private

  • private는 타입 전용 추가이며, 런타임에 영향을 주지 않습니다. 클래스 외부에서 항목에 접근할 수 있습니다.
class Bag {
  private item: any; // 타입 전용 private
}
const a: Bag = new Bag();
a.item; // 접근 가능
  • #private는 런타임에서만 접근 가능한 private로, JavaScript 엔진 내에서 클래스 내부에서만 접근할 수 있도록 강제합니다.
class #Bag {
  #item: any; // 런타임 private
}
const a: Bag = new Bag();
a.#item; // 접근 불가, 오류 발생

‘this’ in Classes

  • 클래스 내 함수에서 this의 값은 함수가 호출되는 방식에 따라 달라집니다. 다른 언어에서 클래스 인스턴스가 항상 this로 사용되는 것과는 다릅니다.
  • this 매개변수, bind 함수, 또는 화살표 함수를 사용하여 문제를 해결할 수 있습니다.
class MyClass {
  constructor(public name: string) {}
  printName = () => {
    console.log(this.name); // 화살표 함수로 'this'를 클래스 인스턴스로 유지
  }
}
const myInstance = new MyClass("Hello");
myInstance.printName(); // "Hello"

Type and Value

  • 클래스는 타입(type)과 값(value)으로 모두 사용할 수 있습니다. 즉, 클래스 이름은 타입 선언과 값(생성자 함수) 모두로 동작합니다.
    • 예시:
const a: Bag = new Bag(); // Bag은 타입(좌측)과 값(우측) 모두로 사용됨
  • 하지만, 클래스와 인터페이스의 차이로 인해 아래와 같은 코드는 피해야 합니다.
class C implements Bag {}
// C는 Bag 인터페이스(혹은 클래스)의 구조만 따르지만, Bag 클래스의 인스턴스는 아님
const bag: Bag = new C(); // 오류 발생: C는 Bag의 인스턴스가 아님
  • 왜 오류? C는 Bag의 구조(예: 메서드나 속성)를 복사하지만, new C()로 만든 객체는 Bag 클래스의 진짜 인스턴스가 아니에요. TypeScript는 인스턴스의 ‘출신’을 확인하거든요. 마치 “이 가방은 Bag 브랜드가 아니야”라고 보는 거죠.
  • 피하는 방법: 만약 Bag을 인터페이스로 사용하려면, 처음부터 interface Bag {}으로 선언하세요. 클래스로 선언했다면 상속(extends)을 고려해보세요.
  • 타입스크립트(Korean: 타입스크립트, English: TypeScript)에서 클래스는 타입과 값의 두 가지 역할을 하므로, 타입과 값의 구분을 명확히 이해해야 합니다.

주요 클래스 문법 (Common Syntax)

  • 클래스 선언, 상속, 인터페이스 구현 등 기본적인 클래스 문법을 사용합니다.
// Account 클래스를 상속(subclass)
// Updatable, Serializable 인터페이스를 구현(implements)
class User extends Account implements Updatable, Serializable {
  id: string; // 필드(멤버 변수)
  displayName?: string; // 선택적 필드
  name!: string; // "무조건 있음"을 컴파일러에 알림(Non-null 단언)
  #attributes: Map<any, any> = new Map(); // #으로 시작하는 필드는 런타임 private(진짜 비공개)
  roles = ["user"]; // 기본값이 있는 필드
  readonly createdAt = new Date(); // 읽기 전용 필드(생성 시 값 고정)
 
  constructor(id: string, email: string) { // 생성자
    super(id); // 부모 클래스 생성자 호출
    this.email = email; // 필드 초기화
    // ...
  }
 
  setName(name: string) { this.name = name; } // 일반 메서드
  verifyName = (name: string) => { /* ... */ }; // 화살표 함수 메서드(필드로 선언)
 
  // 오버로드(Overload) 예시: 같은 이름, 다른 시그니처
  sync(): Promise<{ ... }>;
  sync(cb: ((result: string) => void)): void;
  sync(cb?: ((result: string) => void)): void | Promise<{ ... }> {
    if (cb) {
      // 콜백 방식
    } else {
      return new Promise((resolve) => {
        // 프로미스 방식
      });
    }
  
 
  // Getter/Setter
  get accountID() { /* ... */ }
  set accountID(value: string) { /* ... */ }
 
  private makeRequest() { /* ... */ } // private: 클래스 내부에서만 접근 가능
  protected handleRequest() { /* ... */ } // protected: 자신과 하위 클래스에서만 접근 가능
 
  // static(정적) 필드/메서드: 클래스 자체에 속함, 인스턴스가 아닌 클래스명으로 접근
  static #userCount = 0; // 정적 private 필드
  static registerUser(user: User) { /* ... */ }
 
  static {
    this.#userCount = -1; // static 블록: 정적 필드 초기화 등
  }
}

제네릭 (Generics)

  • 클래스에서 제네릭을 사용하여 타입을 동적으로 정의할 수 있습니다.
class Box<Type> {
  contents: Type; // 제네릭 타입 필드
  constructor(value: Type) {
    this.contents = value; // 생성자에서 제네릭 타입 초기화
  }
}
const stringBox = new Box<string>("Hello"); // string 타입 박스
const numberBox = new Box<number>(42); // number 타입 박스

파라미터 프로퍼티 (Parameter Properties)

  • 생성자 매개변수에 public, private, protected 키워드를 사용하여 자동으로 클래스 필드를 생성할 수 있습니다.
class Location {
  constructor(public x: number, public y: number) {} // x, y 필드
}
const loc = new Location(20, 40);
console.log(loc.x); // 20
console.log(loc.y); // 40

추상 클래스 (Abstract Classes)

  • 추상 클래스는 인스턴스를 만들 수 없지만, 하위 클래스에서 구현해야 하는 메서드를 정의할 수 있습니다.
abstract class Animal {
  abstract getName(): string; // 추상 메서드
  printName() {
    console.log("Hello, " + this.getName()); // 추상 메서드 호출
  }
}
class Dog extends Animal {
  getName(): string { 
    return "Dog"; // 추상 메서드 구현
  }
}
const dog = new Dog();
dog.printName(); // "Hello, Dog"

데코레이터와 속성 (Decorators and Attributes)

  • 클래스, 메서드, 접근자, 속성 및 메서드 매개변수에 데코레이터를 사용할 수 있습니다.
import { Syncable, triggersSync, preferCache, required } from "mylib";
 
@Syncable
class User {
  @triggersSync()
  save() { /* ... */ }
  @preferCache(false)
  get displayName() { /* ... */ }
  @required
  update(@info: Partial<User>) { /* ... */ } // 매개변수 데코레이터
}

인터페이스(Interface)

추가 설명

  • 인터페이스는 객체의 구조(Shape)를 정의하며, 클래스, 객체, 함수 등 다양한 곳에 적용할 수 있습니다.
  • 인터페이스는 여러 번 선언하면 자동으로 병합(Merging)되어, 확장성과 유연성이 뛰어납니다.
  • 인터페이스는 선택적 속성, 읽기 전용 속성, 인덱스 시그니처 등 다양한 문법을 지원합니다.
  • 클래스는 implements 키워드로 인터페이스를 구현(Conformance)할 수 있습니다.
  • 인터페이스는 타입 시스템에서 계약(Contract) 역할을 하여, 코드의 일관성과 유지보수성을 높여줍니다.
  • 인터페이스는 타입 별칭과 달리, 선언적 확장이 가능하여 여러 라이브러리나 모듈에서 구조를 쉽게 합칠 수 있습니다.

주요 포인트 (Key Points)

  • 객체의 모양을 설명하는 데 사용되며, 다른 인터페이스나 타입으로 확장될 수 있습니다.
  • JavaScript의 런타임 동작에 맞게 설계되어 있습니다. JavaScript의 거의 모든 것은 객체이며, 인터페이스는 이러한 런타임 동작에 맞게 설계되었습니다.
built-in 타입 프리미티브 (Built-in Type Primitives)
  • boolean, string, number, undefined, null, any, unknown, never, void, bigint, symbol
일반적인 내장 JS 객체 (Common Built-in JS Objects)
  • Date, Error, Array, Map, Set, Regexp, Promise
타입 리터럴 (Type Literals)
  • 객체: { field: string }
  • 함수: (arg: number) => string
  • 배열: string[] 또는 Array<string>
  • 튜플: [string, number]
피해야 할 것들 (Avoid)
  • Object, String, Number, Boolean (이들은 일반적으로 사용하지 않는 것이 좋습니다. 대신, 구체적인 타입을 사용하세요.)
일반 구문 (Common Syntax)
interface JSONResponse extends Response, HTTPAble {
  version: number; // 필드
  
  /** In bytes */ // JSDoc 주석, 에디터에 표시됨
  payloadSize: number; // 바이트 단위 페이로드 크기
  outOfStock?: boolean; // 이 속성은 객체에 없을 수도 있음
  
  // 함수 속성, 두 가지 방법으로 설명 가능
  update: (retryTimes: number) => void; 
  update: (retryTimes: number): void; 
  
  (): JSONResponse; // 이 객체를 ()로 호출할 수 있음
 
  new (s: string): JSONResponse; // 이 인터페이스가 설명하는 객체를 new로 사용할 수 있음
 
  [key: string]: number; // 이 인터페이스에 설명되지 않은 모든 속성은 숫자여야 함
 
  readonly body: string; // 이 속성은 변경할 수 없음을 TypeScript에 알림
}
제네릭 (Generics)
  • 타입 매개변수를 사용하여 인터페이스에서 변경 가능한 타입을 선언할 수 있습니다.
interface APICall<Response> {
  data: Response; // 제네릭 타입 필드
}
// 사용 예시
const api: APICall<ArtworkCall> = { data: ... };
api.data; // ArtworkCall 타입 데이터 접근
  • 제네릭 매개변수에 extends 키워드를 사용하여 어떤 타입이 허용되는지 제약을 설정할 수 있습니다.
interface APICall<Response extends { status: number }> {
  data: Response; // status 속성이 있는 타입만 허용
}
const api: APICall<ArtworkCall> = { data: { status: 200, ... } };
api.data.status; // status 속성에 접근
오버로드 (Overloads)
  • 호출 가능한 인터페이스는 서로 다른 매개변수 세트에 대해 여러 정의를 가질 수 있습니다.
interface Expect {
  (matcher: boolean): string; // boolean 매처에 대한 정의
  (matcher: string): boolean; // 문자열 매처에 대한 정의
}
Get & Set
  • 객체는 사용자 정의 getter 또는 setter를 가질 수 있습니다.
interface Ruler {
  get size(): number; // 크기 getter
  set size(value: number | string); // 크기 setter, 숫자 또는 문자열 허용
}
// 사용 예시
const r: Ruler = { size: 12 }; // 크기 설정
r.size = "36"; // 문자열로 크기 설정
확장(Extension) via Merging
  • 인터페이스는 병합되므로, 여러 선언이 타입 정의에 새 필드를 추가합니다.
interface APICall<Response> {
  data: Response; // 기본 데이터 필드
}
interface APICall<Response> {
  error?: Error; // 선택적 오류 필드 추가
}
클래스 일치 (Class Conformance)
  • 클래스가 인터페이스를 준수하는지 확인할 수 있습니다.
interface Syncable {
  sync(): void; // 동기화 메서드
}
class Account implements Syncable {
  sync() {
    // 동기화 구현
  }
}

제어 흐름 분석(Control Flow Analysis)

추가 설명

  • 제어 흐름 분석(CFA)은 코드의 실행 경로에 따라 변수의 타입을 자동으로 좁혀주는 기능입니다.
  • if, switch, typeof, instanceof, in, 사용자 정의 타입 가드 등 다양한 방법으로 타입을 좁힐 수 있습니다.
  • 타입 가드(Type Guard)와 어설션 함수(Assertion Function)는 복잡한 유니온 타입을 안전하게 분기 처리할 때 유용합니다.
  • as const를 사용하면 객체와 배열의 모든 속성을 리터럴 타입으로 고정할 수 있습니다.
  • 제어 흐름 분석은 타입스크립트의 강력한 타입 추론(Type Inference) 기능의 핵심입니다.
  • 타입스크립트는 코드의 흐름을 분석하여, 변수의 타입을 상황에 따라 자동으로 좁혀주기 때문에, 안전하고 직관적인 코딩이 가능합니다.

주요 포인트 (Key Points)

  • CFA(제어 흐름 분석)는 거의 항상 유니온 타입을 받아 코드의 논리에 따라 유니온 내부의 타입 수를 줄여줍니다.
  • 대부분의 경우 CFA는 자연스러운 JavaScript 불리언 논리 내에서 동작하지만, TypeScript가 타입을 좁히는 방식에 영향을 주는 사용자 정의 함수를 만들 수도 있습니다.

If Statements

타입이 좁혀지는 대부분의 경우는 if 문 내부의 표현식에서 발생하며, 다양한 타입 연산자가 새로운 스코프에서 타입을 좁혀줍니다.

// - `typeof` (원시 타입에 대해)
const input = getUserInput();
input // string | number
if (typeof input === "string") {
  input; // string
}
 
// - `instanceof` (클래스에 대해),
const input = getUserInput();
input // number | number[]
if (input instanceof Array) {
  input; // number[]
}
 
// - "property" in object (객체에 대해)
const input = getUserInput();
input // string | { error: ... }
if ("error" in input) {
  input; // { error: ... }
}
 
// - type-guard 함수(for anything)
const input = getUserInput();
input // number | number[]
 
if (Array.isArray(input)) {
  input; // number[]
}
 

Expressions

  • 타입 좁히기는 코드와 함께 발생하며, 불리언 연산을 수행할 때도 좁혀집니다.
const input = getUserInput();
input // string | number
const inputLength = (typeof input === "string" && input.length) || input; // input: string

Discriminated Unions

  • 모든 유니온 멤버가 동일한 속성 이름을 가지고 있다면, CFA는 해당 속성을 기준으로 타입을 구분할 수 있습니다.
type Responses =
  | { status: 200, data: any }
  | { status: 301, to: string }
  | { status: 400, error: Error }; 
  • 사용법
const response = getResponse();
response // Responses
 
switch(response.status) {
  case 200: return response.data; // data
  case 301: return redirect(response.to); // to
  case 400: return response.error; // error
}

타입 가드(Type Guards)

  • 타입 가드는 특정 타입인지 확인하는 함수로, TypeScript가 해당 타입으로 좁힐 수 있도록 도와줍니다.
function isErrorResponse(obj: Response): obj is APIErrorResponse {
  return obj instanceof APIErrorResponse; // APIErrorResponse 타입인지 확인
}
  • 사용법
const response = getResponse();
response // Response | APIErrorResponse
if (isErrorResponse(response)) {
  response; // APIErrorResponse
}

Assertion Functions

  • 어설션 함수는 현재 스코프를 변경하거나 예외를 던지는 함수로, TypeScript가 타입을 좁히는 데 사용됩니다.
function assertResponse(obj: any): asserts obj is SuccessResponse {
  if (!(obj instanceof SuccessResponse)) {
    throw new Error("Not a success!"); // 예외 발생
  }
}
  • 사용법
const response = getResponse();
response // SuccessResponse | ErrorResponse
assertResponse(response); // 어설션 함수는 현재 스코프를 변경하거나 예외를 발생시킵니다.
response; // SuccessResponse

Assignment

  • 객체의 하위 필드는 변경 가능한(mutable) 것으로 간주되며, 할당 시 타입이 리터럴(literal) 타입에서 더 넓은(widened) 타입으로 확장됩니다.
  • as const 접두사를 사용하면 모든 타입이 리터럴 타입으로 고정됩니다.
const data1 = { name: "Zagreus" }; // typeof data1 = { name: string }
const data2 = { name: "Zagreus" as const }; // typeof data2 = { name: "Zagreus" }; // 리터럴 타입으로 고정
  • Tracks through related variables
const response = getResponse();
const isSuccessResponse = res instanceof SuccessResponse; // 현재 스코프의 타입 좁히기
if (isSuccessResponse) {
  res.data; // SuccessResponse
}
  • Reassignment updates types
let data: string | number = ...; // 초기 타입
data; // string | number
data = "Hello"; // 재할당 후 타입 업데이트
data; // string

References

https://www.typescriptlang.org/cheatsheets/