본문으로 건너뛰기

any는 검사를 끈다

타입스크립트 목차바깥에서 온 값 — any·unknown·as레슨 27 / 51

목차

any는 검사기가 아무것도 묻지 않는 타입입니다. any인 값과 그 값에서 꺼낸 값은 검사 없이 지나가서, 속성 이름을 잘못 써도 실행할 때에야 드러납니다. JSON 글을 객체로 바꾸는 JSON.parse가 바로 이 any를 돌려줍니다.

작은 주문표 만들기까지 예제의 값은 모두 코드에 적혀 있어 검사기가 미리 볼 수 있었습니다. 실제 프로그램은 파일이나 서버에서 JSON 글을 받아 씁니다. 그 글에 무엇이 들었는지는 실행할 때 정해집니다. 이 단원은 이렇게 바깥에서 온 값을 다룹니다. 이 레슨에서는 any가 검사를 어떻게 바꾸는지 보고, 검사 규칙 6을 세웁니다.

JS 다시 보기: JSON.parse는 JSON 글에서 새 객체를 만들고, 깨진 글이면 SyntaxError를 던집니다. 던진 오류는 try·catch로 잡습니다(JS 'JSON: 객체와 글 사이' · JS 'try와 catch' · JS 'throw로 오류 알리기').

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

4단계 중 1단계. 3번 줄: JSON.parse가 돌려준 값이라 data의 타입은 any입니다.

1JSON.parse는 any를 돌려준다

메뉴 한 줄을 JSON 글로 받았다고 합시다. 실제로는 서버에서 받아 오지만(JS 'fetch로 데이터 받아 오기'), 이 과목은 돌려 볼 수 있게 받은 글을 코드에 직접 적습니다.

const text =
  '{"name":"라테","price":4500}';
const data = JSON.parse(text);
const price = data.price;
console.log(price.toFixed(0));
실행 결과
4500

1~2번 줄은 한 문장을 폰 화면에 맞추려고 = 뒤에서 나눈 것입니다. 편집기에서 3번 줄의 data에 마우스를 올리면 지금 타입이 any로 보입니다. 타입스크립트가 JSON.parse가 돌려주는 타입을 any로 정해 두었기 때문입니다. 글 안이 어떤 모양인지는 글을 읽어 봐야 알고, 글을 읽는 일은 실행할 때 일어납니다.

any는 '어떤 타입이든 될 수 있다'는 표시입니다. 이 예제는 글이 짐작한 모양 그대로라 결과도 맞았습니다. 짐작이 어긋나면 어떻게 되는지 다음 절에서 봅니다.

2any에는 검사기가 묻지 않는다

그림의 코드는 1절 코드와 한 글자만 다릅니다. 4번 줄에서 price를 prise로 잘못 썼습니다.

const text =
  '{"name":"라테","price":4500}';
const data = JSON.parse(text);
const price = data.prise;
console.log(price.toFixed(0));
검사 결과 오류 없음
실행 결과
main.ts:5console.log(price.toFixed(0));                  ^ TypeError: Cannot read properties of undefined (reading 'toFixed')    at main.ts:5:19

타입 검사기가 하는 일에서는 같은 오타를 검사기가 TS2551로 짚었습니다. 이번에는 검사 결과가 오류 없음입니다. data가 any라서 검사기가 prise라는 속성이 있는지 묻지 않았습니다. any인 값에서 꺼낸 값도 any입니다. 4번 줄의 price에 마우스를 올리면 any이고, 그래서 5번 줄의 toFixed()도 검사기는 묻지 않습니다.

실행하면 data.prise는 undefined라, 5번 줄에서 undefined의 toFixed를 꺼내려다 멈춥니다. TypeError 줄은 'undefined의 속성을 읽을 수 없다(toFixed를 읽다가)', 곧 price가 undefined였다는 뜻입니다.

검사 규칙 6. 검사기는 코드에 쓴 것만 믿습니다. 바깥에서 온 값은 검사기도 몰라서 JSON.parse는 any를 돌려줍니다. any는 검사를 끕니다 — any인 값과 그 값에서 꺼낸 값에는 검사기가 아무것도 묻지 않습니다.

'코드에 쓴 것'은 적은 타입과, JSON.parse처럼 타입스크립트가 정해 둔 타입입니다. 이 단원의 뒤 레슨에서 만나는 as와 확인 함수도 이 규칙으로 읽습니다.

3any는 타입을 적은 자리에도 들어간다

