[typescript] TypeScript에서 스위치 블록이 완전한지 어떻게 확인합니까?

몇 가지 코드가 있습니다.

enum Color {
    Red,
    Green,
    Blue
}

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
        // Forgot about Blue
    }

    throw new Error('Did not expect to be here');
}

Color.Blue사건 을 처리하는 것을 잊었고 컴파일 오류가 발생하는 것을 선호합니다. TypeScript가이를 오류로 표시하도록 코드를 구성하려면 어떻게해야합니까?



답변

이를 위해 우리는 never발생하지 않아야하는 값을 나타내는 유형 (TypeScript 2.0에서 도입)을 사용합니다.

첫 번째 단계는 함수를 작성하는 것입니다.

function assertUnreachable(x: never): never {
    throw new Error("Didn't expect to get here");
}

그런 다음 default케이스 (또는 스위치 외부)에서 사용합니다.

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
    }
    return assertUnreachable(c);
}

이 시점에서 오류가 표시됩니다.

return assertUnreachable(c);
       ~~~~~~~~~~~~~~~~~~~~~
       Type "Color.Blue" is not assignable to type "never"

오류 메시지는 전체 스위치에 포함하는 것을 잊은 경우를 나타냅니다! 여러 값을 생략 한 경우 예를 들어 오류가 표시 Color.Blue | Color.Yellow됩니다.

을 사용하는 경우 호출 앞에 strictNullChecks필요합니다 (그렇지 않으면 선택 사항 임).returnassertUnreachable

원한다면 조금 더 멋지게 만들 수 있습니다. 예를 들어 판별 된 공용체를 사용하는 경우 디버깅 목적으로 assertion 함수에서 판별 속성을 복구하는 것이 유용 할 수 있습니다. 다음과 같이 보입니다.

// Discriminated union using string literals
interface Dog {
    species: "canine";
    woof: string;
}
interface Cat {
    species: "feline";
    meow: string;
}
interface Fish {
    species: "pisces";
    meow: string;
}
type Pet = Dog | Cat | Fish;

// Externally-visible signature
function throwBadPet(p: never): never;
// Implementation signature
function throwBadPet(p: Pet) {
    throw new Error('Unknown pet kind: ' + p.species);
}

function meetPet(p: Pet) {
    switch(p.species) {
        case "canine":
            console.log("Who's a good boy? " + p.woof);
            break;
        case "feline":
            console.log("Pretty kitty: " + p.meow);
            break;
        default:
            // Argument of type 'Fish' not assignable to 'never'
            throwBadPet(p);
    }
}

예상 한 모든 경우를 처리했는지 확인하기 위해 컴파일 타임 안전성을 확보하기 때문에 이것은 좋은 패턴입니다. 그리고 정말로 범위를 벗어난 속성 (예 : 일부 JS 호출자가 new를 구성 species)을 얻는 경우 유용한 오류 메시지를 표시 할 수 있습니다.


답변

never끝에 아무것도 사용 하거나 추가 할 필요가 없습니다 switch.

만약

  • 귀하의 switch명세서는 각 경우에 반환됩니다.
  • 당신은이 strictNullChecks타이프 라이터 컴파일 플래그가 켜져
  • 함수에 지정된 반환 유형이 있습니다.
  • 반환 유형이 undefined또는void

switch아무 것도 반환되지 않는 경우가 있기 때문에 진술이 완전하지 않으면 오류가 발생합니다 .

귀하의 예에서

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
        // Forgot about Blue
    }
}

다음과 같은 컴파일 오류가 발생합니다.

함수에 종료 반환 문이없고 반환 유형에 undefined.


답변

해결책

내가하는 일은 오류 클래스를 정의하는 것입니다.

export class UnreachableCaseError extends Error {
  constructor(val: never) {
    super(`Unreachable case: ${JSON.stringify(val)}`);
  }
}

그런 다음 기본 경우이 오류를 발생시킵니다.

enum Color {
    Red,
    Green,
    Blue
}

function getColorName(c: Color): string {
  switch(c) {
      case Color.Red:
          return 'red, red wine';
      case Color.Green:
          return 'greenday';
      case Color.Blue:
          return "Im blue, daba dee daba";
      default:
          // Argument of type 'c' not assignable to 'never'
          throw new UnreachableCaseError(c);
  }
}

