본문으로 건너뛰기

함수의 입력과 출력

타입스크립트 목차기본 타입과 함수레슨 9 / 51

목차

함수의 매개변수에는 타입을 적고, 돌려주는 타입(return type)은 검사기가 return에서 추론합니다. 그러면 검사기는 함수를 부르는 줄마다 인자의 수와 타입이 매개변수와 맞는지 보고, 돌려받은 값의 타입도 압니다.

자바스크립트 함수는 인자를 빠뜨려도, 다른 종류의 값을 넘겨도 멈추지 않고 돌아 NaN 같은 값을 돌려줍니다. 검사기는 그렇게 부르는 줄을 실행하기 전에 막습니다.

5단계 중 1단계. 1~4번 줄: 매개변수 qty와 unit의 선언 타입은 적은 대로 number입니다.

1매개변수에는 타입을 적는다

매개변수 이름 뒤에 : 타입을 적으면, 그 타입이 함수 몸 안에서 그 매개변수의 선언 타입입니다. 아래 1~4번 줄은 폰 화면에 맞추려고 매개변수마다 줄을 나눈 함수 머리이고, 한 줄로 쓴 머리와 같습니다.

function cost(
  qty: number,
  unit: number,
) {
  return qty * unit;
}
console.log(cost(2, 4500));
실행 결과
9000

매개변수에 타입을 적지 않으면 어떻게 될까요? 아래 4번 줄은 n에 4를 넘기니 n이 수라는 것이 보입니다. 검사를 지나갈까요? 답을 정한 뒤 펼쳐 보세요.

function double(n) {
  return n * 2;
}
console.log(double(4));
검사 결과 보기
검사 결과 오류 1개
main.ts(1,17): error TS7006: Parameter 'n' implicitly has an 'any' type.

예상과 다르면 그곳이 배울 곳입니다. 1번 줄이 막힙니다. Parameter 'n'은 '매개변수 n', implicitly has an 'any' type⁠은 '저절로 any 타입이 됐다', 곧 n의 타입이 정해지지 않아 any가 됐다는 뜻입니다. 검사기는 함수 머리를 읽을 때 매개변수의 선언 타입을 정합니다. 부르는 줄의 인자를 보고 정하지 않습니다(검사 규칙 3). any는 어떤 타입이든 된다는 표시이고, any인 곳은 검사기가 검사하지 않습니다(6단원 'any는 검사를 끈다'). strict는 이렇게 저절로 any가 되는 매개변수를 오류로 짚어, 매개변수에 타입을 적게 합니다.

2부르는 줄마다 인자를 매개변수에 맞춰 본다

JS 다시 보기: 인자를 덜 넘기면 모자란 매개변수는 undefined가 되어 계산이 NaN이 되고, 남는 인자는 버려집니다(JS '매개변수와 인자').

부르는 줄에서 검사기는 인자를 차례로 매개변수에 맞춥니다. 인자의 타입이 매개변수의 선언 타입 모음에 드는지 보고(검사 규칙 2), 인자의 수가 매개변수의 수와 같은지도 봅니다.

function cost(
  qty: number,
  unit: number,
) {
  return qty * unit;
}
console.log(cost('2', 4500));
console.log(cost(2));
console.log(cost(2, 4500, 1));
검사 결과 오류 3개
main.ts(7,18): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.main.ts(8,13): error TS2554: Expected 2 arguments, but got 1.main.ts(9,27): error TS2554: Expected 2 arguments, but got 3.
실행 결과
9000
NaN
9000

7번 줄의 TS2345는 number 매개변수 자리에 글자 '2'를 넘겼다는 뜻입니다. 8번 줄의 TS2554는 '인자 2개가 필요한데 1개를 받았다'는 뜻이고, 9번 줄은 3개를 받았다는 같은 오류입니다.

실행 결과도 견줘 봅니다. 7번 줄은 자바스크립트가 '2' * 4500을 수로 바꿔 계산해 9000이 맞게 나오지만, 검사기는 막습니다. 검사기는 결과를 보지 않고 타입만 봅니다(검사 규칙 1). 8번 줄은 unit이 undefined라 NaN이고, 9번 줄은 남는 인자 1을 버려 9000입니다.

3돌려주는 타입은 return에서 추론된다

돌려주는 타입은 적지 않아도 됩니다. 검사기가 return 뒤 식의 타입에서 정합니다(검사 규칙 3). cost는 qty * unit이 수라서 돌려주는 타입이 number이고, 편집기에서 cost에 마우스를 올리면 머리 끝에 : number가 붙어 보입니다. 그래서 const total = cost(2, 4500);⁠의 total도 지금 타입이 number입니다.

