프로그래밍프론트엔드 개발자

함수가 매개변수에 따라 union type 또는 tuple을 반환하도록 구현하고 타입을 지정하는 방법은 무엇인가요? 이후의 작업 및 구조 분해 할당 사용 시 엄격한 타입 유지를 위해 반환값을 어떻게 정확히 설명해야 하나요?

Hintsage AI 어시스턴트로 면접 통과

답변.

질문 배경:

일부 문제에서는 함수가 서로 다른 데이터 구조를 반환할 수 있어야 합니다. 예를 들어, 고정 길이의 배열(tuple) 또는 여러 결과의 변형(union type)을 반환해야 할 수 있습니다. 동적 JavaScript에서는 이 접근 방식이 ‘결과 또는 오류’ 패턴(예: [err, value])에서 널리 사용되지만, TypeScript에서는 후속 코드가 결과 구조를 올바르게 이해하도록 명확한 타입 지정이 필요합니다.

문제:

명시적으로 반환 타입을 지정하지 않으면 TypeScript는 결과를 너무 ‘광범위한’(즉, 좁지 않은) 형태로 규정하게 되며, 이는 개발자가 자동 완성과 타입 안전성을 잃게 만듭니다. 함수 결과를 구조 분해 할당할 경우 요소의 정확한 타입 설명을 잃을 위험이 있습니다.

해결책:

조건부 타입(conditional types)과 함수의 오버로드(overload) 설명을 사용해야 하며, tuple의 경우 결과 배열의 타입을 명시적으로 지정하고 as const를 사용해야 합니다(상수 튜플을 반환하는 경우). union 결과는 제너릭과 타입 좁히기(type narrowing)를 결합하여 외부 코드에서 반환 결과의 타입을 올바르게 구분할 수 있도록 해야 합니다.

코드 예시:

type Result<T> = [null, T] | [Error, null]; function parseNumber(str: string): Result<number> { const n = Number(str); return isNaN(n) ? [new Error('유효하지 않은 숫자'), null] : [null, n]; } const [err, value] = parseNumber('123'); if (err) console.error(err.message); else console.log(value!.toFixed(2)); // tuple 및 as const로 자동 추론 구조: function getStatus(flag: boolean): [string, number] | string { return flag ? ['ok', 200] as const : 'error'; } const r = getStatus(true); if (Array.isArray(r)) { // r: readonly [string, number] }

주요 특징:

  • 매개변수에 따라 다양한 타입을 반환하는 함수에 대해 오버로드를 사용하세요.
  • tuple에 대해 항상 [T, U] 또는 as const를 사용하여 구조 분해 후 정확한 길이와 요소 타입을 유지하세요.
  • 반환값의 union 타입은 추가 작업 시 타입을 좁히기 위해 타입 가드를 필요로 합니다.

속임수 질문.

튜플의 union 반환 후 구조 분해 할당된 배열 요소의 타입은 항상 구체적인가요?

아니요, 함수가 튜플의 union을 반환하면, 구조 분해 후 요소들은 모든 가능한 변형의 타입이 조합된 형태가 됩니다.

type R = [number, null] | [null, string]; const [a, b]: R = [1, null]; // a: number | null // b: null | string

as const를 사용하여 튜플 타입을 추가 설명 없이 완전히 ‘고정’시킬 수 있나요?

아니요, as const는 값은 고정시키지만, 함수가 반환 타입 없이 선언되면 TypeScript는 필요 이상으로 타입을 넓힐 수 있습니다. 반환 타입을 명시적으로 지정하는 것이 좋습니다.

function foo() { return [1, 'ok'] as const; } // foo(): readonly [1, "ok"]

다양한 반환 타입을 가진 오버로드가 구조 분해 후 결과 타입을 보장하나요?

오버로드는 컴파일러에 도움이 되지만, 입력 매개변수가 알려지지 않으면 결과는 모든 변형의 union입니다. 정확한 타입 좁히기를 위해 결과 처리기에서 타입 가드를 사용해야 합니다.

function bar(x: number): string; function bar(x: boolean): number; function bar(x: any): any { return typeof x === 'number' ? 'str' : 123; } const r = bar(Math.random() > 0.5 ? true : 1); // r: number | string

타입 오류 및 안티 패턴

  • 함수가 any[] 또는 (T | U)[]를 반환한다고 설명하여 tuple의 특성을 흐리게 만드는 경우.
  • 타입 좁히기 및 요소 타입 검사가 없이 구조 분해 할당을 시도하는 경우.
  • 반환값 구조에 대한 직접적인 설명이 없는 경우 — 지원 및 자동 완성을 복잡하게 만듭니다.

사례 연구

부정적 케이스

함수는 배열이나 오류를 반환하지만, 이를 type signature에서 명확하게 설명하지 않습니다. 호출 코드에서 결과가 숫자일 것이라고 예상하여 result[1].toFixed(2) 호출 시 런타임 오류가 발생합니다.

장점:

  • 간단한 타입 없는 코드 작성.

단점:

  • 런타임에서만 오류 발생.
  • 엄격한 타입 손실로 인해 버그가 증가합니다.

긍정적 케이스

함수는 Result<T> 타입의 엄격하게 타입 지정된 튜플을 반환하며, 결과 처리는 구조 분해 및 첫 번째 요소(error/null)에 대한 명시적 런타임 검사를 기반으로 합니다. 컴파일러는 결과에 접근할 수 있는지 여부를 성공적으로 타입 좁히기하여 보장합니다.

장점:

  • 코드의 예측 가능성과 명확성.
  • 튜플에 대한 자동 완성 지원.

단점:

  • 타입을 수동으로 설명해야 할 필요.
  • 타입 가드 및 검사가 증가합니다.