본문으로 건너뛰기

객체 타입과 interface

타입스크립트 목차객체의 모양레슨 13 / 51

목차

객체 타입(object type)은 객체가 가져야 할 속성의 이름과 타입을 한 줄씩 적은 것입니다. interface Menu처럼 이름을 붙여 적어 두면, 검사기는 Menu 자리에 오는 객체를 그 줄들과 하나씩 맞춰 보고 빠진 속성 · 타입이 다른 속성 · 없는 속성을 실행 전에 짚습니다.

JS 다시 보기: 객체에 없는 속성을 꺼내면 오류 없이 undefined가 나옵니다(JS '객체: 이름 붙은 칸 묶음'). 그래서 자바스크립트에서는 속성 이름을 잘못 쓰거나 price를 빠뜨린 객체를 넘겨도 멈추지 않고, undefined가 한참 뒤에 NaN이나 undefined원으로 드러납니다.

이 단원을 이미 안다면 단원 연습 문제의 진단 문항부터 풀고 틀린 레슨만 보세요.

7단계 중 1단계. 이 그림 읽는 법: '타입' 카드는 모양, '값' 카드는 객체입니다. 줄 = 속성.

1interface로 객체의 모양 적기

메뉴 한 줄은 이름(name)과 값(price)을 가진 객체입니다. 이 모양에 Menu라는 이름을 붙여 적습니다.

interface Menu {
  name: string;
  price: number;
}

const latte: Menu = {
  name: '라테',
  price: 4500,
};
console.log(latte.price);
실행 결과
4500

1~4번 줄의 interface Menu는 'Menu는 string 타입 name 속성과 number 타입 price 속성을 가진 모양'이라는 뜻입니다. 한 줄이 속성 하나이고, 이 과목은 이 줄을 속성 줄이라고 부릅니다. 6번 줄의 latte: Menu로 latte의 선언 타입이 Menu가 되어, 검사기는 오른쪽 객체를 Menu의 속성 줄과 하나씩 맞춰 봅니다. 두 줄이 맞아 검사를 지나갑니다(검사 규칙 2).

interface와 객체는 닮았지만 하는 일이 다릅니다.

interface(타입 쪽)객체(값 쪽)
: 뒤에 오는 것타입 — string값 — '라테'
속성 사이;(쉼표도 받음),
실행할 때지워져 없음있음

타입을 적지 않은 객체의 타입은 { name: string; }처럼 속성 줄을 중괄호 안에 늘어놓은 꼴로 찍힙니다. type Menu = { … };처럼 type으로도 모양에 이름을 붙일 수 있지만, 이 과목은 객체 모양에는 interface를, 유니언 · 함수 타입에는 type을 씁니다.

2없는 속성과 빠진 속성

검사기는 속성을 꺼내는 줄에서도 모양을 봅니다. 1절 코드의 마지막 줄에서 price를 prise로 잘못 썼습니다.

interface Menu {
  name: string;
  price: number;
}

const latte: Menu = {
  name: '라테',
  price: 4500,
};
console.log(latte.prise);
검사 결과 오류 1개
main.ts(10,19): error TS2551: Property 'prise' does not exist on type 'Menu'. Did you mean 'price'?

Menu 모양에 prise 줄이 없다는 TS2551로, 첫 레슨에서 본 오류입니다. 그때 긴 타입이 찍혔던 자리에 이제 이름 Menu가 찍힙니다.

이번에는 객체 쪽에서 price 줄이 빠졌습니다. 검사기는 몇 번 줄을 짚을까요? 9번 줄의 tea.price는 검사기에게 어떤 타입으로 보이고, Node로 돌리면 무엇을 찍을까요? 답을 정한 뒤 펼쳐 보세요.

interface Menu {
  name: string;
  price: number;
}