JS 다시 보기: return이 없는 함수를 부르면 undefined가 돌아옵니다(JS '함수 만들고 부르기').

function label(total: number) {
  return total + '원';
}
function show(total: number) {
  console.log(label(total));
}
show(9000);
실행 결과
9000원

label은 total + '원'이 글자라서 돌려주는 타입이 string입니다. show처럼 return이 없는 함수의 돌려주는 타입은 void이고, '돌려주는 값이 없다'는 뜻입니다.

예제

라테 2잔과 쿠키 1개의 합계를 찍습니다. 주문 금액 함수 둘에 매개변수 타입만 적었고, 돌려주는 타입은 추론에 맡겼습니다.

function cost(
  qty: number,
  unit: number,
) {
  return qty * unit;
}
function label(total: number) {
  return `합계 ${total}원`;
}
const latte = cost(2, 4500);
const cookie = cost(1, 2000);
const total = latte + cookie;
console.log(label(total));
검사 결과 오류 없음
실행 결과
합계 11000원

10 · 11번 줄의 latte와 cookie는 cost가 돌려준 number라서, 12번 줄의 total도 number이고 13번 줄에서 label의 매개변수 자리에 듭니다. 검사 오류는 0입니다.

자주 틀리는 곳

틀린 생각: 매개변수의 타입도 부르는 줄의 인자에서 추론된다.

이름의 타입은 첫 값에서 추론되니, 매개변수도 넘겨받는 값에서 정해질 것 같습니다. 그러나 매개변수는 부르는 줄이 아니라 함수 머리에서 타입이 정해집니다.

function half(price) {
  return price / 2;
}
console.log(half('4500원'));
검사 결과 오류 1개
main.ts(1,15): error TS7006: Parameter 'price' implicitly has an 'any' type.
실행 결과
NaN

검사 오류는 1번 줄 하나뿐이고, 글자를 넘긴 4번 줄은 짚지 못합니다. price가 any라서 무엇이든 받기 때문입니다. Node로 돌리면 글자를 2로 나눠 NaN이 찍힙니다.

맞는 규칙: 검사기는 함수 머리를 읽을 때 매개변수의 선언 타입을 정하고, 부르는 줄의 인자로 정하지 않습니다. 그래서 매개변수에는 타입을 적습니다 — 넘기는 자리에 바로 쓴 콜백만 문맥에서 추론됩니다(검사 규칙 3).

틀린 생각: 자바스크립트처럼 인자를 덜 넘겨도 된다.

자바스크립트에서는 인자를 빠뜨려도 함수가 돌아가니, 남은 매개변수는 비워 둬도 될 것 같습니다. 검사기는 부르는 줄의 인자 수를 셉니다.

function greet(
  who: string,
  stamps: number,
) {
  return `${who}님 ${stamps}개`;
}
console.log(greet('민지'));
검사 결과 오류 1개
main.ts(7,13): error TS2554: Expected 2 arguments, but got 1.
실행 결과
민지님 undefined개

stamps에 넘길 인자를 빠뜨려 TS2554입니다. Node로 돌리면 stamps가 undefined라 글 가운데에 undefined가 찍힙니다.

맞는 규칙: 검사기는 부르는 줄마다 인자 수를 매개변수 수와 맞춰 봅니다. 비워도 되는 매개변수는 ?나 기본값으로 따로 적습니다(다음 레슨 선택·기본값 매개변수).

틀린 생각: 돌려주는 타입은 늘 적어야 안전하다.

타입을 많이 적을수록 검사가 촘촘해질 것 같습니다. 그러나 돌려주는 타입은 return에서 추론되어, 적지 않아도 부르는 쪽 검사는 같습니다. 적어 두면 쓸모가 있는 때는 따로 있습니다. 함수가 다른 값을 돌려주는 실수를 함수 안에서 잡을 때입니다.

function cost(
  qty: number,
  unit: number,
): number {
  return `${qty * unit}원`;
}
검사 결과 오류 1개
main.ts(5,3): error TS2322: Type 'string' is not assignable to type 'number'.