throw절에 기본 구문 강조 표시가 있기 때문에 Ryan이 권장하는 함수 접근 방식보다 읽기가 더 쉽다고 생각합니다 .

힌트

TS-필수의 라이브러리는 클래스가 UnreachableCaseError을 이 사용 사례에 대해 정확히

런타임 고려 사항

typescript 코드는 javascript로 변환됩니다. 따라서 모든 typescript typecheck는 컴파일 타임에만 작동 하고 런타임 에는 존재 하지 않습니다 . 즉, 변수 c가 실제로 유형 이라는 보장은 없습니다 Color.
이것은 다른 언어와 다릅니다. 예를 들어 Java는 런타임에 유형을 확인하고 잘못된 유형의 인수를 사용하여 함수를 호출하려고하면 의미있는 오류가 발생하지만 javascript는 그렇지 않습니다.

이것이 다음과 같은 default경우에 의미있는 예외를 throw하는 것이 중요한 이유입니다 . Stackblitz : throw 의미있는 오류

이렇게하지 않으면 함수 getColorName()가 암시 적으로 반환합니다 undefined(예기치 않은 인수로 호출 될 때). Stackblitz : return any

위의 예에서는 any문제를 설명하기 위해 유형의 변수를 직접 사용했습니다 . 이것은 실제 프로젝트에서는 일어나지 않을 것이지만 런타임에 잘못된 유형의 변수를 얻을 수있는 다른 방법이 많이 있습니다.
내가 이미 본 것 중 일부는 다음과 같습니다.

  • 각도 형식 사용-형식이 안전하지 않습니다. 모든 형식 필드 값은 any
    ng 형식입니다. Stackblitz 예제
  • 암시 적 any 허용
  • 외부 값이 사용되고 유효성이 확인되지 않습니다 (예 : 서버의 http- 응답이 인터페이스로 캐스팅 됨)
  • 이전 버전의 앱이 작성한 로컬 저장소에서 값을 읽습니다 (이 값이 변경되었으므로 새 논리가 이전 값을 이해하지 못함).
  • 형식이 안전하지 않은 (또는 단순히 버그가있는) 타사 라이브러리를 사용합니다.

그러니 게으르지 말고이 추가 기본 케이스를 작성하십시오-많은 두통을 피할 수 있습니다 …


답변

Ryan의 답변을 바탕으로 추가 기능이 필요하지 않다는 것을 발견 했습니다 . 우리는 직접 할 수 있습니다.

function getColorName(c: Color): string {
  switch (c) {
    case Color.Red:
      return "red";
    case Color.Green:
      return "green";
    // Forgot about Blue
    default:
      const exhaustiveCheck: never = c;
      throw new Error(`Unhandled color case: ${exhaustiveCheck}`);
  }
}

여기 TS 플레이 그라운드 에서 작동하는 모습을 볼 수 있습니다.

편집 : “사용하지 않는 변수”린터 메시지를 피하기위한 제안 포함.


답변

typescript-eslint“노동 조합 유형 스위치의 포괄 성 검사” 규칙 :
@ 타이프 라이터 – eslint / 스위치, 포괄 성 검사


답변

Ryan의 대답에 대한 좋은 변형으로 never임의의 문자열로 대체 하여 오류 메시지를보다 사용자 친화적으로 만들 수 있습니다.

function assertUnreachable(x: 'error: Did you forget to handle this type?'): never {
    throw new Error("Didn't expect to get here");
}

이제 다음을 얻을 수 있습니다.

return assertUnreachable(c);
       ~~~~~~~~~~~~~~~~~~~~~
       Type "Color.Blue" is not assignable to type "error: Did you forget to handle this type?"

이것은 never임의의 문자열을 포함하여 모든 것에 할당 될 수 있기 때문에 작동 합니다.


답변

위에 구축 라이언카를로스 ‘ 답은 별도라는 이름의 함수를 만드는 것을 피하기 위해 익명의 방법을 사용할 수 있습니다 :

function getColorName(c: Color): string {
  switch (c) {
    case Color.Red:
      return "red";
    case Color.Green:
      return "green";
    // Forgot about Blue
    default:
      ((x: never) => {
        throw new Error(`${x} was unhandled!`);
      })(c);
  }
}

스위치가 완전하지 않으면 컴파일 시간 오류가 발생합니다.