TypeScript 독학 가이드 4 - 함수와 타입

learning by Seven Fingers Studio 18분
TypeScript함수타입시스템프로그래밍웹개발

JavaScript로 함수 만들 때 “이 함수 매개변수 뭐였지? 뭘 리턴하지?” 이런 고민 많이 했죠? 저도 그랬어요. 함수 정의를 찾아가서 확인하고… TypeScript 쓰니까 이런 게 확 줄어들더라고요. 오늘은 TypeScript에서 함수를 제대로 쓰는 방법을 알아볼게요.

함수 매개변수 타입

JavaScript에서는 매개변수에 뭐가 들어올지 몰라요. TypeScript는 타입을 명시해서 명확하게 만듭니다.

기본 사용법

function greet(name: string): string {
  return `안녕하세요, ${name}님!`;
}

console.log(greet("철수"));
console.log(greet(123));  // 에러!

실행 결과:

✓ 첫 번째 호출 성공
안녕하세요, 철수님!
✗ 타입 에러
Argument of type 'number' is not assignable to parameter of type 'string'

잘못된 타입의 인자를 전달하면 컴파일 시점에 에러가 발생합니다

여러 매개변수

function add(a: number, b: number): number {
  return a + b;
}

function introduce(name: string, age: number, job: string): string {
  return `저는 ${name}이고, ${age}살이며, ${job}입니다.`;
}

console.log(add(10, 20));
console.log(introduce("김철수", 25, "개발자"));

실행 결과:

✓ 컴파일 성공
30
저는 김철수이고, 25살이며, 개발자입니다.

모든 매개변수의 타입이 명확합니다

반환 타입

함수가 뭘 리턴하는지도 타입으로 명시할 수 있어요. 사실 TypeScript가 알아서 추론하긴 하는데, 명시하는 게 더 명확해요.

function getAge(): number {
  return 25;
}

function getName(): string {
  return "김철수";
}

function isStudent(): boolean {
  return true;
}

만약 리턴 타입이 안 맞으면 에러 나요:

function getScore(): number {
  return "100점";  // 에러!
}

실행 결과:

✗ 타입 에러
Type 'string' is not assignable to type 'number'

선언한 반환 타입과 실제 반환값이 일치해야 합니다

선택적 매개변수

가끔 매개변수가 있어도 되고 없어도 되는 경우가 있죠. ?를 붙이면 선택적 매개변수가 됩니다.

function greet(name: string, greeting?: string): string {
  if (greeting) {
    return `${greeting}, ${name}님!`;
  }
  return `안녕하세요, ${name}님!`;
}

console.log(greet("철수"));
console.log(greet("영희", "좋은 아침"));

실행 결과:

✓ 컴파일 성공
안녕하세요, 철수님!
좋은 아침, 영희님!

선택적 매개변수는 있어도 되고 없어도 됩니다

주의사항

선택적 매개변수는 항상 필수 매개변수 뒤에 와야 해요:

// 잘못된 예
function wrong(name?: string, age: number) {  // 에러!
}

// 올바른 예
function correct(age: number, name?: string) {  // OK
}

기본 매개변수

JavaScript의 기본 매개변수도 TypeScript에서 사용할 수 있어요.

function greet(name: string, greeting: string = "안녕하세요"): string {
  return `${greeting}, ${name}님!`;
}

console.log(greet("철수"));
console.log(greet("영희", "좋은 저녁"));

실행 결과:

✓ 컴파일 성공
안녕하세요, 철수님!
좋은 저녁, 영희님!

기본값이 있으면 타입을 생략해도 추론됩니다

기본값이 있으면 타입을 생략해도 TypeScript가 알아서 추론해요:

function multiply(a: number, b = 2) {  // b는 number로 추론됨
  return a * b;
}

나머지 매개변수 (Rest Parameters)

매개변수 개수가 정해지지 않았을 때 사용해요.

function sum(...numbers: number[]): number {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3));
console.log(sum(1, 2, 3, 4, 5));
console.log(sum(10));

실행 결과:

✓ 컴파일 성공
6
15
10

인자를 몇 개든 전달할 수 있습니다

일반 매개변수와 함께 사용

function introduce(greeting: string, ...names: string[]): string {
  return `${greeting}! ${names.join(", ")}님들!`;
}

console.log(introduce("안녕하세요", "철수", "영희", "민수"));

