TS) 형식을 값으로 사용하여 타입추론을 돕는 방법

김명성·2023년 8월 3일

사용하려는 인터페이스에 대해 일일히 if문으로 타입을 좁히기 힘들다면 implements키워드를 통해 클래스로 인터페이스를 구현하고 형식이 아닌 값이 된 타입을 통해 반복문으로 타입추론을 할 수 있다.

클래스가 인터페이스를 구현하기 위해서는 타겟 인터페이스에 선언된 프로퍼티와 메서드를 반드시 포함해야 한다.

1.기존의 인터페이스

export interface ProvincialEnrollSaveRequestDto {
  /**
   * 만 3세 인원 수 - 세부타입: 유치원
   * @format int32
   */
  age3?: number;

  /**
   * 만 4세 인원 수 - 세부타입: 유치원
   * @format int32
   */
  age4?: number;

  /**
   * 만 5세 인원 수 - 세부타입: 유치원
   * @format int32
   */
  age5?: number;

  /**
   * 노인 인원 수 - 세부타입: 노인
   * @format int32
   */
  elderly?: number;

  /**
   * 교육 시작 희망 일자
   * @format date
   * @example yyyy-MM-dd
   */
  expectedToStartDtime?: string;

  /**
   * 1학년 인원 수 - 세부타입: 초등학교, 중학교, 고등학교
   * @format int32
   */
  grade1?: number;

  /**
   * 2학년 인원 수 - 세부타입: 초등학교, 중학교, 고등학교
   * @format int32
   */
  grade2?: number;

  /**
   * 3학년 인원 수 - 세부타입: 초등학교, 중학교, 고등학교
   * @format int32
   */
  grade3?: number;

  /**
   * 4학년 인원 수 - 세부타입: 초등학교, 중학교, 고등학교
   * @format int32
   */
  grade4?: number;

  /**
   * 5학년 인원 수 - 세부타입: 초등학교, 중학교, 고등학교
   * @format int32
   */
  grade5?: number;

  /**
   * 6학년 인원 수 - 세부타입: 초등학교, 중학교, 고등학교
   * @format int32
   */
  grade6?: number;

  /** 소속 */
  organization?: string;

  /**
   * 자가 운전자 인원 수 - 세부타입: 자가운전자
   * @format int32
   */
  selfDriver?: number;
}
  1. implements keyword를 사용하여 타입을 위한 클래스라는 것을 명시
export class PrincialEnrollResponse implements ProvincialEnrollSaveRequestDto {
  age3: number;
  age4: number;
  age5: number;
  elderly: number;
  expectedToStartDtime: string;
  expiredDtime: string;
  grade1: number;
  grade2: number;
  grade3: number;
  grade4: number;
  grade5: number;
  grade6: number;
  modifiedDtime: string;
  organization: string;
  selfDriver: number;
  seq: number;
  status: number;
  userInfo: string;
  userSeq: number;

  constructor( ) {
    this.age3 = 0;
    this.age4 = 0;
    this.age5 = 0;
    this.elderly = 0;
    this.expectedToStartDtime = "";
    this.expiredDtime = "";
    this.grade1 = 0;
    this.grade2 = 0;
    this.grade3 = 0;
    this.grade4 = 0;
    this.grade5 = 0;
    this.grade6 = 0;
    this.modifiedDtime = "";
    this.organization = "";
    this.selfDriver = 0;
    this.seq = 0;
    this.status = 0;
    this.userInfo = '';
    this.userSeq = 0;
  }
}
  1. 함수로 구현, 매개변수가 해당 클래스에 들어있는지 확인

// 내가 클래스 또는 객체를 알고 있을 때 사용.
export const isAllowedType = (type: string): type is keyof PrincialEnrollResponse => Object.keys(new PrincialEnrollResponse()).includes(type);


// 내가 클래스 또는 객체를 모를 때 사용. 키만 사용할 것이기에 값은 any
export const isAllowedType2 = (obj: Record<string, any>, type: string): type is keyof typeof obj => Object.keys(obj).includes(type);
  1. 해당 함수 사용
const handleEduPersonCount = ( e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
    const {name, value} = e.target;
  
    // name이 ProvinceEnrollResponse에 포함되어 있지 않다면 return
    const isAllowed = isAllowedType(name);
    if(!isAllowed) return;

    if (value.length > 6) return;
    if (value === '') return setValue(name, +value) 
    if (value.length === 0 || value === '0') return setValue(name, +value);
    
    if (isNaN(+value)) return;
    setValue(name, Number(value.replace(/[^0-9]/g, '')));
  };
  1. JSX문 내에서도 사용 가능하다
<TextField
	onChange={handleEduPersonCount}
	name={ap}
	value={isAllowedType(ap) ? watch(ap) : 0}
		sx={{
			marginLeft: '-10px',
            mr: '10px',
            fontWeight: 'bold',
            mb: '4px',
           }}
	/>

isAllowedType을 사용하지 않는다면?

e.target.name은 string이기에 PrincialEnrollResponse보다 넓은 타입이다. 그러므로 타입을 좁혀주지 않는다면
string은 "" | "" | "" ... 에 포함될수 없으므로 ...ts(2345)에러가 발생한다.

1개의 댓글

comment-user-thumbnail
2023년 8월 3일

좋은 글 감사합니다.

답글 달기