TypeScript 독학 가이드 4 - 함수와 타입
JavaScript로 함수 만들 때 “이 함수 매개변수 뭐였지? 뭘 리턴하지?” 이런 고민 많이 했죠? 저도 그랬어요. 함수 정의를 찾아가서 확인하고… TypeScript 쓰니까 이런 게 확 줄어들더라고요. 오늘은 TypeScript에서 함수를 제대로 쓰는 방법을 알아볼게요.
함수 매개변수 타입
JavaScript에서는 매개변수에 뭐가 들어올지 몰라요. TypeScript는 타입을 명시해서 명확하게 만듭니다.
기본 사용법
function greet(name: string): string {
return `안녕하세요, ${name}님!`;
}
console.log(greet("철수"));
console.log(greet(123)); // 에러!
실행 결과:
잘못된 타입의 인자를 전달하면 컴파일 시점에 에러가 발생합니다
여러 매개변수
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, "개발자"));
실행 결과:
모든 매개변수의 타입이 명확합니다
반환 타입
함수가 뭘 리턴하는지도 타입으로 명시할 수 있어요. 사실 TypeScript가 알아서 추론하긴 하는데, 명시하는 게 더 명확해요.
function getAge(): number {
return 25;
}
function getName(): string {
return "김철수";
}
function isStudent(): boolean {
return true;
}
만약 리턴 타입이 안 맞으면 에러 나요:
function getScore(): number {
return "100점"; // 에러!
}
실행 결과:
선언한 반환 타입과 실제 반환값이 일치해야 합니다
선택적 매개변수
가끔 매개변수가 있어도 되고 없어도 되는 경우가 있죠. ?를 붙이면 선택적 매개변수가 됩니다.
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));
실행 결과:
인자를 몇 개든 전달할 수 있습니다
일반 매개변수와 함께 사용
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("철수"));
실행 결과:
화살표 함수도 일반 함수와 동일하게 타입을 지정합니다
함수 타입
함수 자체를 타입으로 정의할 수 있어요. 콜백 함수 받을 때 유용합니다.
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));
실행 결과:
함수 타입을 정의하면 재사용이 편리합니다
함수 오버로딩
같은 이름의 함수를 여러 타입으로 정의할 수 있어요. 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));
실행 결과:
타입에 따라 다른 동작을 수행합니다
오버로딩은 복잡해 보이지만, 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"]));
실행 결과:
문자열과 배열 모두 처리할 수 있습니다
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")}원`);
실행 결과:
타입 별칭, 리터럴 타입, 기본 매개변수를 활용한 실용적인 함수입니다
정리하면
- 매개변수 타입:
(name: string)형식으로 지정 - 반환 타입:
: number형식으로 명시 - 선택적 매개변수:
name?물음표 붙이기 - 기본 매개변수:
greeting = "안녕"기본값 지정 - 나머지 매개변수:
...numbers: number[]배열로 받기 - 함수 타입: 함수 자체를 타입으로 정의
- 오버로딩: 같은 이름, 다른 시그니처
다음 글에서는 인터페이스(interface)에 대해 배워볼게요. 객체 타입을 정의하는 가장 강력한 방법입니다!