Programing

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

crosscheck 2020. 12. 30. 19:02
반응형

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)을 얻는 경우 유용한 오류 메시지를 표시 할 수 있습니다.


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

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

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

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'
            throw new UnreachableCaseError(dataType);
    }
}

throw절에 기본 구문 강조 표시가 있기 때문에 읽기가 더 쉽다고 생각합니다 .


You don't need to use never or add anything to the end of your switch.

If

  • Your switch statement returns in each case
  • You have the strictNullChecks typescript compilation flag turned on
  • Your function has a specified return type
  • The return type is not undefined or void

You will get an error if your switch statement is non-exhaustive as there will be a case where nothing is returned.

From your example, if you do

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

You will get the following compilation error:

Function lacks ending return statement and return type does not include undefined.


Building on top of Ryan's answer, I discovered here that there is no need for any extra function. We can do directly:

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("How did we get here?");
  }
}

You can see it in action here in TS Playground


Create a custom function instead of using a switch statement.

export function exhaustSwitch<T extends string, TRet>(
  value: T,
  map: { [x in T]: () => TRet }
): TRet {
  return map[value]();
}

Example usage

type MyEnum = 'a' | 'b' | 'c';

const v = 'a' as MyEnum;

exhaustSwitch(v, {
  a: () => 1,
  b: () => 1,
  c: () => 1,
});

If you later add d to MyEnum, you will receive an error Property 'd' is missing in type ...

ReferenceURL : https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript

반응형