이펙티브 타입스크립트: 7장 요약 및 핵심 정리

2025. 2. 3. 18:55Programming Language/Typescript

반응형

이번 글에서는 이펙티브 타입스크립트 7장의 내용을 정리합니다. 이번 장에서는 실제 코드를 작성 중에 겪을 수 있는 문제들을 주로 다룹니다.

1. 타입스크립트 기능보다는 ECMAScript 기능을 사용하기

타입스크립트는 자체적인 기능을 제공하지만, ECMAScript 표준을 따르는 것이 더 나은 경우가 많습니다. 특히 다음과 같은 경우 ECMAScript의 기능을 사용하는 것이 좋습니다.

  • 열거형(enum) 대신 리터럴 타입 유니온 사용

타입스크립트의 enum은 런타임에도 유지되는 값이므로, 사용하지 않는 것이 좋습니다. 대신 리터럴 타입 유니온을 사용하는 것이 더욱 안전합니다.

enum FlavorEnum {
  VANILLA = "VANILLA",
  CHOCOLATE = "CHOCOLATE",
  STRAWBERRY = "STRAWBERRY"
}

let enumFlavor: FlavorEnum = FlavorEnum.CHOCOLATE;

위의 코드는 런타임에도 존재하는 객체를 생성하지만, 리터럴 타입 유니온을 사용하면 컴파일 타임에서만 체크되므로 불필요한 코드가 줄어듭니다.

type Flavor = "VANILLA" | "CHOCOLATE" | "STRAWBERRY";
let flavor: Flavor = "CHOCOLATE";
  • 매개변수 속성 사용 지양

타입 스크립트에서는 클래스를 초기화할 때 더 간결한 문법을 제공합니다. 이를 "매개변수 속성" 이라고 불립니다. 

class Person {
  constructor(public name: string) {}
}

하지만 이 방식은 기존 타입스크립트 컴파일 방식과 반대로 오히려 코드가 늘어나게 되는 현상을 만듭니다. 물론 작성할 코드가 줄어들어 가독성 면에서는 훌륭할 수 있으나, 타입 스크립트의 다른 패턴들과 이질적입니다.
더 최악의 경우는 "매개변수 속성" 과 "일반 속성" 을 같이 사용하는 경우입니다. 

(하지만 저는 매개변수 속성을 선호합니다.)

  • 네임스페이스와 트리플 슬래시 임포트 대신 import/export사용

