본문으로 건너뛰기

나머지 매개변수

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

목차

매개변수 이름 앞에 ... 기호를 붙이면 남은 인자를 배열 하나로 모으는 나머지 매개변수(rest parameter)가 되고, 그 타입은 number[]처럼 배열 타입으로 적습니다.

앞 레슨의 ?와 기본값은 정해 둔 자리 하나를 비워도 되게 했습니다. 주문한 것들의 값처럼 몇 개가 올지 모르는 인자는 매개변수를 몇 개 둘지부터 정할 수 없습니다. 이런 인자는 나머지 매개변수 하나로 받습니다.

4단계 중 1단계. 1~3번 줄: ...prices는 남은 인자를 모은 배열이라 number[]입니다.

1나머지 매개변수는 남은 인자를 배열 하나로

JS 다시 보기: 나머지 매개변수 ...prices는 남은 인자를 배열 하나로 모읍니다(JS '단축 속성과 나머지 모으기'). JS 다시 보기: for...of는 배열에서 값을 하나씩 꺼내 이름을 붙이고 덩어리를 실행합니다(JS 'for...of로 배열 돌기').

나머지 매개변수의 타입은 배열 타입으로 적습니다. 검사기는 인자마다 그 원소 타입 모음에 드는지 봅니다(검사 규칙 2).