any인 값은 꺼낸 값뿐 아니라, 그 값을 넘기는 자리로도 검사 없이 들어갑니다. 다음 코드는 주문에 한 잔을 더한 잔 수를 셉니다. 받은 글의 qty가 따옴표 안 "2", 곧 글자입니다.

function addOne(qty: number) {
  return qty + 1;
}
const text =
  '{"item":"라테","qty":"2"}';
const order = JSON.parse(text);
console.log(addOne(order.qty));

검사를 지나갈까요? 지나가지 못하면 몇 번 줄에서 막힐까요? Node로 돌리면 무엇이 찍힐까요?

검사 결과와 실행 결과 보기
검사 결과 오류 없음
실행 결과
21

예상과 다르면 그곳이 배울 곳입니다. 검사는 오류 없이 지나갑니다. addOne의 qty 자리는 number인데, 넘긴 order.qty가 any라 검사기는 넣을 수 있는지 묻지 않습니다. any인 값은 어느 타입 자리에든 넣을 수 있습니다. 실행하면 qty가 붙은 값은 글자 '2'라, 2번 줄의 +가 1을 이어 붙여 21을 찍습니다. 3잔이어야 할 자리입니다. 타입을 적어 둔 addOne 안까지 틀린 값이 검사 없이 들어갔습니다.

예제

검사 결과와 실행 결과를 먼저 적어 본 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.

주문 글을 받아 금액을 셈합니다. 그런데 받은 글에 가격이 없습니다.

const text =
  '{"item":"라테","qty":2}';
const order = JSON.parse(text);
const won =
  order.qty * order.price;
console.log(won);

검사를 지나갈까요? 지나가지 못하면 몇 번 줄에서 막힐까요? Node로 돌리면 무엇이 찍힐까요?

검사 결과와 실행 결과 보기
검사 결과 오류 없음
실행 결과
NaN

예상과 다르면 그곳이 배울 곳입니다. 검사는 지나갑니다. order가 any라 검사기는 price가 있는지 묻지 않습니다. Node도 멈추지 않습니다. order.price가 undefined라 2에 undefined를 곱한 값이 NaN(수가 아님)입니다. 5번 줄에서 막히리라 봤다면 any를 검사하는 보통 타입으로 본 것입니다(any가 무난하다는 생각). 이런 실수는 결과를 손으로 셈한 값과 맞춰 봐야 찾습니다.

자주 틀리는 곳

틀린 생각: any는 아무 값이나 받아 주는 무난한 타입이다.

값이 여러 타입일 수 있을 때, 다 받아 주는 any가 편해 보입니다. 그러나 any는 값을 받기만 하지 않고 그 이름의 검사를 끕니다.

function show(v: any) {
  return v.toUpperCase();
}
console.log(show('a7'));
console.log(show(7));
검사 결과 오류 없음
실행 결과
A7main.ts:2  return v.toUpperCase();           ^ TypeError: v.toUpperCase is not a function    at show (main.ts:2:12)    at main.ts:5:13

유니언 — 여럿 가운데 하나에서 string | number로 적었을 때는 검사기가 toUpperCase()를 부른 줄을 막았습니다. any로 적으면 검사는 지나가고, 수 7을 넘긴 5번 줄의 부르기가 실행에서 멈춥니다.

맞는 규칙: any는 그 값의 검사를 끄는 타입입니다. 여러 타입이 올 수 있으면 유니언으로 적고 확인해 좁힙니다(검사 규칙 6).

틀린 생각: JSON.parse는 받은 글에 맞는 타입을 돌려준다.

코드에 적힌 글 안에 name과 price가 보이니, 검사기도 그 모양을 알 것 같습니다. 검사기는 글 안을 읽지 않습니다.

맞는 규칙: JSON.parse가 돌려주는 타입은 글과 상관없이 늘 any입니다(1절). 글 안이 어떤 모양인지는 실행할 때 코드로 확인합니다.

틀린 생각: any는 any라고 적힌 그 이름에서 그친다.

any인 이름은 data 하나뿐이니, 나머지 코드는 여전히 검사를 받을 것 같습니다.

맞는 규칙: any인 값에서 꺼낸 값도 any이고, any인 값은 어느 타입 자리에든 들어갑니다(2 · 3절). 그래서 any 하나가 코드 여러 곳의 검사를 끕니다.

틀린 생각: 검사 오류는 any나 // @ts-ignore를 붙여 없애면 된다.

검색하면 오류 줄 바로 위에 // @ts-ignore 한 줄을 붙이는 글을 만납니다. 이 주석은 바로 아래 줄의 검사 오류를 지웁니다.

// @ts-ignore
const price: number = '만 원';
console.log(typeof price);
검사 결과 오류 없음
실행 결과
string