4번 줄 ): number {가 돌려주는 타입을 적은 자리입니다. 5번 줄이 글자를 돌려주려 해서 그 줄에서 막힙니다. 적지 않았다면 돌려주는 타입이 string으로 추론되고, 오류는 그 값을 수처럼 쓰는 부르는 쪽 줄에서 납니다.

맞는 규칙: 돌려주는 타입은 return에서 추론되므로 보통은 적지 않습니다. 남이 쓸 함수이거나 돌려줄 값을 못 박아 둘 때만 ): number {처럼 적습니다(검사 규칙 3).

확인 문제

1. 예제의 10번 줄을 const latte = cost(2, '4500');⁠로 바꿨습니다. 검사는 지나갈까요? 지나가지 못하면 몇 번 줄인가요? Node로 돌리면 무엇이 찍힐까요?

function cost(
  qty: number,
  unit: number,
) {
  return qty * unit;
}
function label(total: number) {
  return `합계 ${total}원`;
}
const latte = cost(2, '4500');
const cookie = cost(1, 2000);
const total = latte + cookie;
console.log(label(total));
힌트

10번 줄의 두 인자를 cost의 매개변수와 차례로 맞춰 봅니다. 실행은 자바스크립트가 수와 글자를 곱할 때 무엇을 하는지로 셈합니다.

정답
검사 결과 오류 1개
main.ts(10,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
실행 결과
합계 11000원

10번 줄이 막힙니다. number 매개변수 unit 자리에 글자 '4500'을 넘겼습니다. Node는 돌고, 2 * '4500'은 자바스크립트가 글자를 수로 바꿔 9000이라 합계도 맞게 찍힙니다. 결과가 맞아도 타입이 맞지 않으면 검사기는 막습니다(검사 규칙 1).

흔한 오답은 '검사를 지나간다'입니다. 실행 결과가 맞으니 검사도 지나리라 본 것입니다. 검사기는 코드를 돌려 보지 않고 타입만 봅니다.

2. 검사를 지나도록 빈칸에 타입을 쓰세요. 그리고 7번 줄에서 line이 돌려주는 값의 타입은 무엇인지 쓰세요.

function line(
  item: ____,
  qty: ____,
) {
  return `${item} ${qty}잔`;
}
console.log(line('라테', 2));
힌트

7번 줄에서 매개변수마다 넘기는 인자를 봅니다. 돌려주는 타입은 5번 줄 return 뒤 식이 무엇인지로 정해집니다.

정답

빈칸은 차례로 string · number이고, 돌려주는 타입은 string입니다.

function line(
  item: string,
  qty: number,
) {
  return `${item} ${qty}잔`;
}
console.log(line('라테', 2));
검사 결과 오류 없음
실행 결과
라테 2잔

5번 줄은 템플릿 문자열이라 글자를 돌려줍니다. 그래서 line의 돌려주는 타입은 string입니다.

다르게 써도 되는 곳: 빈칸에 any를 써도 검사는 지나지만, any는 검사를 끄는 타입이라 쓰지 않습니다(6단원).

흔한 오답은 둘째 빈칸의 string입니다. 글 속에 찍히는 값이라 글자로 본 것입니다. 7번 줄이 넘기는 2는 수라서 string 자리에 들지 않습니다.

3. 걸음 수로 걸은 거리(m)를 셈하는 함수가 1번 줄에서 막힙니다. 아래 고치기 가운데 검사 오류를 없애는 것을 모두 고르고, 이 과목이 권하는 고치기는 무엇인지 쓰세요.

function meters(steps) {
  return steps * 0.7;
}
console.log(meters(8000));
검사 결과 오류 1개
main.ts(1,17): error TS7006: Parameter 'steps' implicitly has an 'any' type.
  • ① 1번 줄을 function meters(steps: number) {⁠로 고친다.
  • ② 4번 줄을 console.log(meters(8000: number));⁠로 고친다.
  • ③ 2번 줄을 return Number(steps) * 0.7;⁠로 고친다.
  • ④ 1번 줄을 function meters(steps: any) {⁠로 고친다.
힌트

오류가 짚는 것이 무엇의 타입인지, 그 타입을 적는 자리가 어디인지 봅니다.

정답

검사 오류를 없애는 것은 ①과 ④이고, 권하는 고치기는 ①입니다.

function meters(
  steps: number,
) {
  return steps * 0.7;
}
console.log(meters(8000));
검사 결과 오류 없음
실행 결과
5600

①로 고친 코드입니다. 정답 접기 안은 폭이 좁아 함수 머리를 1절처럼 나눴습니다. TS7006은 매개변수 steps의 타입이 정해지지 않았다는 오류라, 매개변수에 타입을 적는 1번 줄이 고칠 곳입니다. ④도 검사 오류는 사라지지만, any는 검사를 끄는 타입이라 meters('8000걸음') 같은 부르기를 막지 못합니다(6단원). ②는 부르는 자리에 타입을 적는 문법이 없어 TS1005: ',' expected.⁠가 납니다. ③은 2번 줄을 바꿔도 steps의 타입이 여전히 정해지지 않아 TS7006이 그대로입니다.

흔한 오답은 ②입니다. 매개변수의 타입을 부르는 쪽이 정한다고 본 것입니다(매개변수도 추론된다는 생각).

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

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

타입스크립트 목차로