4.Node.js/JavaScript&TypeScript

[TypeScript] 3편. 배열, 튜플, 함수 타입 이해하기 : 매개변수, 반환 타입, 오버로딩

쿼드큐브 2025. 12. 15. 12:29
반응형
반응형

 

3편. 배열, 튜플, 함수 타입 이해하기 : 매개변수, 반환 타입, 오버로딩

 

📚 목차
1. 배열과 튜플 이해하기: 리스트형 데이터 타입 선언법
2. 함수 타입 기초: 매개변수 타입과 반환 타입 명확히 정의하기
3. 함수 타입 심화: void, never, undefined와 함수 오버로딩
4. 함수 타입 별칭과 콜백 타입 정의하기: 재사용 가능한 타입 설계

 

배열, 튜플, 함수 타입 이해하기 삽화 이미지
배열, 튜플, 함수 타입 이해 하기 삽화이미지

 

📂 [GitHub 예시 코드 보러가기] (https://github.com/cericube/nodejs-tutorials) /TypeScript

 

1. 배열과 튜플 이해하기: 리스트형 데이터 타입 선언법

TypeScript에서는 배열과 튜플을 통해 리스트형 데이터를 더 안전하고 구조화된 방식으로 관리할 수 있습니다.

JavaScript의 유연한 배열 사용 방식은 편리하지만, 타입이 섞이기 쉬워 오류를 놓치기 쉽습니다.

TypeScript는 이러한 문제를 방지하기 위해 배열과 튜플에 정확한 타입을 지정할 수 있습니다.

 

🔷 배열 타입 지정 (Array Types)

JavaScript 배열은 여러 종류의 데이터를 담을 수 있지만, TypeScript에서는 하나의 배열이 특정 타입의 요소만 담도록 강제하여 안정성을 높입니다.

두 방식은 동일하게 작동합니다. 다만 string[]처럼 대괄호([])를 사용하는 방식이 간결하고 직관적이어서 더 흔히 사용됩니다.

 

1. 타입 뒤에 대괄호 ([]) 붙이기 (선호되는 방식):

// 문자열만 담을 수 있는 배열
let names: string[] = ["김민수", "이서현", "박지호"];

// 숫자만 담을 수 있는 배열
let ages: number[] = [25, 30, 22];

// 오류 예시: 숫자 배열에 문자열을 넣을 수 없습니다
// ages.push("서른");

 

2. 제네릭 배열 타입 (Array<T>):

// 숫자 배열을 제네릭 문법으로 선언
let scores: Array<number> = [90, 85, 95];

 

 

🔷 튜플 타입의 엄격한 구조 (Tuple Types)

배열은 요소의 순서와 개수에 제약이 없습니다. 하지만 때로는 정해진 개수와 순서에 따라 다른 타입을 저장해야 할 때가 있습니다.

예를 들어, 좌표나 이름-나이 쌍처럼요. 이때 튜플(Tuple)을 사용합니다.

 

튜플은 배열의 길이가 고정되어 있으며, 각 요소의 타입이 정해져 있는 배열의 특수한 형태입니다.

// 튜플: 첫 번째는 숫자, 두 번째는 문자열만 가능
let userInfo: [number, string];

userInfo = [1, "김철수"];       // ✅ 정상
// userInfo = ["김철수", 1];   // ❌ 오류 - 순서가 올바르지 않음
// userInfo = [1, "김철수", 20]; // ❌ 오류 - 요소 개수가 초과됨

 

튜플은 함수에서 다양한 타입의 값을 함께 반환할 때도 자주 사용됩니다.

function getUser(): [string, number] {
  return ["홍길동", 28];
}

const [name, age] = getUser(); // name: string, age: number

 

2. 함수 타입 기초: 매개변수 타입과 반환 타입 명확히 정의하기

JavaScript에서는 함수의 매개변수나 반환값의 타입을 제한하지 않기 때문에, 타입이 잘못된 값이 전달되더라도 실행이 되며,
이는 종종 예상치 못한 버그로 이어질 수 있습니다.

 

TypeScript에서는 함수의 매개변수(parameter)와 반환값(return value)에 정확한 타입을 명시함으로써, 이러한 오류를 사전에 방지하고 코드의 신뢰성과 안정성을 높일 수 있습니다.

 

🔷 매개변수와 반환값에 타입 지정하기

함수를 선언할 때, 각 매개변수 옆에 타입을 지정하며 괄호 () 뒤에 : 를 사용해 반환값(return type)의 타입도 함께 명시할 수 있습니다.

// 매개변수: a는 number, b는 number
// 반환 값: number
function add(a: number, b: number): number {
  return a + b;
}

let sum = add(10, 5); // sum은 number 타입으로 추론됩니다.

// 🚨 오류: 'string' 인수는 'number' 타입 매개변수에 할당될 수 없습니다.
// add(10, "5");

 

 

🔷 선택적 매개변수와 기본 매개변수

JavaScript와 마찬가지로, TypeScript에서도 일부 매개변수를 생략 가능하게 만들 수 있습니다.

이를 위해 TypeScript는 선택적 매개변수와 기본값 매개변수 두 가지를 제공합니다.

 

1. 선택적 매개변수 (?)
매개변수 이름 뒤에 물음표(?)를 붙이면, 그 매개변수는 생략 가능해지며 타입은 자동으로 T | undefined로 추론됩니다.

선택적 매개변수는 필수 매개변수 뒤에만 위치할 수 있습니다.

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

console.log(greet("홍길동"));                     // 홍길동님, 안녕하세요!
console.log(greet("홍길동", "오늘 날씨가 좋아요.")); // 홍길동님께, 오늘 날씨가 좋아요.

 

2. 기본값 매개변수 (Default Parameters)
기본값을 지정하면, 해당 매개변수를 생략해도 기본값이 자동으로 사용됩니다.

기본값이 설정된 매개변수도 사실상 선택적 매개변수로 간주됩니다.

기본값이 있는 매개변수는 앞에 필수 매개변수보다 뒤에 위치해야 합니다.

function calculate(price: number, taxRate: number = 0.1): number {
  return price * (1 + taxRate);
}

console.log(calculate(100));    // 출력: 110
console.log(calculate(100, 0.2)); // 출력: 120

 

 

🔷 함수 표현식과 화살표 함수에 타입 적용

TypeScript는 다양한 함수 선언 방식에 동일하게 타입을 지정할 수 있습니다.

TypeScript는 익명 함수나 화살표 함수에도 정확히 타입을 부여할 수 있어, 코드 스타일에 관계없이 일관된 타입 안전성을 유지할 수 있습니다.

 

1. 함수 표현식 (Function Expression)

// 1. 함수 선언문 (Function Declaration)
function greet(name: string): string {
  return `안녕하세요, ${name}님!`;
}

// 2. 함수 표현식 (Function Expression)
const greet2 = function (name: string): string {
  return `반가워요, ${name}님!`;
};

// 3. 선택적 매개변수 사용
function introduce(name: string, hobby?: string): string {
  if (hobby) {
    return `${name}님은 ${hobby}를 좋아합니다.`;
  }
  return `${name}님의 취미 정보가 없습니다.`;
}

// 4. 기본값 매개변수 사용
function discount(price: number, rate: number = 0.1): number {
  return price * (1 - rate);
}

// 5. 반환 타입이 void인 경우
function logError(message: string): void {
  console.error("❌ 오류:", message);
}

// 6. 매개변수가 없는 함수
function getNow(): Date {
  return new Date();
}

// 7. 반환값이 객체인 경우
function createUser(name: string, age: number): { name: string; age: number } {
  return {
    name,
    age,
  };
}

// 8. 함수 타입을 명확히 지정한 변수 할당
// addFn
//  - “변수 이름”입니다.
// : (a: number, b: number) => number
//  - “이 변수에는 이런 함수만 들어와야 한다”는 타입
//  → “파라미터 2개 받고, 둘 다 number, 그리고 number를 반환하는 함수” 라는 타입
const addFn: (a: number, b: number) => number = function (a, b) {
  return a + b;
};
console.log('addFn(3, 4):', addFn(3, 4)); // 7

 

2. 화살표 함수 (Arrow Function)

// 1. 매개변수가 하나일 때 (소괄호 생략 가능)
const sayHello = (name: string): string => {
  return `안녕하세요, ${name}님!`;
};

// 2. 매개변수가 하나일 때 (한 줄로 작성, return 생략)
const double = (x: number): number => x * 2;

// 3. 매개변수가 여러 개일 때 (소괄호 필수)
const add = (a: number, b: number): number => {
  return a + b;
};

// 4. 매개변수가 여러 개이고 한 줄 반환 (중괄호 생략 시 return 생략 가능)
const multiply = (a: number, b: number): number => a * b;

// 5. 반환 타입이 void인 경우
const logMessage = (msg: string): void => {
  console.log("📢", msg);
};

// 6. 선택적 매개변수 사용 (message는 선택적)
const greet = (name: string, message?: string): string => {
  return message
    ? `${name}님께, ${message}`
    : `${name}님, 안녕하세요!`;
};

// 7. 기본값 매개변수 사용
const calculateTax = (price: number, rate: number = 0.1): number => {
  return price * (1 + rate);
};

// 8. 반환값이 객체일 경우 (소괄호로 감싸야 함!)
const createUser = (name: string, age: number) => ({
  name,
  age,
  createdAt: new Date(),
});
반응형

 

3. 함수 타입 심화: void, never, undefined와 함수 오버로딩

TypeScript에서는 함수의 반환 타입을 명확히 지정할 수 있으며, 그중에서도 특별히 주의해야 할 반환 타입으로는 void, undefined, never가 있습니다.

세 가지는 모두 "값이 없는 것처럼 보이는 함수"에서 사용되지만, 정확한 의미와 사용 목적이 다릅니다.

 

또한, 하나의 함수 이름으로 매개변수의 타입 또는 개수에 따라 다르게 동작하도록 정의할 수 있는 함수 오버로딩(Function Overloading) 기능도 함께 알아보겠습니다.

반환타입 의미 예시
void 아무 값도 반환하지 않음 부수 효과(side effect)만 있는 함수 (예: console.log)
undefined 명시적으로 undefined를 반환 함수가 undefined를 반환한다고 선언할 때
never 절대로 함수가 끝까지 실행되지 않음 예외를 던지거나 무한 루프를 도는 함수

 

🔷 void 예시: 단순 출력 함수

▸ void는 반환값이 없는 함수에서 사용합니다.
▸ 주로 콘솔 출력, 상태 업데이트 등 부수 효과(side effect) 중심의 함수에 사용됩니다.

function logMessage(message: string): void {
  console.log(`[로그]: ${message}`);
}

logMessage("작업이 완료되었습니다.");

 

🔷 undefined 예시: 명시적 반환

▸ undefined는 값을 반환하긴 하지만, 그 값이 undefined임을 명시하는 경우에 사용합니다.
▸ 특별한 의미가 없다면 void를 사용하는 것이 일반적입니다.

function returnUndefined(): undefined {
  return undefined;
}

const result = returnUndefined(); // result는 undefined 타입

 

🔷 never 예시: 절대 끝나지 않는 함수

▸ never는 타입 좁히기(type narrowing)나 switch 문에서 모든 케이스를 다 다뤘는지 확인할 때 유용하게 쓰입니다.

"타입 좁히기(Type Narrowing)"는
TypeScript의 핵심 기능 중 하나로, 복잡한 타입 안에서 실제로 사용할 수 있는 정확한 타입을 좁혀주는 과정
을 말합니다.
type Shape = "circle" | "square";

function getArea(shape: Shape): number {
  switch (shape) {
    case "circle":
      // 👉 shape는 여기서 "circle"로 좁혀짐
      return 3.14 * 10 * 10;
    case "square":
      // 👉 shape는 여기서 "square"로 좁혀짐
      return 10 * 10;
    default:
      // 👉 위에서 처리 안 된 나머지 → shape는 never로 좁혀짐
      return assertNever(shape);
  }
}

function assertNever(x: never): never {
  throw new Error(`Unhandled case: ${x}`);
}

 

🔷 함수 오버로딩 (Function Overloading)

함수 오버로딩은 하나의 함수 이름으로 다양한 매개변수 타입이나 개수에 따라 다르게 동작하도록 정의할 수 있는 기능입니다.
이는 JavaScript에서는 직접 구현하기 어렵지만, TypeScript의 타입 시스템 덕분에 안전하게 사용할 수 있습니다.

 

1. 오버로드 시그니처 (Overload Signatures)
→ 호출 가능한 함수 타입 정의만 작성합니다. (본문 없음)
2. 구현 시그니처 (Implementation Signature)

→ 실제 구현 함수. 이 시그니처는 오버로드된 모든 경우를 포괄할 수 있어야 합니다.

// 1. 오버로드 시그니처 정의
function makeId(name: string): number;     // 문자열 → 숫자 반환
function makeId(count: number): string;    // 숫자 → 문자열 반환

// 2. 실제 구현 (모든 타입을 포괄)
function makeId(arg: string | number): number | string {
  if (typeof arg === "string") {
    return arg.length * 10; // 문자열 길이에 10을 곱해 숫자 반환
  } else {
    return "ID-" + arg.toString(); // 숫자를 문자열로 변환하여 반환
  }
}

// ✅ 사용 예시
let idNum = makeId("Alice"); // idNum은 number
let idStr = makeId(10);      // idStr은 string

console.log(idNum); // 출력: 50
console.log(idStr); // 출력: ID-10

오버로딩을 사용하면, 함수 내부에서는 string | number처럼 유니언 타입으로 처리하면서, 호출하는 쪽에서는 명확한 타입 추론이 가능해집니다.

 

✔️ 오버로드 시그니처 가 없는 경우

타입스크립트 입장에서는
“이 함수는 항상 number | string 둘 다 가능하네?”
라고만 알고 있기 때문에, 호출 위치에서 “매개변수에 따라 반환 타입이 달라진다”는 사실을 모릅니다.

따라서 아래와 같은 코드가 안됩니다.

const id = makeId("Alice");

// 에러: 'number | string'에는 toUpperCase가 없다.
console.log(id.toUpperCase());

// 에러: 'number | string'에는 toFixed가 없다.
console.log(id.toFixed(2));

오버로드 시그니처 가 없는 경우 오류예시
오버로드 시그니처 가 없는 경우 오류예시

4. 함수 타입 별칭과 콜백 타입 정의하기: 재사용 가능한 타입 설계

TypeScript에서는 변수나 객체뿐만 아니라 함수의 구조(시그니처)에도 타입 별칭(type)을 정의하여 코드의 재사용성, 가독성, 유지보수성을 높일 수 있습니다.

이렇게 함수의 타입 구조 전체에 이름을 붙여두고 사용하는 방식은, 공식 문서에서는 “Call Signature” 또는 “Function Type” 정의”라고 부릅니다.

 

🔷 함수 타입 별칭이란?

예를 들어, 다음과 같은 x와 y를 받아 number를 반환하는 함수가 여러 개 있다면, 매번 타입을 직접 작성하는 대신 공통된 타입 구조를 미리 정의해 둘 수 있습니다.

// 함수 타입 시그니처 정의: (매개변수1: 타입, 매개변수2: 타입) => 반환 타입
type MathOperation = (x: number, y: number) => number;

// 해당 타입을 재사용하여 여러 함수를 선언
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;

console.log(add(10, 5));      // 출력: 15
console.log(subtract(10, 3)); // 출력: 7

// 🚨 오류: 'string'은 'number'에 할당될 수 없습니다.
// subtractOperation(10, "5");

이처럼 함수 시그니처에 타입을 적용하면, 함수의 매개변수나 반환값이 잘못 사용될 경우 컴파일 시점에 오류를 알려줍니다.

 

🔷 콜백 함수 타입 정의하기

함수를 다른 함수의 인수로 전달하는 경우(예: 콜백 함수)에도, 타입 별칭을 사용하면 구조를 명확하게 정의할 수 있어 매우 유용합니다.

▸ 콜백 타입 선언 & 사용

// 콜백 함수 시그니처 정의: number를 받아 아무것도 반환하지 않는 함수
type Callback = (result: number) => void;

// calculator 함수는 두 숫자와 콜백 함수를 인수로 받습니다
function calculator(a: number, b: number, callback: Callback): void {
  const sum = a + b;
  callback(sum); // 콜백 함수 실행
}

// 콜백 함수 정의 (Callback 타입과 일치)
const printResult: Callback = (res) => {
  console.log(`계산 결과: ${res}`);
};

// 호출
calculator(20, 30, printResult); // 출력: 계산 결과: 50

 

▸ 콜백 타입이 유용한 상황 예시

// 🔹 제네릭을 활용한 콜백 함수 타입 별칭 정의
// Filter<T>는 어떤 타입 T를 받아서 true 또는 false를 반환하는 함수 타입입니다.
type Filter<T> = (item: T) => boolean;

// 🔹 숫자 배열을 준비합니다.
const numbers = [1, 2, 3, 4, 5];

// 🔹 Filter<number> 타입에 맞는 함수 정의
// 이 함수는 전달된 숫자가 짝수이면 true, 아니면 false를 반환합니다.
const isEven: Filter<number> = (n) => n % 2 === 0;

// 🔹 JavaScript의 배열 filter 메서드는 true를 반환하는 항목만 남깁니다.
// isEven 함수는 filter의 콜백으로 전달됩니다.
const evenNumbers = numbers.filter(isEven);

// 🔹 결과 출력: 짝수만 필터링되어 출력됩니다.
console.log(evenNumbers); // 👉 [2, 4]

제네릭 타입(Filter<T>)을 사용하면 다양한 타입에도 재사용 가능한 콜백 타입을 만들 수 있습니다.

▸ Filter<T>: 어떤 타입 T를 받아 true 또는 false를 반환하는 함수 시그니처
▸ filter(...): 배열에서 조건을 만족하는 요소만 남기는 내장 메서드
▸ isEven(...): 필터링 조건을 담은 콜백 함수이며, 타입 안정성을 위해 명시적으로 타입을 지정

 

🔷 함수 타입 별칭 vs 오버로드 시그니처(호출 선언)

1. type Callback = (result: number) => void; 는 “함수 타입 별칭”

type Callback = (result: number) => void;

▸ “이런 모양의 함수를 가리키는 타입 이름을 만든 것”
▸ 실제 함수 구현은 별도로 만들어야 함.

 

2. function makeId(name: string): number; 는 “오버로드 시그니처(호출 선언)”

function makeId(name: string): number;

▸ “makeId라는 함수가 이렇게 호출될 수 있다”는 함수 선언/오버로드 시그니처
▸ 보통은 여러 시그니처 + 하나의 구현과 함께 써서, 인자 타입에 따라 다른 반환 타입을 정밀하게 표현할 때 사용.

 

3. 비교

코드 역할 사용예
type Callback = (result: number) => void; 함수 타입(alias) 정의 변수/매개변수/프로퍼티에 붙이는 타입
function makeId(name: string): number; 함수 선언/오버로드 시그니처 (구현 없음) makeId라는 함수 이름에 붙는 호출 타입

※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.

반응형

 

반응형