목차
객체 타입과 type 별칭 제대로 이해하기
지금까지는 문자열, 숫자, 불리언처럼 하나의 값을 표현하는 타입을 배웠습니다.
const userName: string = "김타입";
const age: number = 25;
const isDeveloper: boolean = true;배열을 사용해 같은 종류의 값을 여러 개 관리하는 방법도 살펴봤습니다.
const skills: string[] = [
"JavaScript",
"TypeScript",
"React",
];하지만 실제 프로그램의 데이터는 값 하나만 덩그러니 돌아다니지 않습니다.
사용자 한 명을 표현하려면 여러 정보가 함께 필요합니다.
const userName = "김타입";
const age = 25;
const email = "type@example.com";
const isAdmin = false;이렇게 각각의 변수로 관리하면 어떤 값들이 한 명의 사용자에 속하는지 한눈에 파악하기 어렵습니다.
객체로 묶으면 관계가 훨씬 명확해집니다.
const user = {
name: "김타입",
age: 25,
email: "type@example.com",
isAdmin: false,
};이제 네 개의 값이 `user`라는 하나의 객체 안에 모였습니다.
그런데 사용자 객체를 여러 개 만들어야 한다면 어떨까요?
const firstUser: {
name: string;
age: number;
email: string;
isAdmin: boolean;
} = {
name: "김타입",
age: 25,
email: "type@example.com",
isAdmin: false,
};
const secondUser: {
name: string;
age: number;
email: string;
isAdmin: boolean;
} = {
name: "이컴파일",
age: 30,
email: "compile@example.com",
isAdmin: true,
};객체의 타입을 매번 반복하고 있습니다.
사용자가 열 명으로 늘어나면 타입도 열 번 작성해야 합니다.
속성 하나를 추가하려면 곳곳을 찾아다니며 수정해야 합니다.
설계도가 없는 건물에 방을 하나씩 즉흥적으로 붙이다 보면 어느 날 화장실 문을 열었는데 회의실이 나올 수도 있습니다. 🏗️
이럴 때 `type` 별칭을 사용해 객체의 구조에 이름을 붙일 수 있습니다.
type User = {
name: string;
age: number;
email: string;
isAdmin: boolean;
};이제 `User`라는 설계도를 여러 객체에 재사용할 수 있습니다.
const firstUser: User = {
name: "김타입",
age: 25,
email: "type@example.com",
isAdmin: false,
};
const secondUser: User = {
name: "이컴파일",
age: 30,
email: "compile@example.com",
isAdmin: true,
};이번 시간에는 객체의 구조를 타입으로 표현하고, 반복되는 타입에 이름을 붙여 재사용하는 방법을 알아보겠습니다.
1. 이번 시간에 배울 내용
이번 시간에는 다음 내용을 살펴봅니다.
- 객체란 무엇인가?
- 객체 타입은 어떻게 작성하는가?
- 객체 속성의 타입을 지정하는 방법
- 객체 타입을 함수에 사용하는 방법
- `type` 별칭은 무엇인가?
- 같은 객체 구조를 재사용하는 방법
- 선택적 속성 `?`
- 읽기 전용 속성 `readonly`
- 중첩된 객체 타입
- 객체 배열 만들기
- 메서드를 가진 객체 타입
- 타입 별칭을 조합하는 방법
- 구조적 타이핑이란 무엇인가?
- 초과 속성 검사는 왜 발생하는가?
- 객체 타입 설계 시 자주 발생하는 실수
- 실무형 사용자, 상품, 주문 데이터 설계하기
오늘의 핵심 문장은 다음과 같습니다.
객체는 데이터를 묶고, 객체 타입은 그 데이터가 지켜야 할 구조를 정의한다.
객체가 실제 건물이라면 객체 타입은 건축 설계도입니다.
설계도에는 방 이름, 용도, 필수 시설이 적혀 있지만 실제 가구까지 대신 놓아주지는 않습니다.
2. 객체란?
객체는 여러 값을 속성 이름과 함께 묶어 관리하는 자료구조입니다.
const user = {
name: "김타입",
age: 25,
isAdmin: false,
};객체 안의 각 값은 속성(Property)이라고 부릅니다.
name
age
isAdmin각 속성은 이름과 값을 가집니다.
name → "김타입"
age → 25
isAdmin → false객체는 중괄호 `{}`를 사용해 만듭니다.
const 객체이름 = {
속성이름: 값,
속성이름: 값,
};예를 들어 상품 객체는 다음과 같습니다.
const product = {
id: 101,
name: "기계식 키보드",
price: 120000,
isSoldOut: false,
};객체를 사용하면 서로 관련된 데이터를 하나의 단위로 관리할 수 있습니다.
3. TypeScript의 객체 타입 추론
TypeScript는 객체에 들어 있는 값을 보고 속성 타입을 추론합니다.
const user = {
name: "김타입",
age: 25,
isAdmin: false,
};TypeScript가 추론한 구조는 다음과 비슷합니다.
{
name: string;
age: number;
isAdmin: boolean;
}`name`에는 문자열이 들어 있으므로 `string`으로 추론합니다.
`age`에는 숫자가 들어 있으므로 `number`로 추론합니다.
`isAdmin`에는 불리언이 들어 있으므로 `boolean`으로 추론합니다.
올바른 타입의 값으로 수정할 수 있습니다.
user.name = "이타입";
user.age = 30;
user.isAdmin = true;하지만 다른 타입의 값은 넣을 수 없습니다.
user.name = 100;
user.age = "스물다섯";
user.isAdmin = "관리자";TypeScript는 처음 객체를 살펴본 뒤 각 속성에 이름표를 붙입니다.
객체 안에서 속성들이 자리 바꾸기 파티를 열려고 해도 타입 경비원이 막아줍니다.
4. 객체 속성에 접근하기
점 표기법을 사용하면 객체 속성에 접근할 수 있습니다.
const user = {
name: "김타입",
age: 25,
};console.log(user.name);
console.log(user.age);출력 결과:
김타입
25대괄호 표기법도 사용할 수 있습니다.
console.log(user["name"]);
console.log(user["age"]);속성 이름이 변수로 결정되는 경우 대괄호 표기법이 유용합니다.
const propertyName = "name";
console.log(user[propertyName]);다만 속성 이름의 타입이 지나치게 넓으면 TypeScript가 안전성을 보장하기 어렵습니다.
const propertyName: string = "name";
console.log(user[propertyName]);일반 `string`에는 `"name"`뿐 아니라 존재하지 않는 모든 문자열이 들어갈 수 있기 때문입니다.
동적 속성 접근은 이후 `keyof`와 인덱스 접근 타입을 배울 때 더 자세히 살펴보겠습니다.
5. 객체 타입을 직접 작성하기
객체 타입은 객체와 비슷한 모양으로 작성합니다.
const user: {
name: string;
age: number;
isAdmin: boolean;
} = {
name: "김타입",
age: 25,
isAdmin: false,
};왼쪽은 객체 타입입니다.
{
name: string;
age: number;
isAdmin: boolean;
}오른쪽은 실제 객체 값입니다.
{
name: "김타입",
age: 25,
isAdmin: false,
}둘을 비교해 보면 다음과 같습니다.
타입 설계도 실제 값
name: string → name: "김타입"
age: number → age: 25
isAdmin: boolean → isAdmin: falseTypeScript는 실제 객체가 설계도와 일치하는지 확인합니다.
6. 객체 타입에서는 속성 이름과 타입을 작성한다
객체 타입의 기본 문법은 다음과 같습니다.
{
속성이름: 타입;
속성이름: 타입;
}사용자 타입을 직접 작성해 보겠습니다.
const user: {
id: number;
name: string;
email: string;
isActive: boolean;
} = {
id: 1,
name: "김타입",
email: "type@example.com",
isActive: true,
};각 속성은 정해진 타입의 값을 가져야 합니다.
user.id = 2;
user.name = "이컴파일";
user.email = "compile@example.com";
user.isActive = false;잘못된 타입은 허용되지 않습니다.
user.id = "2";
user.name = 100;
user.isActive = "활성";7. 세미콜론과 쉼표
객체 타입의 속성은 세미콜론으로 구분할 수 있습니다.
type User = {
id: number;
name: string;
email: string;
};쉼표를 사용하는 것도 가능합니다.
type User = {
id: number,
name: string,
email: string,
};줄바꿈만으로 구분되는 형태도 일부 상황에서 사용할 수 있습니다.
type User = {
id: number
name: string
email: string
};일반적으로 객체 타입에서는 세미콜론을 많이 사용합니다.
type User = {
id: number;
name: string;
};객체 값에서는 쉼표를 사용합니다.
const user = {
id: 1,
name: "김타입",
};정리하면 다음과 같습니다.
객체 타입
→ 세미콜론을 주로 사용
객체 값
→ 쉼표 사용8. 필수 속성이 빠지면 오류가 발생한다
다음 객체 타입에서는 네 속성이 모두 필수입니다.
type User = {
id: number;
name: string;
email: string;
isActive: boolean;
};모든 속성을 작성해야 합니다.
const user: User = {
id: 1,
name: "김타입",
email: "type@example.com",
isActive: true,
};`email`을 빠뜨리면 오류가 발생합니다.
const user: User = {
id: 1,
name: "김타입",
isActive: true,
};TypeScript는 다음과 비슷한 내용을 알려줍니다.
Property 'email' is missing.`User` 설계도에서 `email`은 필수 시설입니다.
설계도에 엘리베이터가 필수라고 적혀 있는데 계단만 놓고 준공 승인을 받을 수는 없습니다.
9. 정의되지 않은 속성을 추가하면?
다음 타입을 살펴보겠습니다.
type User = {
id: number;
name: string;
};객체를 직접 작성하면서 `role` 속성을 추가해 보겠습니다.
const user: User = {
id: 1,
name: "김타입",
role: "admin",
};TypeScript는 오류를 표시합니다.
`User` 타입에는 `role` 속성이 정의되어 있지 않기 때문입니다.
Object literal may only specify known properties.이를 초과 속성 검사(Excess Property Checking)라고 합니다.
객체 리터럴을 특정 타입에 직접 할당할 때 오타나 잘못된 속성을 찾아주는 검사입니다.
예를 들어 다음 코드에는 오타가 있습니다.
const user: User = {
id: 1,
naem: "김타입",
};`name`을 `naem`으로 잘못 작성했습니다.
TypeScript는 알 수 없는 속성이 들어왔다고 알려줍니다.
초과 속성 검사는 객체의 문 앞에서 방문 명단을 확인하는 안내원입니다.
10. 객체 타입을 반복하면 생기는 문제
사용자 객체를 두 개 만들어 보겠습니다.
const firstUser: {
id: number;
name: string;
email: string;
} = {
id: 1,
name: "김타입",
email: "type@example.com",
};
const secondUser: {
id: number;
name: string;
email: string;
} = {
id: 2,
name: "이컴파일",
email: "compile@example.com",
};두 객체의 타입이 완전히 같습니다.
그런데 객체를 만들 때마다 같은 타입을 다시 작성하고 있습니다.
함수에도 같은 타입이 필요하다면 반복이 더 늘어납니다.
function printUser(user: {
id: number;
name: string;
email: string;
}): void {
console.log(user.name);
}이 구조에 `isActive` 속성을 추가하려면 모든 타입 선언을 찾아 수정해야 합니다.
{
id: number;
name: string;
email: string;
isActive: boolean;
}복사해서 붙여 넣은 타입은 처음에는 편하지만 시간이 지나면 복제 인간 군단이 됩니다.
한 명을 수정했는데 나머지는 예전 모습을 유지하기 시작합니다. 👥
11. `type` 별칭이란?
`type`을 사용하면 타입에 이름을 붙일 수 있습니다.
type User = {
id: number;
name: string;
email: string;
};이것을 타입 별칭(Type Alias)이라고 합니다.
type
타입에 이름을 붙이는 키워드
User
새로 만든 타입 이름
{
id: number;
name: string;
email: string;
}
User가 가리키는 실제 타입 구조이제 `User`를 일반 타입처럼 사용할 수 있습니다.
const firstUser: User = {
id: 1,
name: "김타입",
email: "type@example.com",
};const secondUser: User = {
id: 2,
name: "이컴파일",
email: "compile@example.com",
};함수 매개변수에도 사용할 수 있습니다.
function printUser(user: User): void {
console.log(
`${user.name}: ${user.email}`
);
}12. 타입 별칭의 기본 문법
타입 별칭은 다음 형식으로 작성합니다.
type 타입이름 = 타입;문자열 타입에 이름을 붙일 수도 있습니다.
type UserName = string;const userName: UserName = "김타입";유니언 타입에도 이름을 붙일 수 있습니다.
type UserId =
| string
| number;let userId: UserId = 100;
userId = "USER-100";튜플에도 이름을 붙일 수 있습니다.
type Coordinate = [
x: number,
y: number,
];const position: Coordinate = [
10,
20,
];함수 타입에도 이름을 붙일 수 있습니다.
type Calculator = (
firstNumber: number,
secondNumber: number
) => number;const add: Calculator = (
firstNumber,
secondNumber
) => {
return firstNumber + secondNumber;
};`type` 별칭은 객체 전용 문법이 아닙니다.
거의 모든 타입 표현에 이름을 붙일 수 있습니다.
13. 타입 이름은 대문자로 시작하는 것이 일반적이다
타입 별칭 이름은 일반적으로 파스칼 표기법을 사용합니다.
type User = {
id: number;
name: string;
};type ProductItem = {
id: number;
name: string;
price: number;
};type RequestStatus =
| "idle"
| "loading"
| "success"
| "error";파스칼 표기법은 각 단어의 첫 글자를 대문자로 작성합니다.
User
ProductItem
RequestStatus
ShoppingCart일반 변수와 구분하기도 쉽습니다.
type User = {
name: string;
};
const user: User = {
name: "김타입",
};User
→ 타입 설계도
user
→ 실제 객체대문자 설계도와 소문자 입주민이 한눈에 구분됩니다.
14. 타입 별칭은 새로운 런타임 값을 만들지 않는다
다음 타입을 정의했습니다.
type User = {
id: number;
name: string;
};`User`는 TypeScript의 타입 영역에서만 존재합니다.
다음처럼 출력할 수 없습니다.
console.log(User);`User`라는 실제 JavaScript 값은 없기 때문입니다.
TypeScript 코드를 JavaScript로 변환하면 타입 정보는 사라집니다.
type User = {
id: number;
name: string;
};
const user: User = {
id: 1,
name: "김타입",
};변환된 JavaScript에서는 다음과 비슷해집니다.
const user = {
id: 1,
name: "김타입",
};`type User`는 실행 중에 존재하지 않습니다.
타입 별칭은 건축 설계도와 비슷하지만 건물이 완성된 뒤에는 현장 안내판에서 사라집니다.
건물의 구조에는 영향을 주지만 입주민이 설계도를 식탁으로 사용할 수는 없습니다.
15. 타입 별칭은 값의 구조를 검사한다
다음 타입을 정의합니다.
type Product = {
id: number;
name: string;
price: number;
isSoldOut: boolean;
};정상적인 상품 객체입니다.
const product: Product = {
id: 101,
name: "기계식 키보드",
price: 120000,
isSoldOut: false,
};속성 타입이 틀리면 오류가 발생합니다.
const product: Product = {
id: "101",
name: "기계식 키보드",
price: "120000원",
isSoldOut: "아니요",
};TypeScript는 각각의 속성을 검사합니다.
id
string이 아니라 number 필요
price
string이 아니라 number 필요
isSoldOut
string이 아니라 boolean 필요16. 함수 매개변수에 객체 타입 사용하기
사용자 정보를 출력하는 함수를 만들어 보겠습니다.
type User = {
id: number;
name: string;
email: string;
};function printUser(user: User): void {
console.log(`번호: ${user.id}`);
console.log(`이름: ${user.name}`);
console.log(`이메일: ${user.email}`);
}함수를 호출합니다.
printUser({
id: 1,
name: "김타입",
email: "type@example.com",
});잘못된 객체는 전달할 수 없습니다.
printUser({
id: 1,
name: "김타입",
});필수 속성인 `email`이 없습니다.
속성 타입이 틀려도 오류가 발생합니다.
printUser({
id: "1",
name: "김타입",
email: "type@example.com",
});17. 함수 반환 타입에 객체 타입 사용하기
함수가 객체를 반환할 때도 타입 별칭을 사용할 수 있습니다.
type User = {
id: number;
name: string;
email: string;
};function createUser(
id: number,
name: string,
email: string
): User {
return {
id,
name,
email,
};
}함수를 호출합니다.
const user = createUser(
1,
"김타입",
"type@example.com"
);`user`의 타입은 `User`입니다.
console.log(user.name);함수에서 잘못된 객체를 반환하면 오류가 발생합니다.
function createUser(): User {
return {
id: 1,
name: "김타입",
};
}반환 객체에 `email`이 빠졌기 때문입니다.
반환 타입은 출고 검사표 역할을 합니다.
공장에서 객체가 나가기 전에 부품이 모두 들어 있는지 확인합니다. 🏭
18. 객체를 매개변수로 받고 객체를 반환하기
사용자 이름을 변경하는 함수를 만들어 보겠습니다.
type User = {
id: number;
name: string;
email: string;
};function renameUser(
user: User,
newName: string
): User {
return {
...user,
name: newName,
};
}사용합니다.
const originalUser: User = {
id: 1,
name: "김타입",
email: "type@example.com",
};
const renamedUser = renameUser(
originalUser,
"김제네릭"
);원본 객체와 새 객체를 확인합니다.
console.log(originalUser.name);
console.log(renamedUser.name);출력 결과:
김타입
김제네릭전개 연산자 `...user`로 기존 속성을 복사한 뒤 `name`을 새 값으로 덮어썼습니다.
19. 선택적 속성 `?`
모든 사용자가 닉네임을 가지고 있는 것은 아닙니다.
다음처럼 `nickname`을 필수로 정의하면 모든 사용자 객체에서 값을 작성해야 합니다.
type User = {
id: number;
name: string;
nickname: string;
};닉네임이 없는 사용자를 표현하기 어렵습니다.
속성 이름 뒤에 `?`를 붙이면 선택적 속성이 됩니다.
type User = {
id: number;
name: string;
nickname?: string;
};이제 `nickname`을 생략할 수 있습니다.
const firstUser: User = {
id: 1,
name: "김타입",
};닉네임을 포함해도 됩니다.
const secondUser: User = {
id: 2,
name: "이컴파일",
nickname: "컴파일러",
};`?`는 해당 속성이 있을 수도 있고 없을 수도 있다는 뜻입니다.
nickname?: string
nickname 속성이 존재함
→ 값은 string
nickname 속성이 없음
→ 읽으면 undefined 가능20. 선택적 속성을 읽을 때 주의하기
선택적 속성을 읽으면 `undefined`일 가능성이 있습니다.
type User = {
id: number;
name: string;
nickname?: string;
};const user: User = {
id: 1,
name: "김타입",
};다음 코드는 오류가 발생할 수 있습니다.
console.log(
user.nickname.toUpperCase()
);`nickname`이 없을 수 있기 때문입니다.
값을 확인한 뒤 사용합니다.
if (
user.nickname !== undefined
) {
console.log(
user.nickname.toUpperCase()
);
}선택적 연결 연산자를 사용할 수도 있습니다.
console.log(
user.nickname?.toUpperCase()
);기본값을 지정할 수도 있습니다.
const displayName =
user.nickname ?? user.name;console.log(displayName);21. 선택적 속성과 `string | undefined`
다음 두 타입은 비슷해 보입니다.
type FirstUser = {
nickname?: string;
};type SecondUser = {
nickname:
string | undefined;
};하지만 객체를 만들 때 차이가 있습니다.
`FirstUser`는 속성 자체를 생략할 수 있습니다.
const firstUser: FirstUser = {};`SecondUser`는 `nickname` 속성이 반드시 존재해야 합니다.
const secondUser: SecondUser = {
nickname: undefined,
};다음 코드는 오류입니다.
const secondUser: SecondUser = {};차이를 정리하면 다음과 같습니다.
nickname?: string
속성 자체가 없을 수 있음
nickname: string | undefined
속성은 반드시 존재함
값이 undefined일 수 있음주소록에 이름이 등록되지 않은 경우와, 이름은 있지만 전화번호 칸이 비어 있는 경우의 차이입니다.
22. `exactOptionalPropertyTypes` 참고하기
TypeScript 설정에 따라 선택적 속성의 `undefined` 처리가 더 엄격해질 수 있습니다.
{
"compilerOptions": {
"exactOptionalPropertyTypes": true
}
}다음 타입을 살펴보겠습니다.
type User = {
nickname?: string;
};이 설정이 활성화되면 `nickname?: string`은 속성이 없거나 문자열 값이 있는 상태를 더 정확하게 구분합니다.
명시적으로 `undefined`를 넣으려면 타입에 포함해야 할 수 있습니다.
type User = {
nickname?:
string | undefined;
};초보 단계에서는 다음 기준을 기억하면 충분합니다.
속성을 생략할 수 있음
→ ?
속성은 필수이지만 값이 없을 수 있음
→ Type | undefined23. 읽기 전용 속성 `readonly`
사용자 ID처럼 한 번 정해진 뒤 변경되면 안 되는 값이 있습니다.
type User = {
readonly id: number;
name: string;
email: string;
};객체를 생성할 때는 `id` 값을 작성할 수 있습니다.
const user: User = {
id: 1,
name: "김타입",
email: "type@example.com",
};일반 속성은 변경할 수 있습니다.
user.name = "이타입";
user.email = "new@example.com";하지만 `readonly` 속성은 변경할 수 없습니다.
user.id = 2;TypeScript는 다음과 비슷한 오류를 알려줍니다.
Cannot assign to 'id'
because it is a read-only property.`readonly`는 객체 생성 후 해당 속성의 재할당을 막습니다.
24. `const`와 `readonly`의 차이
다음 객체는 `const`로 선언되었습니다.
const user = {
id: 1,
name: "김타입",
};`const`이므로 다른 객체를 다시 할당할 수 없습니다.
user = {
id: 2,
name: "이컴파일",
};하지만 객체 내부의 속성은 변경할 수 있습니다.
user.id = 2;
user.name = "이컴파일";속성 변경까지 막으려면 `readonly`가 필요합니다.
type User = {
readonly id: number;
name: string;
};const user: User = {
id: 1,
name: "김타입",
};user.id = 2;오류가 발생합니다.
정리하면 다음과 같습니다.
const
변수에 다른 객체를 다시 할당하지 못함
readonly
객체의 특정 속성을 다시 할당하지 못함`const`는 건물 주소 이전을 막고, `readonly`는 건물 안의 금고 위치 변경을 막습니다.
25. `readonly`는 런타임 보안 장치가 아니다
`readonly`는 TypeScript 타입 검사 단계에서 속성 변경을 막습니다.
type User = {
readonly id: number;
name: string;
};컴파일된 JavaScript에서는 타입 정보가 사라집니다.
따라서 `readonly`는 데이터베이스 보안이나 JavaScript 실행 중 객체 동결을 자동으로 제공하지 않습니다.
런타임에서 객체 변경을 제한하려면 별도의 처리가 필요할 수 있습니다.
const user = Object.freeze({
id: 1,
name: "김타입",
});readonly
TypeScript 컴파일 단계의 보호
Object.freeze()
JavaScript 실행 단계의 변경 제한두 기능은 서로 다른 시간대에 근무합니다.
26. `readonly`는 얕게 적용된다
중첩 객체를 살펴보겠습니다.
type User = {
readonly id: number;
profile: {
nickname: string;
};
};const user: User = {
id: 1,
profile: {
nickname: "타입마스터",
},
};`id`는 변경할 수 없습니다.
user.id = 2;하지만 중첩된 `profile.nickname`은 변경할 수 있습니다.
user.profile.nickname =
"컴파일마스터";`profile` 자체를 읽기 전용으로 만들 수도 있습니다.
type User = {
readonly id: number;
readonly profile: {
nickname: string;
};
};그래도 내부의 `nickname`은 여전히 변경할 수 있습니다.
user.profile.nickname =
"새 닉네임";내부 속성까지 읽기 전용으로 지정해야 합니다.
type User = {
readonly id: number;
readonly profile: {
readonly nickname: string;
};
};`readonly`는 자동으로 객체 깊숙한 곳까지 순찰하지 않습니다.
지정된 문 앞에서만 근무하는 경비원입니다.
27. 중첩 객체 타입
객체 안에 다른 객체가 들어갈 수 있습니다.
const user = {
id: 1,
name: "김타입",
address: {
city: "서울",
district: "강남구",
zipCode: "06000",
},
};객체 타입은 다음처럼 작성할 수 있습니다.
type User = {
id: number;
name: string;
address: {
city: string;
district: string;
zipCode: string;
};
};실제 객체를 만듭니다.
const user: User = {
id: 1,
name: "김타입",
address: {
city: "서울",
district: "강남구",
zipCode: "06000",
},
};중첩된 속성에 접근합니다.
console.log(
user.address.city
);출력 결과:
서울28. 중첩 타입을 별도로 분리하기
주소 구조가 다른 타입에서도 사용될 수 있다면 별칭으로 분리하는 것이 좋습니다.
type Address = {
city: string;
district: string;
zipCode: string;
};사용자 타입에서 재사용합니다.
type User = {
id: number;
name: string;
address: Address;
};배송지 타입에서도 사용할 수 있습니다.
type Delivery = {
orderId: number;
recipient: string;
address: Address;
};실제 객체를 만들어 보겠습니다.
const user: User = {
id: 1,
name: "김타입",
address: {
city: "서울",
district: "강남구",
zipCode: "06000",
},
};타입을 작은 단위로 나누면 재사용하기 쉽고 읽기도 편해집니다.
Address
주소 설계도
User
사용자 설계도 안에서 Address 사용
Delivery
배송 설계도 안에서도 Address 사용큰 설계도를 한 장에 모두 그리지 않고 전기, 수도, 구조 도면을 나누는 것과 같습니다.
29. 선택적인 중첩 객체
모든 사용자에게 프로필 정보가 있는 것은 아닐 수 있습니다.
type User = {
id: number;
name: string;
profile?: {
nickname: string;
imageUrl?: string;
};
};프로필이 없는 사용자입니다.
const firstUser: User = {
id: 1,
name: "김타입",
};프로필이 있는 사용자입니다.
const secondUser: User = {
id: 2,
name: "이컴파일",
profile: {
nickname: "컴파일러",
},
};중첩 속성에 접근할 때는 존재 여부를 확인해야 합니다.
const imageUrl =
secondUser.profile?.imageUrl
?? "/images/default.png";`profile`도 없을 수 있고 `imageUrl`도 없을 수 있기 때문에 `?.`를 사용했습니다.
30. 객체 배열 만들기
사용자 여러 명을 관리하려면 객체 배열을 사용할 수 있습니다.
type User = {
id: number;
name: string;
isActive: boolean;
};const users: User[] = [
{
id: 1,
name: "김타입",
isActive: true,
},
{
id: 2,
name: "이컴파일",
isActive: false,
},
{
id: 3,
name: "박제네릭",
isActive: true,
},
];`users` 배열에는 `User` 구조를 만족하는 객체만 들어갈 수 있습니다.
잘못된 객체를 추가하면 오류가 발생합니다.
users.push({
id: 4,
name: "최인터페이스",
});`isActive` 속성이 빠졌습니다.
속성 타입이 틀려도 오류입니다.
users.push({
id: "4",
name: "최인터페이스",
isActive: true,
});31. 객체 배열에서 원하는 데이터 찾기
`find()`를 사용해 특정 사용자를 찾을 수 있습니다.
const foundUser =
users.find(
(user) => user.id === 2
);`find()`는 값을 찾지 못하면 `undefined`를 반환합니다.
따라서 타입은 다음과 같습니다.
User | undefined안전하게 확인합니다.
if (
foundUser !== undefined
) {
console.log(
foundUser.name
);
}기본값을 적용할 수도 있습니다.
const foundUserName =
foundUser?.name
?? "사용자 없음";32. 객체 배열 필터링하기
활성 사용자만 선택해 보겠습니다.
const activeUsers =
users.filter(
(user) => user.isActive
);`activeUsers`의 타입은 `User[]`입니다.
사용자 이름만 추출할 수도 있습니다.
const activeUserNames =
users
.filter(
(user) => user.isActive
)
.map(
(user) => user.name
);결과:
["김타입", "박제네릭"]객체 타입이 정의되어 있으므로 콜백 안에서 자동완성도 받을 수 있습니다.
user.id
user.name
user.isActive33. 메서드를 가진 객체 타입
객체에는 값뿐 아니라 함수도 속성으로 포함할 수 있습니다.
객체 안에 들어 있는 함수를 메서드(Method)라고 부릅니다.
const user = {
name: "김타입",
greet() {
return "안녕하세요.";
},
};타입은 다음처럼 작성할 수 있습니다.
type User = {
name: string;
greet(): string;
};객체를 만듭니다.
const user: User = {
name: "김타입",
greet() {
return `안녕하세요, ${this.name}입니다.`;
},
};메서드를 호출합니다.
console.log(
user.greet()
);출력 결과:
안녕하세요, 김타입입니다.34. 메서드 타입 작성 방법
메서드 타입은 두 가지 방식으로 작성할 수 있습니다.
메서드 문법
type User = {
name: string;
greet(message: string): string;
};함수 속성 문법
type User = {
name: string;
greet: (
message: string
) => string;
};두 방식 모두 문자열을 받고 문자열을 반환하는 `greet` 기능을 표현합니다.
사용 예제입니다.
const user: User = {
name: "김타입",
greet(message) {
return `${message}, ${this.name}입니다.`;
},
};console.log(
user.greet("반갑습니다")
);간단한 객체 메서드에는 메서드 문법이 읽기 편합니다.
함수 자체를 값처럼 다루는 속성이라는 점을 강조하려면 함수 속성 문법을 사용할 수 있습니다.
35. 선택적 메서드
객체의 메서드도 선택 사항으로 만들 수 있습니다.
type User = {
id: number;
name: string;
onLogin?: () => void;
};콜백이 없는 사용자입니다.
const firstUser: User = {
id: 1,
name: "김타입",
};콜백이 있는 사용자입니다.
const secondUser: User = {
id: 2,
name: "이컴파일",
onLogin() {
console.log(
"로그인 이벤트 실행"
);
},
};선택적 메서드는 존재하지 않을 수 있으므로 안전하게 호출합니다.
secondUser.onLogin?.();36. 객체 타입 안에서 유니언 타입 사용하기
객체 속성에 유니언 타입을 사용할 수 있습니다.
type User = {
id: string | number;
name: string;
role:
| "admin"
| "manager"
| "user";
};문자열 ID를 사용할 수 있습니다.
const firstUser: User = {
id: "USER-100",
name: "김타입",
role: "admin",
};숫자 ID도 사용할 수 있습니다.
const secondUser: User = {
id: 200,
name: "이컴파일",
role: "user",
};허용되지 않은 역할은 오류입니다.
const thirdUser: User = {
id: 300,
name: "박타입",
role: "guest",
};객체 타입 안에서도 지금까지 배운 타입을 자유롭게 조합할 수 있습니다.
기본 타입
배열
튜플
유니언
리터럴
null
undefined
함수 타입
다른 객체 타입객체 타입은 이전에 배운 타입들이 모여 사는 작은 도시입니다. 🏙️
37. 타입 별칭 안에서 다른 타입 별칭 사용하기
작은 타입을 조합해 큰 타입을 만들 수 있습니다.
type UserId =
| string
| number;type UserRole =
| "admin"
| "manager"
| "user";type Address = {
city: string;
district: string;
};이 타입들을 `User`에 조합합니다.
type User = {
readonly id: UserId;
name: string;
role: UserRole;
address?: Address;
};실제 객체를 만듭니다.
const user: User = {
id: "USER-100",
name: "김타입",
role: "admin",
address: {
city: "서울",
district: "강남구",
},
};작은 타입을 재사용하면 의미가 분명해지고 변경 범위도 줄어듭니다.
38. 타입 별칭을 조합하는 교차 타입
두 객체 타입을 합치고 싶을 때 `&`를 사용할 수 있습니다.
type BasicUser = {
id: number;
name: string;
};type UserPermission = {
canEdit: boolean;
canDelete: boolean;
};두 타입을 결합합니다.
type AdminUser =
BasicUser &
UserPermission;`AdminUser`는 두 타입의 모든 속성을 가져야 합니다.
const admin: AdminUser = {
id: 1,
name: "김관리자",
canEdit: true,
canDelete: true,
};`&`는 교차 타입(Intersection Type)을 만듭니다.
BasicUser
그리고
UserPermission
두 조건을 모두 만족유니언 `|`가 여러 선택지 중 하나라면, 교차 `&`는 여러 조건을 모두 만족해야 합니다.
교차 타입은 이후 객체 타입 확장 회차에서 더 자세히 다루겠습니다.
39. 구조적 타이핑이란?
TypeScript는 타입 이름보다 실제 구조를 중요하게 생각합니다.
다음 타입을 정의합니다.
type User = {
id: number;
name: string;
};별도의 타입 선언 없이 객체를 만듭니다.
const developer = {
id: 1,
name: "김타입",
language: "TypeScript",
};`developer`에는 `User`에 없는 `language` 속성이 추가로 있습니다.
하지만 다음 함수에 전달할 수 있습니다.
function printUser(
user: User
): void {
console.log(user.name);
}printUser(developer);`developer`가 `User`에 필요한 구조를 모두 가지고 있기 때문입니다.
User가 요구하는 속성
id: number
name: string
developer가 가진 속성
id: number
name: string
language: string필요한 속성을 모두 가지고 있으므로 호환됩니다.
이것을 구조적 타이핑(Structural Typing)이라고 합니다.
TypeScript는 명함의 직책보다 실제로 어떤 장비를 가지고 있는지 확인합니다.
40. 구조가 같으면 서로 호환될 수 있다
서로 다른 이름의 타입을 정의해 보겠습니다.
type User = {
id: number;
name: string;
};type Member = {
id: number;
name: string;
};타입 이름은 다르지만 구조는 같습니다.
const member: Member = {
id: 1,
name: "김타입",
};`User` 변수에 할당할 수 있습니다.
const user: User =
member;TypeScript는 이름이 아니라 구조를 비교하기 때문입니다.
User
{id: number, name: string}
Member
{id: number, name: string}
구조가 같음
→ 호환 가능Java나 C# 같은 일부 언어의 명목적 타입 시스템과 다른 중요한 특징입니다.
41. 초과 속성 검사와 구조적 타이핑은 왜 달라 보일까?
다음 함수가 있습니다.
type User = {
id: number;
name: string;
};function printUser(
user: User
): void {
console.log(user.name);
}객체 리터럴을 직접 전달하면서 추가 속성을 넣으면 오류가 발생합니다.
printUser({
id: 1,
name: "김타입",
role: "admin",
});하지만 먼저 변수에 저장하면 전달될 수 있습니다.
const admin = {
id: 1,
name: "김타입",
role: "admin",
};
printUser(admin);왜 결과가 다를까요?
객체 리터럴을 직접 전달할 때는 오타 가능성을 줄이기 위해 초과 속성 검사가 더 엄격하게 적용됩니다.
변수로 전달할 때는 구조적 타이핑을 기준으로 필요한 속성이 있는지 확인합니다.
객체 리터럴 직접 전달
→ 알려지지 않은 속성까지 검사
기존 변수 전달
→ 필요한 구조를 만족하는지 검사42. 초과 속성 검사는 보안 기능이 아니다
다음 객체에는 `role` 속성이 있습니다.
const admin = {
id: 1,
name: "김타입",
role: "admin",
};`User` 타입 변수에 할당합니다.
const user: User =
admin;TypeScript에서 `user.role`에는 접근할 수 없습니다.
console.log(user.role);하지만 실행 중 실제 객체에서 `role` 속성이 삭제된 것은 아닙니다.
console.log(user);실제 객체에는 여전히 `role`이 존재합니다.
타입은 코드에서 어떤 속성을 안전하게 사용할 수 있는지 결정할 뿐, 런타임 객체에서 추가 속성을 제거하지 않습니다.
타입 수준
user는 User로 취급
실제 객체
role 속성도 여전히 존재따라서 초과 속성 검사는 데이터 보안이나 정보 제거 기능이 아닙니다.
43. 빈 객체 타입을 조심하자
다음 타입은 속성이 없는 객체처럼 보입니다.
type Empty = {};하지만 TypeScript에서 `{}`는 단순히 빈 객체만 뜻하지 않을 수 있습니다.
`null`과 `undefined`를 제외한 다양한 값이 할당될 수 있습니다.
let value: {};
value = "TypeScript";
value = 100;
value = true;
value = {};따라서 “아무 속성도 없는 객체”라는 의도로 `{}`를 무심코 사용하는 것은 주의해야 합니다.
일반적인 객체를 표현하고 싶다면 목적에 따라 구체적인 구조를 작성하는 것이 좋습니다.
type User = {
id: number;
name: string;
};키와 값을 알 수 없는 객체를 다루려면 이후 배울 `Record`나 인덱스 시그니처를 고려할 수 있습니다.
44. `object` 타입은 무엇일까?
소문자 `object` 타입은 원시 타입이 아닌 값을 나타냅니다.
let value: object;객체를 저장할 수 있습니다.
value = {
name: "김타입",
};배열과 함수도 객체 범주로 저장할 수 있습니다.
value = [1, 2, 3];
value = () => {};하지만 문자열, 숫자, 불리언 같은 원시 값은 저장할 수 없습니다.
value = "TypeScript";
value = 100;
value = true;문제는 `object`만으로는 내부 속성을 알 수 없다는 것입니다.
value.name;따라서 실제 데이터 구조를 알고 있다면 구체적인 객체 타입을 작성해야 합니다.
type User = {
name: string;
};const user: User = {
name: "김타입",
};`object`는 건물이라는 사실만 알려줄 뿐, 몇 층인지 어떤 방이 있는지는 알려주지 않습니다.
45. 대문자 `Object`는 피하는 것이 좋다
TypeScript에는 대문자 `Object` 타입도 존재합니다.
let value: Object;하지만 일반적인 객체 구조를 표현할 때 대문자 `Object`를 사용하는 것은 권장하지 않습니다.
타입의 범위가 지나치게 넓고 구체적인 속성 정보를 제공하지 않기 때문입니다.
다음처럼 목적에 맞는 타입을 작성하는 것이 좋습니다.
type User = {
id: number;
name: string;
};또는 원시 타입이 아닌 값이라는 의미가 필요하다면 소문자 `object`를 고려합니다.
function processObject(
value: object
): void {
console.log(value);
}대부분의 애플리케이션 데이터에는 구체적인 객체 설계도가 가장 안전합니다.
46. 객체를 수정하는 함수
사용자 상태를 변경하는 함수를 만들어 보겠습니다.
type User = {
id: number;
name: string;
isActive: boolean;
};function activateUser(
user: User
): void {
user.isActive = true;
}사용합니다.
const user: User = {
id: 1,
name: "김타입",
isActive: false,
};
activateUser(user);console.log(
user.isActive
);출력 결과:
true객체는 참조로 전달되므로 함수 내부 수정이 원본 객체에도 반영됩니다.
원본을 수정하지 않으려면 새로운 객체를 반환할 수 있습니다.
function activateUser(
user: User
): User {
return {
...user,
isActive: true,
};
}47. 읽기 전용 객체를 함수에 전달하기
함수가 사용자 정보를 읽기만 해야 한다면 매개변수 타입에 읽기 전용 속성을 사용할 수 있습니다.
type ReadonlyUser = {
readonly id: number;
readonly name: string;
readonly email: string;
};function printUser(
user: ReadonlyUser
): void {
console.log(user.name);
user.name = "변경";
}마지막 줄은 오류가 발생합니다.
하지만 속성마다 `readonly`를 반복하는 것이 번거로울 수 있습니다.
TypeScript의 유틸리티 타입을 사용하면 다음처럼 표현할 수 있습니다.
function printUser(
user: Readonly<User>
): void {
console.log(user.name);
}`Readonly<T>`는 이후 실무 TypeScript 기능에서 자세히 다룰 예정입니다.
48. 타입 별칭은 중복된 의미를 줄인다
다음 코드에서는 `number`를 여러 곳에 사용합니다.
function findUser(
id: number
): void {}
function deleteUser(
id: number
): void {}
function updateUser(
id: number
): void {}타입 자체는 간단하지만 의미를 더 분명히 만들 수 있습니다.
type UserId = number;function findUser(
id: UserId
): void {}
function deleteUser(
id: UserId
): void {}
function updateUser(
id: UserId
): void {}`UserId`를 사용하면 해당 숫자가 단순한 수량이나 가격이 아니라 사용자 번호라는 의미를 전달합니다.
다만 타입 별칭만으로 서로 다른 숫자 타입이 완전히 구분되는 것은 아닙니다.
type UserId = number;
type ProductId = number;두 타입은 모두 결국 `number`이므로 구조적으로 호환됩니다.
const userId: UserId = 1;
const productId: ProductId =
userId;타입 별칭은 의미를 개선하지만 자동으로 새로운 런타임 종류를 만들지는 않습니다.
49. 실습 1: 사용자 프로필 설계하기
사용자 역할 타입을 정의합니다.
type UserRole =
| "admin"
| "manager"
| "user";주소 타입을 정의합니다.
type Address = {
city: string;
district: string;
detail?: string;
};사용자 타입을 정의합니다.
type User = {
readonly id: number;
name: string;
email: string;
role: UserRole;
nickname?: string;
address?: Address;
isActive: boolean;
};사용자 객체를 만듭니다.
const user: User = {
id: 1,
name: "김타입",
email: "type@example.com",
role: "admin",
nickname: "타입마스터",
address: {
city: "서울",
district: "강남구",
},
isActive: true,
};사용자 정보를 출력하는 함수를 만듭니다.
function printUser(
user: User
): void {
const displayName =
user.nickname
?? user.name;
const city =
user.address?.city
?? "지역 미등록";
console.log(
`사용자: ${displayName}`
);
console.log(
`역할: ${user.role}`
);
console.log(
`지역: ${city}`
);
}함수를 호출합니다.
printUser(user);출력 결과:
사용자: 타입마스터
역할: admin
지역: 서울50. 실습 2: 상품 데이터 설계하기
상품 상태 타입을 정의합니다.
type ProductStatus =
| "sale"
| "sold-out"
| "hidden";상품 타입을 정의합니다.
type Product = {
readonly id: number;
name: string;
price: number;
stock: number;
status: ProductStatus;
description?: string;
};상품 목록을 만듭니다.
const products: Product[] = [
{
id: 1,
name: "기계식 키보드",
price: 120000,
stock: 10,
status: "sale",
},
{
id: 2,
name: "무선 마우스",
price: 60000,
stock: 0,
status: "sold-out",
},
{
id: 3,
name: "모니터",
price: 350000,
stock: 5,
status: "sale",
description:
"27인치 고해상도 모니터",
},
];판매 중인 상품만 선택합니다.
const saleProducts =
products.filter(
(product) =>
product.status === "sale"
);상품 메시지를 만듭니다.
const productMessages =
saleProducts.map(
(product) => {
return (
`${product.name}: ` +
`${product.price.toLocaleString()}원`
);
}
);출력합니다.
productMessages.forEach(
(message) => {
console.log(message);
}
);51. 실습 3: 주문 데이터 설계하기
주문 상품 타입을 정의합니다.
type OrderItem = {
productId: number;
productName: string;
price: number;
quantity: number;
};배송지 타입을 정의합니다.
type ShippingAddress = {
recipient: string;
phone: string;
address: string;
detail?: string;
};주문 상태를 정의합니다.
type OrderStatus =
| "pending"
| "paid"
| "shipping"
| "delivered"
| "cancelled";주문 타입을 정의합니다.
type Order = {
readonly id: string;
userId: number;
items: OrderItem[];
status: OrderStatus;
shippingAddress: ShippingAddress;
createdAt: string;
};주문 객체를 만듭니다.
const order: Order = {
id: "ORDER-2026-001",
userId: 1,
items: [
{
productId: 101,
productName:
"기계식 키보드",
price: 120000,
quantity: 1,
},
{
productId: 102,
productName:
"무선 마우스",
price: 60000,
quantity: 2,
},
],
status: "paid",
shippingAddress: {
recipient: "김타입",
phone: "010-0000-0000",
address: "서울시 강남구",
},
createdAt:
"2026-08-02T12:00:00",
};주문 합계를 계산합니다.
function calculateOrderTotal(
order: Order
): number {
return order.items.reduce(
(total, item) => {
return (
total +
item.price *
item.quantity
);
},
0
);
}const totalPrice =
calculateOrderTotal(order);
console.log(
`주문 금액: ${
totalPrice.toLocaleString()
}원`
);출력 결과:
주문 금액: 240,000원52. 실습 4: 게시글과 작성자 설계하기
작성자 타입을 정의합니다.
type Author = {
readonly id: number;
name: string;
profileImage?: string;
};게시글 상태를 정의합니다.
type PostStatus =
| "draft"
| "published"
| "archived";게시글 타입을 정의합니다.
type Post = {
readonly id: number;
title: string;
content: string;
author: Author;
tags: string[];
status: PostStatus;
createdAt: string;
updatedAt?: string;
};게시글 객체를 만듭니다.
const post: Post = {
id: 1,
title:
"TypeScript 객체 타입 이해하기",
content:
"객체 타입과 type 별칭을 배웁니다.",
author: {
id: 10,
name: "김타입",
},
tags: [
"TypeScript",
"객체",
"타입별칭",
],
status: "published",
createdAt:
"2026-08-02",
};게시글 요약을 출력합니다.
function printPostSummary(
post: Post
): void {
const authorImage =
post.author.profileImage
?? "/images/default.png";
console.log(post.title);
console.log(
`작성자: ${post.author.name}`
);
console.log(
`프로필: ${authorImage}`
);
console.log(
`태그: ${post.tags.join(", ")}`
);
}53. 자주 발생하는 실수
실수 1. 객체 타입을 매번 반복하기
const firstUser: {
id: number;
name: string;
} = {
id: 1,
name: "김타입",
};
const secondUser: {
id: number;
name: string;
} = {
id: 2,
name: "이컴파일",
};반복되는 구조에는 이름을 붙입니다.
type User = {
id: number;
name: string;
};실수 2. 선택적인 값을 필수 속성으로 만들기
type User = {
id: number;
name: string;
nickname: string;
};닉네임이 없는 사용자까지 억지로 빈 문자열을 넣게 될 수 있습니다.
const user: User = {
id: 1,
name: "김타입",
nickname: "",
};정말 선택 사항이라면 `?`를 사용합니다.
type User = {
id: number;
name: string;
nickname?: string;
};실수 3. 선택적 속성을 확인하지 않고 사용하기
console.log(
user.nickname.toUpperCase()
);`nickname`이 없을 수 있습니다.
const nickname =
user.nickname
?? "닉네임 없음";실수 4. 변경되면 안 되는 ID를 일반 속성으로 만들기
type User = {
id: number;
name: string;
};user.id = 999;ID가 고정되어야 한다면 `readonly`를 사용합니다.
type User = {
readonly id: number;
name: string;
};실수 5. `readonly`가 중첩 객체까지 자동 적용된다고 생각하기
type Config = {
readonly database: {
host: string;
};
};`database` 객체를 통째로 교체할 수는 없지만 내부 `host`는 변경할 수 있습니다.
config.database.host =
"new-host";내부 속성에도 `readonly`를 작성해야 합니다.
실수 6. 타입 별칭이 실행 중에도 존재한다고 생각하기
type User = {
id: number;
name: string;
};
console.log(User);`User`는 타입 영역에서만 존재하므로 값처럼 사용할 수 없습니다.
실수 7. 외부 데이터를 바로 객체 타입으로 단언하기
const user =
JSON.parse(text)
as User;실제 데이터 구조는 검증되지 않습니다.
const data: unknown =
JSON.parse(text);외부 데이터에는 런타임 검증이 필요합니다.
실수 8. 모든 객체에 `object`를 사용하기
function printUser(
user: object
): void {
console.log(user.name);
}`object` 타입만으로는 `name` 속성이 있다는 사실을 알 수 없습니다.
구체적인 타입을 작성합니다.
function printUser(
user: User
): void {
console.log(user.name);
}실수 9. 타입 하나에 너무 많은 책임을 넣기
type User = {
id: number;
name: string;
email: string;
city: string;
district: string;
zipCode: string;
paymentCardNumber: string;
paymentCardCompany: string;
deliveryMessage: string;
};관련된 구조를 별도 타입으로 나눌 수 있습니다.
type Address = {
city: string;
district: string;
zipCode: string;
};
type PaymentMethod = {
cardNumber: string;
company: string;
};작은 타입을 조합하면 구조가 더 명확해집니다.
54. 객체 타입 설계 공식
객체 타입을 만들 때 다음 질문을 확인해 보세요.
질문 1. 함께 움직이는 데이터인가?
관련된 값이라면 객체로 묶습니다.
type User = {
id: number;
name: string;
};질문 2. 같은 구조를 두 번 이상 사용하는가?
`type` 별칭으로 이름을 붙입니다.
type Product = {
id: number;
name: string;
price: number;
};질문 3. 반드시 있어야 하는 속성인가?
필수 속성으로 작성합니다.
name: string;질문 4. 없어도 되는 속성인가?
선택적 속성으로 작성합니다.
nickname?: string;질문 5. 생성 후 변경되면 안 되는가?
`readonly`를 고려합니다.
readonly id: number;질문 6. 중첩 구조가 다른 곳에서도 사용되는가?
별도 타입으로 분리합니다.
type Address = {
city: string;
district: string;
};질문 7. 허용 가능한 값이 제한되어 있는가?
리터럴 유니언 타입을 사용합니다.
type Role =
| "admin"
| "user";질문 8. 객체가 기능도 가지고 있는가?
메서드 타입을 작성합니다.
greet(): string;질문 9. 외부에서 들어온 데이터인가?
타입 단언만 하지 말고 실제 구조를 검증합니다.
질문 10. 타입이 지나치게 커졌는가?
작은 타입으로 분리한 뒤 조합합니다.
55. 미니 퀴즈
문제 1
다음 객체 타입에서 필수 속성은 무엇일까요?
type User = {
id: number;
name: string;
nickname?: string;
};정답
`id`와 `name`은 필수입니다.
`nickname`은 선택 사항입니다.
문제 2
다음 코드에서 오류가 발생하는 이유는 무엇일까요?
type Product = {
id: number;
name: string;
price: number;
};
const product: Product = {
id: 1,
name: "키보드",
};정답
필수 속성인 `price`가 빠졌기 때문입니다.
문제 3
다음 코드에서 오류가 발생할까요?
type User = {
readonly id: number;
name: string;
};
const user: User = {
id: 1,
name: "김타입",
};
user.name = "이타입";정답
오류가 발생하지 않습니다.
`name`은 읽기 전용 속성이 아니므로 변경할 수 있습니다.
문제 4
다음 코드에서 오류가 발생할까요?
user.id = 2;정답
오류가 발생합니다.
`id`는 `readonly` 속성이기 때문입니다.
문제 5
다음 두 타입의 차이는 무엇일까요?
type First = {
nickname?: string;
};type Second = {
nickname:
string | undefined;
};정답
`First`에서는 `nickname` 속성 자체를 생략할 수 있습니다.
`Second`에서는 속성이 반드시 존재해야 하지만 값으로 `undefined`를 사용할 수 있습니다.
문제 6
다음 코드에서 `User`를 출력할 수 있을까요?
type User = {
id: number;
};
console.log(User);정답
출력할 수 없습니다.
`User`는 타입 영역에만 존재하며 JavaScript 실행 중에는 사라집니다.
문제 7
다음 객체는 `User` 타입 함수에 전달할 수 있을까요?
type User = {
id: number;
name: string;
};
const admin = {
id: 1,
name: "김타입",
role: "admin",
};function printUser(
user: User
): void {}
printUser(admin);정답
전달할 수 있습니다.
`admin` 객체가 `User`에 필요한 `id`와 `name` 구조를 모두 가지고 있기 때문입니다.
문제 8
다음 타입을 `type` 별칭으로 분리해 보세요.
const product: {
id: number;
name: string;
price: number;
} = {
id: 1,
name: "키보드",
price: 120000,
};정답 예시
type Product = {
id: number;
name: string;
price: number;
};
const product: Product = {
id: 1,
name: "키보드",
price: 120000,
};문제 9
다음 타입에서 주소를 별도 타입으로 분리해 보세요.
type User = {
id: number;
address: {
city: string;
district: string;
};
};정답 예시
type Address = {
city: string;
district: string;
};
type User = {
id: number;
address: Address;
};문제 10
다음 코드의 문제는 무엇일까요?
function printUser(
user: object
): void {
console.log(
user.name
);
}정답
`object` 타입만으로는 `name` 속성이 있다고 보장할 수 없습니다.
구체적인 객체 타입을 사용해야 합니다.
function printUser(
user: User
): void {
console.log(
user.name
);
}56. 핵심 정리
객체 타입
객체의 속성 이름과 타입을 정의합니다.
const user: {
id: number;
name: string;
} = {
id: 1,
name: "김타입",
};`type` 별칭
반복되는 타입에 이름을 붙입니다.
type User = {
id: number;
name: string;
};타입 재사용
const firstUser: User = {
id: 1,
name: "김타입",
};
const secondUser: User = {
id: 2,
name: "이컴파일",
};선택적 속성
type User = {
id: number;
nickname?: string;
};속성 자체가 없을 수 있습니다.
읽기 전용 속성
type User = {
readonly id: number;
name: string;
};객체 생성 후 `id`를 다시 할당할 수 없습니다.
중첩 객체 타입
type Address = {
city: string;
district: string;
};
type User = {
id: number;
address: Address;
};객체 배열
const users: User[] = [
{
id: 1,
name: "김타입",
},
];메서드 타입
type User = {
name: string;
greet(): string;
};구조적 타이핑
타입 이름보다 실제 객체 구조를 기준으로 호환성을 판단합니다.
const admin = {
id: 1,
name: "김타입",
role: "admin",
};
printUser(admin);초과 속성 검사
객체 리터럴을 직접 할당하거나 전달할 때 정의되지 않은 속성을 검사합니다.
const user: User = {
id: 1,
name: "김타입",
role: "admin",
};`User`에 `role`이 없다면 오류가 발생합니다.
57. 마무리
객체는 서로 관련된 여러 값을 하나로 묶습니다.
const user = {
id: 1,
name: "김타입",
email: "type@example.com",
};객체 타입은 그 객체가 어떤 속성을 가져야 하는지 정의합니다.
{
id: number;
name: string;
email: string;
}같은 구조를 반복해서 사용한다면 `type` 별칭으로 이름을 붙일 수 있습니다.
type User = {
readonly id: number;
name: string;
email: string;
nickname?: string;
};이제 `User`라는 설계도를 변수, 함수 매개변수, 반환 타입, 배열 등 다양한 곳에서 재사용할 수 있습니다.
const user: User = {
id: 1,
name: "김타입",
email: "type@example.com",
};function printUser(
user: User
): void {
console.log(user.name);
}const users: User[] = [
user,
];이번 편의 핵심을 한 문장으로 정리하면 다음과 같습니다.
객체 타입은 데이터의 구조를 정의하고, type 별칭은 그 구조에 의미 있는 이름을 붙인다.
선택적 속성 `?`는 비어 있는 방을 허용합니다.
`readonly`는 중요한 시설의 무단 이동을 막습니다.
중첩 타입은 큰 설계도를 작은 도면으로 나눕니다.
구조적 타이핑은 건물 이름보다 실제 시설을 확인합니다.
좋은 객체 타입은 단순히 오류를 막는 장치가 아닙니다.
코드를 읽는 사람에게 데이터의 구성과 규칙을 알려주는 살아 있는 문서입니다. 📐
58. 다음 편 예고
지금까지 우리는 `type`을 사용해 객체 구조에 이름을 붙였습니다.
type User = {
readonly id: number;
name: string;
email: string;
nickname?: string;
};그런데 TypeScript에는 객체의 구조를 정의하는 또 다른 문법이 있습니다.
interface User {
readonly id: number;
name: string;
email: string;
nickname?: string;
}`interface`도 객체의 설계도를 만들 수 있습니다.
const user: User = {
id: 1,
name: "김타입",
email: "type@example.com",
};그렇다면 `type`과 `interface`는 같은 기능일까요?
type Product = {
id: number;
name: string;
};interface Product {
id: number;
name: string;
}두 문법 중 무엇을 사용해야 할까요?
기존 인터페이스를 확장해 새로운 속성을 추가할 수도 있습니다.
interface AdminUser
extends User {
permissions: string[];
}같은 이름의 인터페이스를 여러 번 선언하면 어떻게 될까요?
interface User {
id: number;
}
interface User {
name: string;
}`type` 별칭에서도 같은 방식이 가능할까요?
다음 편에서는 TypeScript에서 객체 설계도를 만드는 또 다른 핵심 문법인 `interface`를 만나보겠습니다.
다음 이야기
[TypeScript 완전정복 #13] 객체 설계도의 또 다른 문법 | interface와 type의 차이 제대로 이해하기
- `interface`는 무엇일까요?
- 인터페이스로 객체 타입을 어떻게 정의할까요?
- 선택적 속성과 읽기 전용 속성도 사용할 수 있을까요?
- 메서드는 어떻게 정의할까요?
- `interface`를 다른 인터페이스로 확장하려면 어떻게 해야 할까요?
- 다중 확장은 가능할까요?
- 선언 병합은 무엇일까요?
- `type`과 `interface`의 공통점은 무엇일까요?
- 두 문법의 차이는 무엇일까요?
- 실무에서는 어떤 기준으로 선택해야 할까요?
다음 편에서는 닮은 듯 다른 두 설계도 제작 도구, `type`과 `interface`를 나란히 올려놓고 어디에서 같고 어디에서 갈라지는지 살펴보겠습니다.
