프로그래밍프론트엔드 개발자

TypeScript에서 Spread 연산자를 통해 객체의 타입 확장이 어떻게 작동하며, 이것이 타입화에 어떤 영향을 미칩니까? 복잡한 구조를 확장할 때 발생할 수 있는 유형의 함정과 비정상적인 상황은 무엇입니까?

Hintsage AI 어시스턴트로 면접 통과

답변.

JavaScript에서 Spread 연산자 (...)는 객체와 배열을 복사하고 확장하는 데 편리함을 제공하기 위해 도입되었습니다. ES2018 이후로는 객체에도 사용되고 있습니다. TypeScript에서는 Spread가 단순히 문법적 개선이 아닌 객체 조작 시 정교한 타입화를 위한 도구가 되었습니다.

역사적으로 JavaScript에서 객체를 복제하거나 확장하는 방법은 Object.assign을 통해 이루어졌으나, 이러한 접근법은 타입 안전성을 잃고 복잡한 객체 구조일 경우 키 충돌이 발생하기 쉽게 했습니다.

문제는 Spread를 사용하여 객체를 병합/확장할 때 TypeScript가 입력 구조를 기반으로 새 타입을 유추하고 키 충돌을 처리하는 과정에서 (‘마지막 것이 이긴다’) 개발자가 의도했던 것과 다를 수 있다는 것입니다. optional 필드, readonly 및 클래스의 프라이빗 속성과의 교차점에 주의할 필요가 있습니다.

해결책: Spread를 사용하는 것을 허용하여 TypeScript 컴파일러가 결과를 자동으로 유추하도록 합니다. 복잡한 케이스에서는 입력 구조의 타입을 명시적으로 제한하고 타입 구조의 변경 사항을 면밀히 살펴보는 것이 중요합니다.

코드 예시:

interface User { name: string; age: number; } interface Extra { isAdmin?: boolean; readonly city: string; } const base: User = { name: 'Ivan', age: 28 }; const extended: User & Extra = { ...base, city: 'Moscow', isAdmin: true };

주요 특징:

  • Spread는 키를 병합하며, 마지막으로 만난 키가 값과 타입을 결정합니다.
  • readonly 및 optional 필드는 전달되지만 원래 타입 간의 충돌이 있을 경우 덮어씌워질 수 있습니다.
  • 클래스 확장 시 프라이빗 속성은 복사되지 않아 오류를 초래할 수 있습니다.

함정 질문들.

Spread 상속을 통해 TypeScript에서 객체의 "deep copy"를 얻을 수 있습니까?

답변: 아닙니다. Spread는 단지 얕은 복사(shallow copy)만 수행하며, 중첩된 객체는 참조로 남습니다.

const original = { user: { name: 'Anna' } }; const cloned = { ...original }; cloned.user.name = 'Maria'; // original.user.name도 변경됩니다

Spread를 사용할 때 객체의 교차하는 키가 있을 경우 타입이 축소되거나 확장되며, 이것이 변수 주석에 어떻게 영향을 미칩니까?

답변: Spread에서는 변수의 타입이 확장되며, 만약 키가 겹치는 경우 오른쪽 속성이 이깁니다; 명시적인 타입 주석이 있는 경우 타입 불일치로 인한 오류가 발생할 수 있습니다.

const a = { id: 4, value: "abc" }; const b = { value: 123 }; const c: { id: number; value: number } = { ...a, ...b }; // ok

Spread는 클래스에 적용될 수 있으며, 프라이빗 속성은 어떻게 됩니까?

답변: Spread는 클래스의 퍼블릭 속성에만 적용 가능하며, 프라이빗 (private/#) 및 보호된 protected 필드는 결과 객체에 포함되지 않습니다.

class Person { private id = 77; name = "Bob"; } const p = new Person(); const spreaded = { ...p }; // spreaded: { name: string }

타입 오류 및 안티 패턴

  • 중첩 객체의 깊은 복제를 위해 Spread를 사용하려는 시도.
  • 키 충돌과 타입 변경의 위험을 잊어버림.
  • 프라이빗 속성의 복사를 기대하며 클래스 인스턴스와 함께 Spread 사용.
  • 다양한 타입의 배열에 Spread를 적용하여 타입 안전성을 상실함.

생활 속 예시

부정적인 케이스

개발자가 사용자 옵션을 확장하기 위해 settings 객체 값을 Spread합니다:

const common = { theme: 'light', notifications: true }; const user = { notifications: false, signature: 'Sasha' }; const merged = { ...common, ...user };

merged가 타입적으로 일관되기를 기대했으나, 실수로 signature 필드의 타입에 대한 오류를 간과하여 Spread가 단순히 키를 복사할 뿐 그 값을 검증하지 않음을 잊었습니다.

장점:

  • 객체를 빠르고 편리하게 병합함.

단점:

  • 중요한 값의 덮어씌움, 일부 필수 속성의 손실, 새로운 키 추가 시 눈에 띄지 않는 오류.

긍정적인 케이스

구성을 병합하기 위해 Spread를 사용하기 전에 검증 후 반환 타입 주석과 함께 사용합니다:

interface Settings { theme: "light" | "dark"; notifications: boolean; signature?: string; } function getSettings(common: Settings, specific: Partial<Settings>): Settings { return { ...common, ...specific }; }

장점:

  • 타입의 정밀 제어, 투명한 확장,
  • 검증의 안전성,
  • 객체 구조의 가시성.

단점:

  • 더 많은 코드 필요,
  • 인터페이스의 최신성 유지 필요.