const tea: Menu = {
  name: '녹차',
};
console.log(tea.price);
검사 결과와 실행 결과 보기
검사 결과 오류 1개
main.ts(6,7): error TS2741: Property 'price' is missing in type '{ name: string; }' but required in type 'Menu'.
실행 결과
undefined

예상과 다르면 그곳이 배울 곳입니다. Property 'price' is missing⁠은 'price 속성이 빠졌다'는 뜻이고, 문장 전체는 '{ name: string; } 타입에는 price 속성이 없는데 Menu 타입에서는 꼭 있어야 한다'입니다. 곧 6번 줄의 객체에서 price 줄이 빠졌다는 뜻입니다. 검사기는 객체를 Menu 자리에 넣는 6번 줄을 짚고, 9번 줄은 짚지 않습니다. tea의 선언 타입이 Menu라서 9번 줄에서 tea.price의 지금 타입은 number입니다. 검사기는 적힌 타입을 믿고 읽습니다. Node는 검사하지 않고 돌려, 없는 속성 price를 꺼낸 undefined를 찍습니다. 자바스크립트였다면 오류 없이 지나갔을 곳입니다.

3들여 쓴 설명 줄 — 어느 속성이 왜 안 맞나

속성의 타입이 다르면 오류가 한 줄로 끝나지 않을 수 있습니다. 아래 latte는 타입 없이 만든 뒤 10번 줄에서 Menu 자리에 넣었는데, price가 글자 '4500'입니다.

interface Menu {
  name: string;
  price: number;
}

const latte = {
  name: '라테',
  price: '4500',
};
const menu: Menu = latte;
console.log(menu.price + 500);
검사 결과 오류 1개
main.ts(10,7): error TS2322: Type '{ name: string; price: string; }' is not assignable to type 'Menu'.  Types of property 'price' are incompatible.    Type 'string' is not assignable to type 'number'.
실행 결과
4500500

첫 줄은 'latte의 타입을 Menu 타입에 넣을 수 없다'까지만 말하고, 어느 속성 때문인지는 아래 들여 쓴 줄에 있습니다. 두 칸 들인 둘째 줄은 'price 속성의 타입이 서로 맞지 않는다', 네 칸 들인 셋째 줄은 'string 타입은 number 타입에 넣을 수 없다'입니다. 한 단계 더 들일수록 더 좁은 곳을 말합니다. 그래서 오류는 '파일(줄,열) → 오류 번호 → 문장' 다음에 넷째 조각, 들여 쓴 설명 줄까지 읽습니다. 고칠 곳은 오류가 난 10번 줄이 아니라 price가 글자로 정해진 8번 줄입니다. Node는 글자 '4500'에 500을 이어 붙인 4500500을 찍습니다.

타입을 적은 자리에 객체를 바로 쓰면 오류 자리가 다릅니다. 검사기가 속성 줄마다 바로 맞춰 보고, 타입이 다른 속성은 그 속성 줄을 설명 줄 없이 한 줄로 짚습니다. 빠진 속성은 짚을 줄이 없어 2절처럼 객체를 넣는 줄을 짚습니다.

예제

메뉴 한 줄을 받는 함수

매개변수에 Menu를 적으면 함수 안에서 item이 가진 속성 줄을 검사기가 압니다. 부르는 쪽에서는 넘기는 객체를 Menu 모양과 맞춰 봅니다(검사 규칙 2).

interface Menu {
  name: string;
  price: number;
}

function label(item: Menu) {
  const won = `${item.price}원`;
  return `${item.name} ${won}`;
}

const tea: Menu = {
  name: '녹차',
  price: 3500,
};
console.log(label(tea));
실행 결과
녹차 3500원

자주 틀리는 곳

틀린 생각: 객체 값에도 속성마다 타입 이름을 적는다.

interface와 객체가 같은 이름: 꼴이라, 값을 만들 때도 : 뒤에 타입을 쓰게 됩니다.

const latte = { name: string };
console.log(latte);
검사 결과 오류 1개
main.ts(1,23): error TS2693: 'string' only refers to a type, but is being used as a value here.