function sum(
  ...prices: number[]
) {
  let total = 0;
  for (const p of prices) {
    total = total + p;
  }
  return total;
}
console.log(sum(4500, 2000));
console.log(sum());
console.log(sum(4000, '4500'));
검사 결과 오류 1개
main.ts(12,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
실행 결과
6500
0
40004500

10번 줄은 인자 둘이 prices 하나로 모여 [4500, 2000]이 됩니다. 11번 줄처럼 인자가 없어도 검사를 지나고, prices가 빈 배열에 붙어 0을 돌려줍니다. 12번 줄은 둘째 인자 '4500'이 number 원소 자리에 들지 않아 막힙니다. Node로 돌리면 4000에 글자를 이어 붙여 40004500이 찍힙니다.

이 과목은 함수 머리가 폰 화면에 넘치면 매개변수를 한 줄에 하나씩 쓰고, 줄마다 끝에 쉼표를 둡니다. 나머지 매개변수 줄만은 예외입니다. 2번 줄 ...prices: number[] 끝에 쉼표를 두면 TS1013 오류이고, Node도 돌리지 못합니다.

2앞 매개변수가 받고 남은 인자를 모은다

나머지 매개변수 앞에는 다른 매개변수를 둘 수 있습니다. 인자는 차례로 매개변수와 짝지어지고, 나머지 매개변수는 앞 매개변수들이 받고 남은 인자를 모읍니다. 아래 pickup은 첫 인자를 name으로 받고 남은 인자를 items로 모읍니다. 8번 줄과 9번 줄은 검사를 지나갈까요? 답을 정한 뒤 펼쳐 보세요.

function pickup(
  name: string,
  ...items: string[]
) {
  console.log(name, items);
}
pickup('민지', '라테', '쿠키');
pickup('서준');
pickup();
검사 결과와 실행 결과 보기
검사 결과 오류 1개
main.ts(9,1): error TS2555: Expected at least 1 arguments, but got 0.
실행 결과
민지 [ '라테', '쿠키' ]
서준 []
undefined []

예상과 다르면 그곳이 배울 곳입니다. 8번 줄은 검사를 지납니다. name이 '서준'을 받고 남은 인자가 없으니 items는 빈 배열 []에 붙습니다. 9번 줄만 막힙니다. TS2555는 '인자가 적어도 1개 필요한데 0개를 받았다'는 뜻입니다. items는 비워도 되지만 name은 꼭 있어야 하는 매개변수입니다. Node는 그대로 돌려, 9번 줄에서 name을 undefined로 찍습니다.

따로 받을 매개변수는 이렇게 나머지 매개변수 앞에 둡니다. 뒤에 두면 어떻게 되는지는 자주 틀리는 곳 첫째에서 봅니다.

예제

라테 2잔과 쿠키 1개의 합계를 냅니다. 줄마다 금액은 함수의 입력과 출력의 cost로 내고, 그 금액들을 sum이 모두 더합니다.

sum(1절과 같음)
function sum(
  ...prices: number[]
) {
  let total = 0;
  for (const p of prices) {
    total = total + p;
  }
  return total;
}

앞 코드에 이어 cost를 쓰고, 금액 둘을 sum에 넘깁니다.

function cost(
  qty: number,
  unit: number,
) {
  return qty * unit;
}
const total = sum(
  cost(2, 4500),
  cost(1, 2000),
);
console.log(total);
검사 결과 오류 없음
실행 결과
11000

17 · 18번 줄의 cost는 number를 돌려주니, 두 인자 모두 prices의 원소 자리에 듭니다. 검사 오류는 0입니다. 주문이 세 줄이 되어도 sum의 머리는 그대로입니다. cost, 앞 레슨의 receipt, 이 레슨의 sum이 이 단원의 주문 함수 셋입니다.

자주 틀리는 곳

틀린 생각: 나머지 매개변수 뒤에도 매개변수를 더 둘 수 있다.

메모 같은 매개변수 하나를 끝에 더 받고 싶어지는 때가 있습니다. 그러나 나머지 매개변수는 남은 인자를 모두 모으므로, 그 뒤에 올 인자가 없습니다.

function count(
  ...items: string[],
  memo: string,
) {
  return items.length;
}
검사 결과 오류 1개
main.ts(2,3): error TS1014: A rest parameter must be last in a parameter list.

TS1014는 '나머지 매개변수는 매개변수 목록의 맨 끝에 와야 한다'는 뜻입니다. 이 머리도 Node가 돌리지 못합니다.

맞는 규칙: 나머지 매개변수는 남은 인자를 모두 배열 하나로 모으므로 맨 끝에 하나만 둡니다. 따로 받을 매개변수는 그 앞에 둡니다.

틀린 생각: 인자를 하나도 넘기지 않으면 나머지 매개변수에서 막힌다.

앞 레슨들에서 인자가 모자라면 TS2554로 막혔으니, 나머지 매개변수도 비우면 막힐 것 같습니다. 그러나 1절 11번 줄 sum()처럼 인자가 없어도 검사를 지나고, prices는 빈 배열 []에 붙습니다. 2절 9번 줄이 막힌 것도 items가 아니라 앞의 name 때문입니다.

맞는 규칙: 나머지 매개변수는 인자가 몇 개든, 없어도 배열 하나로 받습니다. 인자 수로 막히는 것은 그 앞의 꼭 있어야 하는 매개변수에 줄 인자가 모자랄 때뿐입니다(2절의 TS2555).

확인 문제

1. 예제의 16 · 19번 줄을 바꿔, 두 금액을 배열 하나에 담아 넘겼습니다. 검사는 지나갈까요? 지나가지 못하면 몇 번 줄인가요?

sum과 cost(예제와 같음)
function sum(
  ...prices: number[]
) {
  let total = 0;
  for (const p of prices) {
    total = total + p;
  }
  return total;
}
function cost(
  qty: number,
  unit: number,
) {
  return qty * unit;
}
const total = sum([
  cost(2, 4500),
  cost(1, 2000),
]);
console.log(total);
힌트

16번 줄이 sum에 넘기는 인자가 몇 개인지 세고, 그 인자를 prices의 원소 자리와 맞춰 봅니다.

정답
검사 결과 오류 1개
main.ts(16,19): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'number'.
실행 결과
09000,2000

16번 줄이 막힙니다. 넘긴 인자는 배열 하나이고, 그 타입 number[]은 원소 자리 number에 들지 않습니다. 검사기는 prices 전체가 아니라 인자 하나하나를 원소 타입과 맞춰 봅니다(검사 규칙 2). Node는 그대로 돌립니다. prices는 배열 하나를 원소로 가진 [[9000, 2000]]에 붙습니다. 0에 배열을 더하면 배열이 원소를 쉼표로 이은 글자 '9000,2000'으로 바뀌어 이어 붙으므로, 09000,2000이 찍힙니다.

흔한 오답은 '검사를 지난다'입니다. prices의 타입이 number[]이니 수 배열을 넘기면 맞는다고 본 것입니다. number[]은 모은 뒤의 타입이고, 넘기는 인자는 하나하나가 number여야 합니다.

2. 장바구니에 담은 물건 수를 찍는 함수입니다. 실행 결과가 2 · 0 두 줄이 되도록 2번 줄의 빈칸에 타입을 쓰세요. 검사도 지나야 합니다.

function bag(
  ...goods: ____
) {
  console.log(goods.length);
}
bag('우유', '달걀');
bag();
힌트

6번 줄이 넘기는 인자의 타입을 봅니다. 그다음 1절에서 prices의 타입을 어떻게 적었는지 봅니다.

정답

빈칸은 string[]입니다. 2번 줄이 ...goods: string[]이 됩니다.

function bag(
  ...goods: string[]
) {
  console.log(goods.length);
}
bag('우유', '달걀');
bag();
검사 결과 오류 없음
실행 결과
2
0

인자 '우유' · '달걀'이 글자라 원소 타입은 string이고, 나머지 매개변수에는 그것을 모은 배열 타입 string[]을 적습니다. 7번 줄은 빈 배열이라 0입니다.

흔한 오답은 string입니다. 인자 하나의 타입을 적은 것입니다. 나머지 매개변수에 배열이 아닌 타입을 적으면 2번 줄이 TS2370('나머지 매개변수는 배열 타입이어야 한다')으로 막힙니다. 1절을 따라 number[]로 적으면 6번 줄의 글자가 number 원소 자리에 들지 않아 TS2345입니다.

다르게 써도 되는 곳: (string | number)[]도 검사는 지나지만 숫자까지 받아 원소 타입을 넓힙니다. any[]는 검사를 끄는 타입이라 쓰지 않습니다(6단원).

3. 성적의 평균을 내는 함수입니다. 검사 결과와 Node로 돌린 실행 결과를 각각 쓰세요.

function average(
  ...scores: number[]
) {
  let total = 0;
  for (const s of scores) {
    total = total + s;
  }
  return total / scores.length;
}
console.log(average(90, 80));
console.log(average());
힌트

11번 줄처럼 인자가 없을 때 scores에 무엇이 오는지 1절에서 찾고, 그때 8번 줄의 나눗셈을 셈합니다.

정답
검사 결과 오류 없음
실행 결과
85
NaN

검사는 오류 없이 지나갑니다. 나머지 매개변수는 인자가 없어도 빈 배열로 받으니, 11번 줄도 맞는 부르기입니다. 실행하면 11번 줄은 total이 0, scores.length도 0이라 0을 0으로 나눠 NaN이 찍힙니다. 검사를 지나도 결과가 맞는지는 따로 봐야 합니다.

흔한 오답은 '11번 줄에서 막힌다'입니다. 인자를 하나도 넘기지 않으면 TS2554처럼 막힌다고 본 것입니다. ...scores는 인자가 몇 개든, 없어도 받습니다.

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

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

타입스크립트 목차로