본문으로 건너뛰기

01 개념

1. 오류 종류

1. 구문 오류

타입스크립트가 자바스크립트로 변환되는 것을 차단한 경우 타입스크립트가 코드로 이해할 수 없는 잘못된 구문을 감지

let let wat;
// ',' expected.(1005)

2. 타입 오류

타입 검사기에 따라 일치하지 않는 것이 감지 타입스크립트의 타입 검사기가 프로그램의 타입에서 오류를 감지

console.blub();
// Property 'blub' does not exist on type 'Console'.(2339)

2. 객체

1. 명시된 객체 타입 유니언

type PoemWithPages = {
name: string;
pages: number;
};

type PoemWithRhymes = {
name: string;
rhymes: boolean;
};

type Poem = PoemWithPages | PoemWithRhymes;

const poem: Poem =
Math.random() > 0.5
? { name: 'The Double', pages: 7 }
: { name: 'Kind', rhymes: true };

poem.name; // OK
poem.pages; // Error
poem.rhymes; // Error

poem은 PoemWithPages 또는 PoemWithRhymes 타입을 갖는다. PoemWithPages, PoemWithRhymes 타입은 각각 names을 갖고 있지만, pages, rhymes를 따로 가지고 있다.

즉, poem.pages와 poem.rhymes는 항상 존재한다는 보장이 없기 때문에 타입가드로 객체 타입 유니언의 타입을 좁혀야 한다.

2. 객체 타입 내로잉

그렇다면 어떻게 타입을 좁혀야 할까? 타입 검사기가 유니언 타입 값에 특성 속성이 포함된 경우에만 코드 영역을 실행하게 하면 된다.

값의 타입을 해당 속성을 포함하는 구성 요소로 좁히는 방법으로 코드의 영역을 실행하게 해보자.

즉, 코드에서 객체의 형태를 확인하고 타입 내로잉이 객체에 적용된다.

type PoemWithPages = {
name: string;
pages: number;
};

type PoemWithRhymes = {
name: string;
rhymes: boolean;
};

type Poem = PoemWithPages | PoemWithRhymes;

const poem: Poem = Math.random() > 0.5
? { name: "The Double", pages: 7 }
: { name: "Kind", rhymes: true };

// poem의 pages가 타입스크립트의 타입가드 역할을 해, PoemWithPages 임을 나타내는지 확인한다.
if ("pages" in poem) {
poem.pages; // OK: poem은 PoemWithPages로 좁혀짐
} else {
poem.rhymes; // OK: poem은 PoemWithRhymes로 좁혀짐
}

if(poem.pages) { ... } // Error
노트

타입스크립트는 if(poem.pages)와 같은 형식으로 참 여부를 확인하는 것을 허용하지 않는다. 존재하지 않는 객체의 속성에 접근하려고 시도하면 타입 가드처럼 작동하는 방식으로 사용되더라도 타입 오류로 간주한다.

노트

in 연산자 명시된 속성이 명시된 객체에 존재하면 true를 반환합니다.

3. 판별된 유니언

in 연산자를 사용하는 것 외에, 판별된 유니언으로 타입을 좁히는 방법도 존재한다.

객체의 속성이 객체의 형태를 나타내도록 하는 타입 형태를 판별된 유니언이라 부르고 객체의 타입을 가리키는 속성을 판별값이라고 한다.

// 판별된 유니언
type PoemWithPages = {
name: string;
pages: number;
// 판별값
type: 'pages';
};

type PoemWithRhymes = {
name: string;
rhymes: boolean;
type: 'rhymes';
};

type Poem = PoemWithPages | PoemWithRhymes;

const poem: Poem =
Math.random() > 0.5
? { name: 'The Double', pages: 7, type: 'pages' }
: { name: 'Kind', rhymes: true, type: 'rhymes' };

if (poem.type === 'pages') {
console.log(`It's got pages: ${poem.pages}`); // OK
} else {
console.log(`It rhymes: ${poem.rhymes}`); // OK
}

poem.type; // 'pages' | 'rhymes'
poem.pages; // Property 'pages' does not exist on type 'Poem'.
// Property 'pages' does not exist on type 'PoemWithRhymes'.

type에 판별된 유니언을 만들어서 넣어주어야 한다.

4. 교차 타입

type ShortPoem = { author: string } & (
| { kigo: string; type: 'haiku' }
| { meter: number; type: 'villanelle' }
);

// Ok
const morningGlory: ShortPoem = {
author: 'Fukuda Chiyo-ni',
kigo: 'Morning Glory',
type: 'haiku',
};

const oneArt: ShortPoem = {
author: 'Elizabeth Bishop',
type: 'villanelle',
};
// Error: Type '{ author: string; type: "villanelle"; }'
// is not assignable to type 'ShortPoem'.
// Type '{ author: string; type: "villanelle"; }' is not assignable to
// type '{ author: string; } & { meter: number; type: "villanelle"; }'.
// Property 'meter' is missing in type '{ author: string; type: "villanelle"; }'
// but required in type '{ meter: number; type: "villanelle"; }'.

문제는 없지만 이렇게 교차 타입을 쓰면 할당 가능성 오류 메시지가 너무 길어지고 읽기 어려워진다. 즉, 타입 검사기의 메시지도 이해하기 어려워진다. 별칭으로 타입을 쪼개도록 하자.

type ShortPoemBase = { author: string };
type Haiku = ShortPoemBase & { kigo: string; type: 'haiku' };
type Villanelle = ShortPoemBase & { meter: number; type: 'villanelle' };
type ShortPoem = Haiku | Villanelle;

const oneArt: ShortPoem = {
author: 'Elizabeth Bishop',
type: 'villanelle',
};
// Type '{ author: string; type: "villanelle"; }'
// is not assignable to type 'ShortPoem'.
// Type '{ author: string; type: "villanelle"; }'
// is not assignable to type 'Villanelle'.
// Property 'meter' is missing in type
// '{ author: string; type: "villanelle"; }'
// but required in type '{ meter: number; type: "villanelle"; }'.

또한 교차 타입은 잘못 사용하기 쉽고 불가능한 타입을 생성한다. 원시 타입의 값은 동시에 여러가지 타입이 될 수 없으므로 교차 타입의 구성요소로 함께 결합할 수 없다.

typeNotPossible = number & string;
// Type: never

let notNumber: NotPossible = 0;
// ~~~~~~~~~
// Error: Type 'number' is not assignable to type 'never'.

let notString: never = '';
// ~~~~~~~~~
// Error: Type 'string' is not assignable to type 'never'.

never은 대부분 교차 타입을 잘못 사용해 발생한 실수다.