한 입 크기로 잘라먹는 타입스크립트(TypeScript) 강의의 섹션 3-4: 객체 타입의 호환성을 듣다가 초과 속성 검사(Excess Property Checks)
라는 개념을 접하게 됐습니다.
강의를 통해 어떤 기능인지 이해했지만, “왜 이런 기능이 필요할까?”라는 궁금증이 생겼습니다. 그래서 공식 문서를 참고하여 공부한 내용을 정리해 보았습니다.
초과 속성 감사는 객체 리터럴을 특정 타입에 직접 할당하거나 함수에 매개변수로 전달할 때, 정의되지 않은 속성이 포함되어 있으면 에러를 발생시키는 타입 검사 기능입니다.
쉽게 말해, 객체를 만들 때 예상치 못한 속성(=초과 속성)이 들어 있는지 확인하고, 타입에 없는 속성이 발견되면 TypeScript는 에러로 알려 줍니다.
// @errors: 2345 2739
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
return {
color: config.color || "red",
area: config.width ? config.width * config.width : 20,
};
}
let mySquare = createSquare({ colour: "red", width: 100 });
// ❌ Error: Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
위 예제에서 colour
속성은 SquareConfig
인터페이스에 없는 속성이기 때문에, TypeScript는 이를 감지하고 에러를 출력합니다.
이처럼 초과 속성 검사는 객체 리터럴이 예상된 타입과 정확히 일치하는지 확인해주는 안전 장치입니다.
이번에는 초과 속성 검사가 구체적으로 언제 발생하는지 다시 정리해보겠습니다.
TypeScript는 객체 리터럴을 직접 타입에 할당하거나, 함수의 인자로 넘길 때만 초과 속성 검사를 수행합니다. 객체 리터럴을 직접 사용
하는 경우에만 해당됩니다.
type User = {
id: string;
pwd: string;
};
const user1: User = {
id: 'testId1',
pwd: 'testPwd1',
email: 'testUser@example.com' // ❌ 초과 속성 오류
};
User
타입에는 email
속성이 정의되어 있지 않기 때문에, TypeScript는 이를 초과된 속성으로 판단하고 에러를 발생시킵니다.
type User = {
id: string;
pwd: string;
};
function signIn({id, pwd}: User) {
// login logic
}
// ❌ 초과 속성 오류
const isSuccess = signIn({ id: 'testId1', pwd: 'testPwd1', email: 'testUser@example.com'});
객체 리터럴을 함수의 매개변수로 직접 전달했기 때문에, 초과된 email
속성에 대해 TypeScript가 에러를 발생시킵니다.
초과 속성 검사로 인해 생기는 오류를 해결하는 방법에 대해 알아보겠습니다.
type User = {
id: string;
pwd: string;
};
const user = {
id: 'testUserID',
pwd: 'testUserPWD',
email: 'testUser@example.com'
} as User;
as
키워드를 사용하여, TypeScript에게 “이 객체는 User
타입이 맞으니 초과 속성을 신경 쓰지마!”라고 단언합니다.
단, 이 방법은 잘못된 속성이 포함돼 있어도 무시되어, 정말 확신이 있을 때만 사용해야 합니다.
type User = {
id: string;
pwd: string;
[key: string]: any; // 추가 속성 허용
};
정의되지 않은 속성도 허용하는 타입을 만들고 싶을 때 인덱스 시그니처를 사용합니다. 예를 들어, 유저 정보에 유동적인 속성이 여러 개 들어올 수 있는 상황에 유용합니다.
단, 어떤 속성이든 허용되어 타입 안정성이 낮아질 수 있습니다.
type User = {
id: string;
pwd: string;
};
const temp = {
id: 'testUserID',
pwd: 'testUserPWD',
email: 'testUser@example.com'
};
const user: User = temp;
이 방법은 객체를 중간 변수에 먼저 저장한 후 타입이 지정된 변수에 할당하는 방식입니다. TypeScript는 구조적 타입 시스템을 사용하기 때문에, 이 경우 초과 속성 검사를 생략합니다.
구조적 타입 시스템?
TypeScript는 “구조(structure)”만 맞으면 같은 타입으로 인정해주는 방식을 사용합니다. 이걸 구조적 타입 시스템이라고 부릅니다.
temp
객체는 User
타입에 필요한 id
와 pwd
속성을 모두 가지고 있으므로, email
이라는 여분의 속성이 있어도 TypeScript는 "User 타입과 구조적으로 호환된다"
고 판단합니다. 그래서 초과 속성 검사를 건너뛰고 에러 없이 통과시킵니다.
처음에는 이 초과 속성 검사라는 기능이 왜 필요한지 잘 이해가 되지 않았습니다. 객체를 변수에 먼저 담으면 에러가 나지 않는데, 굳이 직접 넣을 때만 검사하는 이유가 뭘까? 처음에는 그저 불편하게만 느껴졌습니다.
그런데 공식 문서를 읽어보고 정리하면서, 이 기능이 우리가 흔히 할 수 있는 실수를 미리 잡아주는 일종의 안전장치라는 걸 알게 되었습니다.
아래 코드는 content
를 contnet
로 오타를 낸 상황입니다.
interface Post {
id?: number;
title: string;
content: string;
author: string;
}
// ❌ Error: Object literal may only specify known properties, but 'contnet' does not exist in type 'Post'. Did you mean to write 'content'?(2561)
const post: Post = {
id: 1004,
title: 'test post title',
contnet: 'test post content', // 오타
author: 'test author'
};
JavaScript에서는 이 코드가 그냥 실행되지만, contnet
는 Post
타입에 없기 때문에 실제로 사용되지 않고 버그로 이어질 수 있습니다.
하지만 TypeScript는 초과 속성 검사를 통해 타입에 없는 속성은 잘못된 것일 수 있다고 판단하고, 에러를 발생시켜 실수를 컴파일 단계에서 알려줍니다.
어떤 함수에 타입이 명확히 정해져 있을 때, 정의되지 않은 속성이 함께 넘어오면 다른 사람이 그 속성도 사용된다고 착각할 수 있습니다.
interface Post {
id?: number;
title: string;
content: string;
author: string;
}
function createPost(post: Post){
// 게시글 생성 로직
}
createPost({
title: 'test post title',
content: 'test post content',
author: 'test author'
isLike: false // ❌ createPost는 이 속성을 전혀 사용하지 않음
});
위 코드에서 isLike
는 실제로는 함수 내부에서 아무 역할도 하지 않지만, 나중에 이 코드를 본 사람은 “isLike 속성도 처리해주는 함수구나”
라고 오해할 수 있습니다.
이런 혼란을 줄이기 위해, TypeScript는 객체 리터럴을 직접 넘길 때 구조가 정확히 일치하는지 더 엄격하게 검사합니다.
초과 속성 검사는 다음과 같은 역할을 합니다:
이 기능 덕분에 우리의 코드를 더 안전하고 신뢰할 수 있게 작성할 수 있고, 나중에 유지보수할 때도 의도를 파악하기 쉬운 코드를 작성할 수 있습니다.
강의를 듣고 공식 문서를 읽으며 초과 속성 검사(Excess Property Checks)에 대해 정리해 보았습니다. 처음에는 “왜 이런 기능이 필요한 걸까?” 라는 의문이 들었습니다.
하지만 공식 문서를 읽어보고 예제 코드를 생각해 보면서 타입에 맞지 않는 속성이나 오타를 사전에 막아주는 안전장치라는 점을 알게 되었습니다. 또한, 객체의 구조를 정확히 맞춰서 코드의 명확성과 유지보수성을 높이기 위한 목적이라는 것도 이해할 수 있었습니다.
초과 속성 검사로 인해 발생하는 오류를 해결하는 방법으로는 타입 단언, 인덱스 시그니처, 중간 변수 사용 같은 여러 가지 방법이 있다는 것도 새롭게 배웠습니다.
앞으로는 객체 리터럴을 직접 특정 타입에 할당하거나 함수에 넘길 때, 왜 오류가 발생하는지 이해하고, 적절한 방식으로 해결할 수 있을 것 같습니다.