타입스크립트에서는 네임스페이스(namespace)와 삼중 슬래시(/// <reference path="..." />)를 사용하여 모듈을 관리할 수 있습니다. 하지만 최신 ECMAScript 모듈 시스템(import/export)을 활용하는 것이 더 표준적이고, 유지보수 및 코드 가독성 측면에서 유리합니다.

// 모듈 파일 (math.ts)
export function add(a: number, b: number): number {
  return a + b;
}

// 임포트 파일 (main.ts)
import { add } from "./math";

console.log(add(2, 3));

위와 같이 importexport를 사용하면 모듈을 보다 쉽게 관리할 수 있으며, 트리 쉐이킹(tree shaking) 최적화도 가능합니다.

  • 데코레이터 사용 시 주의 (실험적인 기능이므로 안정화 전까지 사용 지양)

타입스크립트의 데코레이터는 아직 ECMAScript의 공식 표준이 아니며, 실험적인 기능으로 제공됩니다. 일부 프레임워크(예: Angular)에서는 유용하게 사용되지만, 런타임에서의 예상치 못한 동작과 복잡한 디버깅 이슈를 초래할 수 있습니다.

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Method ${propertyKey} was called with arguments:`, args);
    return originalMethod.apply(this, args);
  };
  return descriptor;
}

class Example {
  @Log
  sayHello(name: string) {
    return `Hello, ${name}!`;
  }
}

const example = new Example();
console.log(example.sayHello("Alice"));

데코레이터의 문제점

  • 표준화되지 않은 기능: 향후 ECMAScript의 공식 사양에서 변경될 가능성이 큼.
  • 런타임 오버헤드: 코드 실행 중 동적으로 메서드를 변경하므로 성능 이슈 발생 가능.
  • 디버깅 난이도 증가: 코드 흐름이 복잡해지고 예상치 못한 동작을 초래할 수 있음.
  • 트랜스파일러 의존: experimentalDecorators 옵션을 활성화해야 하며, Babel 또는 TypeScript 설정이 필요함.

따라서, 데코레이터 사용을 고려할 때는 이러한 문제점을 염두에 두고 신중하게 접근해야 합니다.

2. 객체를 순회하는 노하우

객체를 순회할 때는 for...in 루프보다 Object.keys(), Object.entries() 등을 활용하는 것이 더 안전합니다.

for...in 을 사용하다보면 순회 값을 할당 받는 변수가 객체의 키값보다 더 넓은 범위의 타입을 가질 수 있습니다.

const obj = { name: "Alice", age: 25 };

for (const key in obj) {
  const v = obj[key] // key 의 타입은 string 으로 추론되어 에러발생
}
  • 객체 키를 안전하게 접근하기 위해 keyof 활용
interface Person {
  name: string;
  age: number;
}

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

for (const key of Object.keys(person)) {
  const value = person[key as keyof Person];
  console.log(value);
}
  • Object.entries 사용
for (const [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);
}

3. DOM 계층 구조 이해하기

타입스크립트에서 DOM 객체를 다룰 때는 올바른 타입을 지정해야 합니다. 이는 브라우저 환경에서 주로 관련이 있으며, Node.js와 같은 서버 환경에서는 해당 개념이 적용되지 않습니다.

const element = document.getElementById("myElement");

if (element instanceof HTMLDivElement) {
  element.innerText = "Hello, World!";
}

4. 정보를 감추는 목적으로 private 예약어 사용하지 않기

타입스크립트의 private 키워드는 컴파일 타임에만 적용되므로, 런타임에서는 여전히 접근이 가능합니다. 따라서 정보 은닉을 완벽히 수행하는 방법으로 ES6의 #을 활용하거나 클로저를 사용할 수 있습니다.

4.1 ES6 #을 사용한 정보 은닉

ES6의 #을 사용하면 클래스 내부에서만 접근 가능한 진정한 프라이빗 필드를 만들 수 있습니다.

class Person {
  #name: string;
  constructor(name: string) {
    this.#name = name;
  }
  getName() {
    return this.#name;
  }
}

const person = new Person("Alice");
console.log(person.getName()); // 정상 출력: Alice
console.log(person.#name); // 오류 발생: Private field '#name' must be declared in an enclosing class

4.2 클로저를 활용한 정보 은닉

클로저를 사용하면 특정 변수에 대한 접근을 제한할 수 있으며, 외부에서 직접 접근이 불가능한 변수를 유지할 수 있습니다.

function createPerson(name: string) {
  let _name = name;

  return {
    getName: () => _name,
    setName: (newName: string) => { _name = newName; },
  };
}

const person = createPerson("Alice");
console.log(person.getName()); // 정상 출력: Alice
person.setName("Bob");
console.log(person.getName()); // 정상 출력: Bob
console.log(person._name); // 오류: 접근 불가능

이 방식은 내부 변수를 보호하면서도 setName을 통해 안전하게 값을 변경할 수 있는 기능을 제공합니다. 이를 활용하면 정보 은닉과 상태 관리를 동시에 수행할 수 있습니다.

5. 소스맵을 활용한 디버깅

타입스크립트로 작성하여 자바스크립트로 변환된 코드는 디버깅하기 굉장히 어렵습니다.

그렇기에 타입스크립트 코드를 디버깅하려면 소스맵을 활성화해야 합니다.

{
  "compilerOptions": {
    "sourceMap": true
  }
}

브라우저 제조사들의 협력 덕에 타입스크립트로 작성된 코드와 변환된 자바스크립트 코드를 서로 매핑할 수 있게되어 디버깅이 편리해졌습니다. 이것이 소스맵 (Source Map) 입니다. 

이를 통해 브라우저에서 타입스크립트 원본 코드를 직접 확인할 수 있으며, 프로덕션 환경에서는 노출되지 않도록 주의해야 합니다.


마무리

이펙티브 타입스크립트 7장은 타입스크립트 코드 작성 시 고려해야 할 실행 방식과 ECMAScript 기능을 적절히 활용하는 방법을 다룹니다.

더 나은 코드 품질을 위해 ECMAScript 기능을 우선 사용하고, 객체 순회를 안전하게 수행하며, 정보 은닉과 디버깅 방법을 잘 활용하는 것이 중요합니다.

반응형