실행 결과:

✓ 컴파일 성공
안녕하세요! 철수, 영희, 민수님들!

첫 번째 매개변수는 필수, 나머지는 배열로 받습니다

화살표 함수

화살표 함수도 똑같이 타입을 지정할 수 있어요.

const add = (a: number, b: number): number => {
  return a + b;
};

const multiply = (a: number, b: number): number => a * b;

const greet = (name: string): string => `안녕, ${name}!`;

console.log(add(5, 3));
console.log(multiply(4, 7));
console.log(greet("철수"));

실행 결과:

✓ 컴파일 성공
8
28
안녕, 철수!

화살표 함수도 일반 함수와 동일하게 타입을 지정합니다

함수 타입

함수 자체를 타입으로 정의할 수 있어요. 콜백 함수 받을 때 유용합니다.

type MathOperation = (a: number, b: number) => number;

const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;

function calculate(a: number, b: number, operation: MathOperation): number {
  return operation(a, b);
}

console.log(calculate(10, 5, add));
console.log(calculate(10, 5, subtract));

실행 결과:

✓ 컴파일 성공
15
5

함수 타입을 정의하면 재사용이 편리합니다

함수 오버로딩

같은 이름의 함수를 여러 타입으로 정의할 수 있어요. JavaScript에는 없는 TypeScript만의 기능입니다.

function combine(a: string, b: string): string;
function combine(a: number, b: number): number;
function combine(a: any, b: any): any {
  if (typeof a === "string" && typeof b === "string") {
    return a + b;
  }
  if (typeof a === "number" && typeof b === "number") {
    return a + b;
  }
}

console.log(combine("Hello, ", "World!"));
console.log(combine(10, 20));

실행 결과:

✓ 컴파일 성공
Hello, World!
30

타입에 따라 다른 동작을 수행합니다

오버로딩은 복잡해 보이지만, API 설계할 때 유용해요.

실전 예제: 배열 또는 단일 값 받기

function getLength(value: string): number;
function getLength(value: string[]): number;
function getLength(value: string | string[]): number {
  if (typeof value === "string") {
    return value.length;
  }
  return value.length;
}

console.log(getLength("Hello"));
console.log(getLength(["a", "b", "c"]));

실행 결과:

✓ 컴파일 성공
5
3

문자열과 배열 모두 처리할 수 있습니다

void와 never

void - 리턴값 없음

function log(message: string): void {
  console.log(message);
  // return 없음
}

log("로그 메시지");

실행 결과:

✓ 컴파일 성공
로그 메시지

void 함수는 값을 반환하지 않습니다

never - 절대 리턴하지 않음

function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // 무한 루프
  }
}

never는 함수가 끝까지 실행되지 않을 때 사용해요. 에러를 던지거나 무한 루프일 때죠.

실전 예제: 할인 계산기

배운 내용을 종합해서 실용적인 함수 만들어볼게요:

type DiscountType = "percent" | "fixed";

function calculateDiscount(
  price: number,
  discount: number,
  type: DiscountType = "percent"
): number {
  if (type === "percent") {
    return price * (1 - discount / 100);
  }
  return price - discount;
}

const originalPrice = 50000;
console.log(`10% 할인: ${calculateDiscount(originalPrice, 10)}원`);
console.log(`5000원 할인: ${calculateDiscount(originalPrice, 5000, "fixed")}원`);

실행 결과:

✓ 컴파일 성공
10% 할인: 45000원
5000원 할인: 45000원

타입 별칭, 리터럴 타입, 기본 매개변수를 활용한 실용적인 함수입니다

정리하면

  • 매개변수 타입: (name: string) 형식으로 지정
  • 반환 타입: : number 형식으로 명시
  • 선택적 매개변수: name? 물음표 붙이기
  • 기본 매개변수: greeting = "안녕" 기본값 지정
  • 나머지 매개변수: ...numbers: number[] 배열로 받기
  • 함수 타입: 함수 자체를 타입으로 정의
  • 오버로딩: 같은 이름, 다른 시그니처

다음 글에서는 인터페이스(interface)에 대해 배워볼게요. 객체 타입을 정의하는 가장 강력한 방법입니다!


다음 글 보기

← 이전 글
TypeScript 독학 가이드 3 - 기본 타입
다음 글 →
TypeScript 독학 가이드 5 - 인터페이스
← 블로그 목록으로