only refers to a type⁠은 'string은 타입만 가리킨다', 곧 타입 자리에만 쓰는 이름을 값 자리에 썼다는 뜻입니다.

맞는 규칙: 타입 이름은 검사기만 읽고 실행할 때는 없어서 값 자리에 쓸 수 없습니다. 객체의 : 뒤에는 '라테' 같은 값을 쓰고, 타입은 interface 안과 이름 뒤 : Menu에 적습니다(검사 규칙 1).

틀린 생각: interface도 실행할 때 남아 있어 값처럼 찍어 볼 수 있다.

interface Menu도 코드에 적은 이름이라 console.log로 찍힐 것 같습니다.

interface Menu {
  name: string;
  price: number;
}

console.log(Menu);
검사 결과 오류 1개
main.ts(6,13): error TS2693: 'Menu' only refers to a type, but is being used as a value here.
실행 결과
main.ts:6console.log(Menu);            ^ ReferenceError: Menu is not defined    at main.ts:6:13

검사기는 타입 이름 Menu를 값 자리에 썼다고 짚습니다. Node는 interface 네 줄을 지운 코드를 돌리므로 Menu라는 이름을 찾지 못합니다.

맞는 규칙: interface는 검사기가 모양을 맞춰 볼 때만 쓰고, Node는 지운 뒤에 돌립니다. 그래서 Menu는 타입 자리에만 쓰고, 실행 중에 찍거나 값처럼 쓸 수 없습니다(검사 규칙 1).

틀린 생각: 구조 분해 매개변수 안의 : 뒤에 속성의 타입을 적는다.

JS 다시 보기: 구조 분해 안의 { name: menuName }은 name 속성을 꺼내 menuName이라는 이름을 붙입니다(JS '구조 분해로 꺼내기'). 매개변수를 구조 분해로 받으면서 속성마다 타입을 붙이려고 :을 쓰면, 그 :은 이름 바꾸기로 읽힙니다.

interface Menu {
  name: string;
  price: number;
}

function show({
  name: string,
  price: number,
}) {
  console.log(price);
}
검사 결과 오류 3개
main.ts(7,9): error TS7031: Binding element 'string' implicitly has an 'any' type.main.ts(8,10): error TS7031: Binding element 'number' implicitly has an 'any' type.main.ts(10,15): error TS2304: Cannot find name 'price'.

7번 줄의 name: string은 'name 속성을 꺼내 string이라는 이름을 붙인다'로 읽힙니다. 그래서 TS7031은 '꺼낸 이름 string의 타입이 정해지지 않아 any가 됐다'고 짚고, TS2304는 price라는 이름이 없다고 짚습니다.

고친 코드 보기
interface Menu {
  name: string;
  price: number;
}

function show({
  name,
  price,
}: Menu) {
  console.log(price);
}
검사 결과 오류 없음

검사는 오류 없이 지나갑니다.

맞는 규칙: 구조 분해 안의 :은 자바스크립트의 이름 바꾸기라 타입 자리가 아닙니다. 타입은 구조 분해를 닫은 뒤 }: Menu처럼 한 번에 붙입니다.

확인 문제

1. 1절 코드에서 8번 줄의 4500을 글자 '4500'으로 바꿨습니다. 검사를 지나갈까요? 막힌다면 몇 번 줄일까요?

interface Menu {
  name: string;
  price: number;
}

const latte: Menu = {
  name: '라테',
  price: '4500',
};
console.log(latte.price);
힌트

3절은 타입 없이 만든 객체를 10번 줄에서 넣었습니다. 이번에는 타입을 적은 자리에 객체를 바로 씁니다.

정답

8번 줄에서 막힙니다.

검사 결과 오류 1개
main.ts(8,3): error TS2322: Type 'string' is not assignable to type 'number'.