타입은 실행 전에 지워진다에서 TS2322로 막혔던 줄이 검사를 지났습니다. 그러나 price가 붙은 값은 여전히 글자 '만 원'입니다. 2번 줄의 : number를 : any로 바꿔 덮어도 마찬가지입니다.

맞는 규칙: any와 // @ts-ignore는 검사 결과에서 오류만 지우고, 값과 실행 결과는 그대로 둡니다. 검사 오류는 까닭을 찾아 값이나 타입을 고쳐서 없앱니다(검사 규칙 6).

확인 문제

1. 2절 코드의 4번 줄을 data.price로 고쳤습니다. 그런데 이번에는 받은 글 쪽에 prise라고 잘못 적혀 왔습니다. 검사를 지나갈까요? 지나가지 못하면 몇 번 줄에서 막힐까요? Node로 돌리면 무엇이 찍힐까요?

const text =
  '{"name":"라테","prise":4500}';
const data = JSON.parse(text);
const price = data.price;
console.log(price.toFixed(0));
힌트

3번 줄 data의 타입부터 정합니다. 그다음 실행할 때 data.price에 무엇이 나오는지 글 안의 속성 이름과 견줘 봅니다.

정답
검사 결과 오류 없음
실행 결과
main.ts:5console.log(price.toFixed(0));                  ^ TypeError: Cannot read properties of undefined (reading 'toFixed')    at main.ts:5:19

검사는 지나가고, 실행은 5번 줄에서 2절과 같은 TypeError로 멈춥니다. 글 안에는 price가 없어 data.price가 undefined입니다. 검사기는 data가 any라 묻지 않고, 글 안도 읽지 않습니다. 오타가 코드에 있든 받은 글에 있든 검사 결과는 같습니다. 흔한 오답은 '2번 줄에서 막힌다'입니다. 검사기가 JSON 글 안의 이름까지 맞춰 본다고 본 것입니다.

2. 아래 코드의 9번 줄은 price를 prise로 잘못 썼습니다. 검사기가 이 오타를 짚도록 5번 줄의 빈칸에 타입을 채우세요.

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

1~4번 줄에 이 객체의 모양을 적은 타입이 있습니다. 빈칸에 any를 넣으면 9번 줄을 검사기가 어떻게 볼지도 떠올립니다.

정답

빈칸은 Menu입니다. 검사 결과는 9번 줄의 TS2551입니다 — latte의 타입이 Menu라 검사기는 prise가 Menu에 있는지 묻고, 없으니 짐작 price와 함께 짚습니다. 다르게 써도 되는 곳: : ____을 통째로 지워도 검사기가 객체에서 모양을 추론해 9번 줄을 짚습니다. 흔한 오답은 any입니다. any를 채우면 검사는 지나가고, 실행하면 undefined가 찍힙니다(any가 무난하다는 생각).

3. 장보기 합계에 봉투값 100원을 더하는 코드가 4번 줄에서 막혔습니다. 검사 오류를 없애고, 실행하면 3100이 찍히게 하는 고치기를 고르세요.

function addBag(won: number) {
  return won + 100;
}
console.log(addBag('3000'));
검사 결과 오류 1개
main.ts(4,20): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
  • ① 4번 줄을 console.log(addBag(3000));⁠으로 바꾼다.
  • ② 1번 줄의 won: number를 won: any로 바꾼다.
  • ③ 4번 줄 바로 위에 // @ts-ignore 한 줄을 넣는다.
힌트

고친 코드마다 검사 결과와 실행 결과를 따로 따져 봅니다. 실행할 때 won이 붙는 값이 수인지 글자인지 봅니다.

정답

①입니다. 넘기는 값이 수 3000이 되어 검사도 지나고, 실행하면 3100이 찍힙니다.

  • ② 검사 오류는 사라지지만 won이 any라 검사를 꺼 버린 것입니다. 실행하면 글자 '3000' 뒤에 100이 이어 붙어 3000100이 찍힙니다(any가 무난하다는 생각).
  • ③ 4번 줄의 검사 오류만 지웁니다. 넘긴 값은 그대로 글자라 ②와 같이 3000100이 찍힙니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 2절 코드를 main.ts로 저장하고, 3번 줄의 const data 뒤에 : unknown을 붙입니다. 검사 결과가 어떻게 바뀔지 먼저 적고, npx tsc로 검사해 맞춰 봅니다. 바뀐 결과는 다음 레슨 unknown — 확인하고 쓰기에서 읽습니다. 검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.

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

타입스크립트 목차로