TypeScript의 일반 유형 가드에 대한 주의 사항(및 솔루션)입니다.

특히 API와 상속에서 사용자 데이터를 다룰 때 코드를 일반화하고 DRY 원칙을 따르는 것이 종종 어렵습니다.

TypeScript 언어는 타입 가드 🛡️라는 개념을 사용합니다. 이는 더 안전한 코드를 작성하고 화나고 불평하는 컴파일러를 처리하는 데 도움이 되는 영리한 컴파일러 기능입니다.

컴파일러는 가드를 사용하여 값 유형을 좁히고 IntelliSense 제안을 제공합니다.

주어진 상속 모델이 있다고 가정해 보겠습니다.

class Vehicle {
  brand: string;
}

class Aircraft extends Vehicle {
  usage: 'civil' | 'military';
}

class Car extends Vehicle {
  drive: 'AWD' | 'FWD' | 'RWD';
}

속성 측면에서 확장secretVehicle하는 것으로 알고 있는 Vehicle 객체가 제공됩니다. 그러나 개체는 이러한 클래스의 인스턴스가 아닙니다.

따라서 instanceof 접근 방식은 왼쪽 피연산자가 인스턴스여야 하므로 작동하지 않습니다.

if (secretVehicle instanceof Car) {
  console.log(`This is a car with ${secretVehicle.drive} drive`);
  // TypeScript doesn't complain, but this will never print!
}

대신 우리가 할 수 있는 일은 secretVehicle 서브클래스의 모든 속성이 있는지 확인하는 것입니다.

리플렉션을 사용하거나 해당 클래스의 실제 인스턴스를 만들고 Object.keys() 를 사용하여 키를 조회하여 이를 수행합니다.

export const hasAllKeys =
  <T>(obj: Record<string, any>, cls: new () => T): obj is T => {
    const properties = Object.keys(new cls());
    for (const p of properties) {
      if (!(p in obj)) return false;
    }
    return true;
  };

그런 다음 가드를 사용하여 TypeScript에 secretVehicle가 실제로 주어진 유형임을 확인할 수 있습니다.

if (hasAllKeys(secretVehicle, Car)) {
  console.log(`This is a car with ${secretVehicle.drive} drive`);
}
if (hasAllKeys(secretVehicle, Aircraft)) {
  console.log(`This is a ${secretVehicle.usage} aircraft`);
}

그러나 일부 극단적인 경우 이 솔루션은 문제가 있습니다. 사용자 지정 생성자가 있는 클래스와 함께 사용하면 속성을 잘못 확인할 수 있습니다.

더욱이 때로는 단순히 우리에게 필요한 것이 아닙니다. 우리가 얻는 입력 데이터는 종종 Partial<T> 대신 T 이며, 이는 일부 속성이 누락될 수 있음을 의미합니다(예: id ).

이에 대응하기 위해 모든 속성 대신 특정 속성을 확인하는 가드를 사용합시다.

export const hasKeys =
  <T>(
    obj: Record<string, any>,
    properties: (keyof T)[]
  ): obj is T =>
    properties.filter(p => p in obj).length == properties.length;
    // functional approach

TypeScript 컴파일러는 지정하고 싶지 않은 경우 자체적으로 T 알아낼 만큼 충분히 영리합니다.

예를 들어, hasKeys(secretVehicle, ['usage'])T{usage: any} 유형이라고 추론하므로 if 문 내부에서 usage 키를 사용할 수 있습니다.

if (hasKeys(secretVehicle, ['usage'])) {
  console.log(`
    Not sure what this is,
    but it has a ${secretVehicle.usage} usage!
  `);
}

아아, 이것은 우리가 any 유형의 값에 대해 작업하도록 합니다.
해당 키의 유형을 전달할 수 있습니다.

hasKeys<{usage: 'civil' | 'military'}>(secretVehicle, ['usage']);

또는 단순히 전체 클래스를 전달하십시오.

hasKeys<Aircraft>(secretVehicle, ['usage']);

이렇게 하면 키를 정의할 때 IntelliSense 제안도 제공됩니다!

그러나 두 하위 클래스에 동일한 필드가 있지만 유형이 다른 경우에는 어떻게 될까요? 문제가 더 복잡해지고 리플렉션을 사용해야 할 수도 있습니다.
그러나 기본 클래스에 type 필드를 지정하여 유형을 쉽게 구분함으로써 이 문제를 극복할 수 있습니다.

class Vehicle {
  brand: string;
  type: 'Car' | 'Aircraft';
}

const ofType =
  <T>(
    obj: Record<string, any> & {type?: string},
    cls: new () => T
  ): obj is T =>
    obj.type == (new cls()).constructor.name;
    // or use another argument for the type field

if (ofType(secretVehicle, Car)) {
  console.log(`This is a car with ${secretVehicle.drive} drive`);
}



TypeScript는 강력한 언어이며 이러한 구문을 사용하면 최대한 활용하는 데 도움이 됩니다.
dev.to 커뮤니티에 대한 저의 첫 번째 기고문을 읽어주셔서 감사합니다.

즐거운 코딩! 🎉

좋은 웹페이지 즐겨찾기