타입을 적은 자리에 객체를 바로 쓰면 검사기는 속성 줄마다 바로 맞춰 보고, 맞지 않는 price 줄을 짚습니다. 그래서 설명 줄 없이 'string 타입은 number 타입에 넣을 수 없다' 한 줄이 납니다.

흔한 오답은 6번 줄입니다. 3절처럼 객체 전체를 넣는 줄에서 막힌다고 본 것입니다. 3절은 이미 만들어 둔 latte를 넣었기 때문에 넣는 줄을 짚었습니다.

2. 빈칸 두 줄을 채워 검사를 지나게 하세요. 다 채운 코드는 3을 찍어야 합니다.

interface Order {
  ________
  ________
}

const a: Order = {
  item: '라테',
  qty: 2,
};
const b: Order = {
  item: '커피',
  qty: 1,
};
console.log(a.qty + b.qty);
힌트

6~9번 줄 객체의 속성 줄을 하나씩 보고, 값이 아니라 그 값의 타입을 적습니다.

정답
interface Order {
  item: string;
  qty: number;
}

const a: Order = {
  item: '라테',
  qty: 2,
};
const b: Order = {
  item: '커피',
  qty: 1,
};
console.log(a.qty + b.qty);
실행 결과
3

다르게 써도 되는 곳: 두 줄의 차례를 바꿔도 같습니다. 검사기는 차례가 아니라 속성 이름으로 맞춥니다. 줄 끝 ;을 ,로 써도 지나갑니다.

흔한 오답은 item: '라테'; · qty: 2;처럼 값을 적는 것입니다(타입과 값을 섞는 생각). 그러면 Order가 '라테'와 2 하나씩만 들어가는 타입이 되어, 11 · 12번 줄의 '커피'와 1에서 막힙니다. 값 하나짜리 타입은 뒤의 '리터럴 유니언으로 값 고르기' 레슨에서 다룹니다.

3. 성적을 다루는 코드입니다. 이 코드는 10번 줄에서 검사에 막힙니다. 검사 오류를 없애면서 95가 찍히게 하는 고치기는 어느 것일까요?

interface Score {
  name: string;
  math: number;
}

const kim = {
  name: '도윤',
  math: '90',
};
const s: Score = kim;
console.log(s.math + 5);
검사 결과 오류 1개
main.ts(10,7): error TS2322: Type '{ name: string; math: string; }' is not assignable to type 'Score'.  Types of property 'math' are incompatible.    Type 'string' is not assignable to type 'number'.
  • ① 3번 줄을 math: string;으로 바꾼다.
  • ② 8번 줄을 math: 90,으로 바꾼다.
  • ③ 10번 줄의 : Score를 지운다.
힌트

들여 쓴 설명 줄이 짚는 속성을 보고, 그 속성의 값이 어디서 정해졌는지 거슬러 갑니다. 고친 뒤 Node가 찍을 값도 따져 봅니다.

정답

②입니다. math가 수 90이 되어 검사를 지나고, s.math + 5가 95를 찍습니다.

interface Score {
  name: string;
  math: number;
}

const kim = {
  name: '도윤',
  math: 90,
};
const s: Score = kim;
console.log(s.math + 5);
실행 결과
95

①과 ③도 검사 오류를 없앱니다. 그러나 math는 여전히 글자 '90'이라 Node가 905를 찍습니다. ①은 타입을 틀린 값에 맞췄고, ③은 타입을 지워 자바스크립트로 돌아갔습니다(검사를 지나면 맞다는 생각). 검사를 지난 뒤에도 값으로 돌려 결과를 맞춰 봅니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 1절 코드를 main.ts로 저장해 npx tsc로 검사하고 node main.ts로 돌립니다. 그다음 8번 줄 price: 4500,을 지우면 검사 결과와 실행 결과가 어떻게 바뀔지 먼저 적고, 다시 검사하고 돌려서 맞춰 봅니다. 검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

타입스크립트 목차로