TypeScript에서의 형제이전제(type predicates)는 이해하기 어려운 구문적 특징입니다. 반환 형제annotation과 같은 곳에 나타나지만, 일반 annotation과 다르게 짧은 affirmative 문장처럼 보입니다. 이것은 형제 점검을 더욱 자주 사용하는 것입니다.

TypeScript 5.5 릴리스 이후, 형제이전제를 사용하는 것이 더욱 直观적이었습니다. 많은 경우 자동으로 추론할 수 있습니다. 하지만 약간 더 老旧한 코드 베이스에 있으면, 亲手 写出한 형제이전제를 자주 만나게 됩니다.

이 記事에서는 간단히 형제이전제가 무엇인지 그리고 왜 유용한지 예시로 보여드릴 것입니다. 그렇다면 어느 문제를 해결하는지 보는 것이 좋습니다.

문제

형제이전제의 유용성을 이해하는 가장 좋은 방법은, 그들이 없는 경우 발생하는 문제를 注意하는 것입니다.:

function isString(value: unknown): boolean {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // string | number
}

이 예시에서 isString 함수의 반환 형제가 boolean로 설정되었고, padLeft 함수에서 입력 문자열의 왼쪽에 패딩을 추가하는 것을 용이하게 해줍니다. padding는 문자열을 주어진 것이나 지정한 공간 문자의 수입니다.

리턴 타입을 boolean으로 하드코딩한 이유에 대해 궁금할 수 있습니다. 이는 문제를 설명하기 위한 것입니다. 만약 리턴 타입 어노테이션을 추가하지 않고 TypeScript의 최신 버전을 사용한다면 여기서는 어떤 문제도 발견하지 못할 것입니다. 지금은 양해를 구합니다 – 버전 관련 차이에 대해 곧 논의하겠습니다.

이 함수는 런타임에서 원활하게 작동하지만 TypeScript는 isString으로 타입을 좁힐 수 없습니다. 그 결과, padding의 타입은 if문 안과 밖에서 모두 string | number로 유지됩니다. 이는 repeat의 첫 번째 인자에 대한 기대와 충돌을 일으켜 타입 오류가 발생합니다.

해결책: 타입 프리디케이트 사용

타입 프리디케이트라는 용어를 들어보지 못한 사람이라도 이미 사용해 본 적이 있을 것입니다. 프로그래밍에서 프리디케이트는 예/아니오 질문에 대한 답으로 불리언 값을 반환하는 단순한 함수입니다. filter, find, every, some 등의 JavaScript 내장 배열 메서드들은 프리디케이트를 사용하여 결정을 돕습니다.

타입 프리디케이트는 타입 좁힘에 더 유용하게 사용할 수 있는 방법입니다. 타입 프리디케이트를 리턴 타입으로 사용하여 문제를 해결할 수 있습니다:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

여기에서 타입 프리디케이트는 value is string입니다. 이것은 세 가지를 의미합니다:

  • 이 함수는 프리디케이트입니다. 따라서 TypeScript는 불리언 값 이외의 것을 리턴하려고 하면 오류를 표시합니다.

  • 真假면 value는 문자열 유형입니다.

  • 거짓이면 value는 문자열 유형이 아닙니다.

유형 제약 조건을 사용하여 사용자 정의 유형 가드를 만들 수 있습니다. 유형 가드는 더 구체적인 유형으로 유형을 좁히는 논리적인 점검입니다. 따라서, 위의 함수는 또한 사용자 정의 유형 가드입니다.

전체 코드는 다음과 같습니다:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string
  }
  return " ".repeat(padding) + input;
                 //   ^
                 // number
}

여기에서 TypeScript가 if 구문 내과 外에서 padding의 유형을 좁혀주는 것을 correctly 다시 확인할 수 있습니다.

이제 TypeScript 5.5 이전에 유형 제약 조건이 어떻게 작동했는지 짧게 살펴봅시다.

TypeScript 5.5 이전의 Type Predicates

이전 예시에서 리턴 유형을 지정하지 않으면 boolean로 推论되ます:

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // string | number
}

따라서, 우리는 직접 boolean 리턴 형이라는 에러를 手動으로 썼을 때와 같은 에러를 가지고 있습니다. 위에 있는 코드 fragment에 대한 TypeScript 遊び場 링크을 보세요. 함수나 변수를 마우스 오버하여 형이 어떻게 되는지 느낄 수 있습니다. 그 다음, type predicate를 쓰는 것이 문제를 해결하는 방법을 보여주며 이를 확인하십시오.

type predicate를 지정하지 않는다면, filter과 같은 방법을 사용하여 오rong type detection이 나는 것も 가능합니다.:

function isString(value: unknown) {
  return typeof value === "string";
}

const numsOrStrings = [1, 'hello', 2, 'world'];
//      ^
//    strings: (string | number)[]

const strings = numsOrStrings.filter(isString);
//      ^
//    strings: (string | number)[]

이제 TypeScript 5.5가 상황을 어떻게 改善하는지 보겠습니다.

Type Predicates After TypeScript 5.5

TypeScript 5.5의 주요 기능 중 하나는 함수 몸체를 분석하여 type predicate를 추론하는 것입니다. 따라서, TypeScript 5.5 이상을 사용하고 있다면 isString의 리턴 형이나 type predicate를 쓸 필요가 없습니다. TypeScript가 이를 하게 해줍니다. 아래의 예제와 같은 코드는 完璧하게 작동합니다.

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // 문자열
  }
  return " ".repeat(padding) + input; // 여기서 타입 오류 발생
                 //   ^
                 // 숫자
}

const numsOrStrings = [1, 'hello', 2, 'world'];

const strings = numsOrStrings.filter(isString);
//      ^
//    strings: string[]

const numbers = numsOrStrings.filter((v) => !isString(v));
//      ^
//    numbers: number[]

나는 아직까지 자동 유추된 타입 예측에 불만족한 상황을 찾지 못했다. 그러나 당신이 찾는다면, 직접 작성할 수도 있다.

추가 공부

이 글에서는 TypeScript의 타입 예측에 대해 간단히 알아보았다. 더 자세한 내용과 예외 사항을 이해하려면, 공식 가이드를 참조하십시오:

읽어주셔서 감사합니다! 다음 시간 에서 만나요!

和教育背静이 Mona EendraUnsplash에서 찍었습니다.