diff --git "a/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.2_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\355\203\200\354\236\205_\352\260\200\353\223\234/seulgi.md" "b/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.2_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\355\203\200\354\236\205_\352\260\200\353\223\234/seulgi.md" index 3beced8..a9f75d9 100644 --- "a/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.2_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\355\203\200\354\236\205_\352\260\200\353\223\234/seulgi.md" +++ "b/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.2_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\355\203\200\354\236\205_\352\260\200\353\223\234/seulgi.md" @@ -1 +1,240 @@ - +# 타입 좁히기 - 타입 가드 + +타입 좁히기는 변수 또는 표현식의 타입 범위를 더 작은 범위로 좁혀나가는 과정이다. +더 정확하고 명시적인 타입 추론을 할 수 있고, 복잡한 타입을 작은 범위로 축소하여 타입 안정성을 높일 수 있다. + +## 1) 타입 가드에 따라 분기 처리하기 + +타입스크립트에서 분기 처리는 **조건문과 타입 가드를 활용**한다.
변수나 표현식의 타입 범위를 좁혀 다양한 상황에 따라 다른 동작을 수행하는 것이다. + +타입 가드는 **런타임에 조건문을 사용해 타입을 검사해 타입 범위를 좁히는 기능**이다. + +### 여러 타입을 할당할 수 있는 스코프에서 분기 처리하려면? + +여러 타입을 할당할 수 있는 스코프(타입스크립트에서 스코프는 변수와 함수 등 식별자가 유효한 범위, +
즉 변수와 함수를 선언하거나 사용할 수 있는 영역)에서 특정 타입을 조건으로 만들어 분기 처리하고 싶을 때가 있다. + +> 이 때, 여러 타입을 할당할 수 있다는 것은 변수가 유니온 타입 또는 any 타입 등과 같이 여러 가지 타입을 받을 수 있다는 것을 말한다. + +### 함수가 A | B 타입의 매개변수를 받을 때,
인자 타입이 A 또는 B일 때를 구분해서 로직을 처리하고 싶다면? + +if 문을 사용해서 처리한다해도 컴파일 시 타입 정보는 모두 제거되기 때문에 런타임에 존재하지 않는다. + +> 컴파일해도 타입 정보가 사라지지 않는 방법을 사용해야 하고, 특정 문맥 안에서 타입스크립트가 해당 변수를 타입 A로 추론하도록 유도하면서 런타임에서도 유효한 방법이 필요하다! + +### 이 때 사용하는 것이 "타입 가드"! + +1. 자바스크립트 연산자를 사용한다. +2. 사용자 정의 타입 가드로 사용한다. + +위의 두 방법으로 타입 가드를 사용할 수 있다. + +### 자바스크립트 연산자를 활용한 타입 가드에 대해 알아보자. + +- typeof, instanceof, in과 같은 연산자를 사용한다. +- 제어문으로 특정 타입 값을 가질 수밖에 없는 상황을 유도해 자연스럽게 타입을 좁히는 방식이다. + +> 런타임에 유효한 타입 가드를 만들기 위해 자바스크립트 연산자를 사용한다. + +
+
+ +## 2) 원시 타입을 추론할 때: typeof 연산자 활용하기 + +- typeof 연산자를 활용하면 원시 타입에 대해 추론할 수 있다. +- typeof A === B를 조건으로 분기 처리하면 해당 분기 내에서 A의 타입이 B로 추론된다. +- 다만, typeof는 자바스크립트 타입 시스템만 대응 가능하다. +- null, 배열 타입 등 object 타입이 판별되는 등 복잡한 타입을 검증하기에는 한계가 있다. + +> 따라서, typeof 연산자는 주로 원시 타입을 좁히는 용도로만 사용하는 것을 권장한다. + +### typeof 연산자를 사용해 검사할 수 있는 타입들 + +- string +- number +- boolean +- undefined +- object +- function +- bigint +- symbol + +```ts +const replaceHyphen: (date: string | Date) => string | Date = (date) => { + if (typeof date === "string") { + return date.replace(/-/g, "/"); + } + + return date; +}; +``` + +
+
+ +## 3) 인스턴스화된 객체 타입을 판별할 때: instanceof 연산자 활용하기 + +selected 매개변수가 Date인지 검사 후 Range 타입의 객체를 반환할 수 있도록 분기처리하는 예시를 보자. + +```ts +interface Range { + start: Date; + end: Date; +} + +interface DatePickerProps { + selectedDates?: Date | Range; +} + +const DatePicker = ({ selectedDates }: DatePickerProps) => { + const [selected, setSelected] = useState(convertToRange(selectedDates)); +}; + +export function convertToRange(selected?: Date | Range): Range | undefined { + return selected instanceof Date + ? { start: selected, end: selected } + : selected; +} +``` + +- instanceof 연산자는 인스턴스화된 객체 타입을 판별하는 타입 가드로 사용한다. +- A instanceof B 형태로 사용한다. +- A에는 타입을 검사할 대상 변수, B에는 특정 객체의 생성자가 들어간다. + +> **instanceof?**
+> instanceof는 A의 프로토타입 체인에 생성자 B가 존재하는지를 검사해
존재한다면 true, 그렇지 않다면 false를 반환한다. + +### A의 프로토타입 속성 변화에 따라 instanceof 연산자의 결과가 달라질 수 있다. + +```ts +const onKeyDown = (event: React.KeyboardEvent) => { + if (event.target instanceof HTMLElement && event.key === "Enter") { + event.target.blur(); + onCTAButtonClick(event); + } +}; +``` + +
+
+ +## 4) 객체의 속성이 있는지 없는지에 따른 구분: in 연산자 활용하기 + +- in연산자는 객체에 속성이 있는지 확인한 다음에 true 또는 false를 반환한다. +- A in B의 형태로 사용한다. +- A라는 속성이 B 객체에 존재하는지를 검사한다. +- 프로토타입 체인으로 접근할 수 있는 속성이면 전부 true를 반환한다. + +### 프로토타입 체인으로 접근할 수 있는 속성이면 전부 true를 반환한다? + +```ts +const obj = { a: 1 }; + +console.log("a" in obj); // true (자기 자신의 속성) +console.log("toString" in obj); // true (Object.prototype에 존재하는 속성) +console.log("b" in obj); // false (존재하지 않는 속성) +``` + +> 객체 자체뿐만 아니라 프로토타입 체인까지 확인하는 연산자가 바로 in 연산자! + +- B 객체에 존재하는 속성에 undefined를 할당한다고 해서 false를 반환하지 않는다. +- delete 연산자를 사용해 객체 내부에서 해당 속성을 제거해야만 false를 반환한다. + +```ts +interface BasicDialogProps { + Title: string; + Body: string; +} + +interface DialogWithCookieProps extends DialogProps { + cookieKey: string; + noForADay?: boolean; +} + +export type DialogProps = BasicDialogProps | DialogWithCookieProps; +``` + +위의 Dialog 컴포넌트는 BasicDialogProps | DialogWithCookieProps를 유니온 타입으로 가지는 DialogProps를 props로 받는다. + +> Dialog 컴포넌트가 props로 받는 객체 타입이 둘 중에 어떤 타입이냐에 따라 렌더링하는 컴포넌트가 달라지게 하려면 props의 타입에 따라 렌더링하는 컴포넌트를 분기 처리하면 된다. + +### 위의 예제에서 두 객체 타입을 어떻게 분기 처리할까? + +- 자바스크립트 in 연산자는 런타임의 값만 검사한다. +- 타입스크립트에서는 객체 타입에 속성이 존재하는지를 검사한다. + +```ts +const Dialog: React.FC = (props) => { + if ("cookieKey" in props) { + return ; + } + + return ; +}; +``` + +- 위의 예제는 두 객체 타입을 cookieKey라는 속성을 가졌는지 확인하는 조건을 만든다. +- if문 스코프에서 타입스크립트는 props 객체를 cookieKey 속성을 갖는 객체 타입인 DialogWithCookieProps로 해석한다. +- 얼리 리턴으로, if문 스코프 밖에 위치하는 return문의 props 객체는 BasicDialogProps 타입으로 해석한다. + +> 여러 객체 타입을 유니온 타입으로 가지고 있을 때 in 연산자를 사용해 속성의 유무에 따라 조건 분기를 할 수 있다. + +
+
+ +## 5) is연산자로 사용자 정의 타입 가드 만들어 활용하기 + +직접 타입 가드 함수를 만들 수 있다. 해당 방식의 타입 가드는 반환 타입이 타입 명제(type predicates)인 함수를 정의해 사용할 수 있다. + +- 타입 명제는 A is B 형식으로 작성한다. +- A는 "매개변수 이름", B는 "타입"이다. ("매개변수" is "타입") +- 참/거짓의 진릿값을 반환하면서 반환 타입을 타입 명제로 지정하게 되면 반환 값이 참일 때 A 매개변수의 타입을 B타입으로 취급한다. +- 타입 명제는 함수의 반환 타입에 대한 타입 가드를 수행하기 위해 사용되는 특별한 형태의 함수다. + +### predicates? + +수학적으로 주어진 조건을 만족하는지 아닌지를 테스트하는 함수나 표현식을 의미한다. +프로그래밍 컨텍스트에서는 보통 어떤 값을 입력으로 받아 Boolean 값을 반환하는 함수를 가리키는데 대부분 통용되는 용어다. + +```ts +const isDestinationCode = (x: string): x is DestinationCode => + destinationCodeList.includes(x); +``` + +1. `isDestinationCode` 함수는 string 타입의 x 매개변수가 `DestinationCode` 배열의 원소 중 하나인지를 검사해 `boolean`을 반환하는 함수이다. +2. 함수의 반환 값을 `boolean`이 아닌 `x is DestinationCode`로 타이핑한다. +3. 해당 타이핑으로 타입스크립트에게 이 함수가 사용되는 곳의 타입을 추론할 때 해당 조건을 타입 가드로 사용하도록 알려준다. + +### 반환 값의 타입이 boolean인 것과 비교해보자 + +```ts +const getAvailableDestinationNameList = async ():Promise => { +const data = await AxiosRequest('get', '.../destinations'); + const destinationNames = DestinationName[] = []; + + data?.forEach((str) => { + if (isDestinationCode(str)) { + destinationNames.push(DestinationNameSet[str]); + } + }); + + return destinationNames; +} +``` + +> isDestinationCode(str)의 반환 값에 is를 사용하지 않고 boolean이라고 하면 에러가 발생한다. 위의 if 문 내 isDestinationCodeList의 문자열 원소 인지 체크하고 맞으면 destinationNames에 push한다. + +### isDestinationCode의 반환 값 타이핑을 x is DestinationCode가 아닌
boolean으로 했다면 타입스크립트는 어떻게 추론할까? + +개발자는 if문 내부에서 str타입이 DestinationCode라는 걸 알 수 있는데 Array.inclueds를 해석할 수 있기 때문이다. + +하지만, 타입스크립트는 isDestinationCode 함수 내부에 있는 includes 함수를 해석해 타입 추론을 할 수 없다. + +> 타입스크립트는 if문 스코프의 str타입을 좁히지 못하고 string으로만 추론한다. destinationNames의 타입은 DestinationName[]이기 때문에 string타입의 str을 push할 수 없다는 에러가 발생한다. + +### 타입스크립트에게 반환 값에 대한 타입 정보를 알려주고 싶을 때 is를 사용할 수 있다! + +반환 값 타입을 x is DestinationCode로 알려줌으로써 타입스크립트는 if문 스코프의 str 타입을 DestinationCode로 추론할 수 있게 된다. + +
+
diff --git "a/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.3_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\354\213\235\353\263\204\355\225\240_\354\210\230_\354\236\210\353\212\224_\354\234\240\353\213\210\354\230\250/seulgi.md" "b/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.3_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\354\213\235\353\263\204\355\225\240_\354\210\230_\354\236\210\353\212\224_\354\234\240\353\213\210\354\230\250/seulgi.md" index 3beced8..9ec90af 100644 --- "a/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.3_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\354\213\235\353\263\204\355\225\240_\354\210\230_\354\236\210\353\212\224_\354\234\240\353\213\210\354\230\250/seulgi.md" +++ "b/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.3_\355\203\200\354\236\205_\354\242\201\355\236\210\352\270\260_\354\213\235\353\263\204\355\225\240_\354\210\230_\354\236\210\353\212\224_\354\234\240\353\213\210\354\230\250/seulgi.md" @@ -1 +1,195 @@ - +# 타입 좁히기 - 식별할 수 있는 유니온(Discriminated Unions) + +태그된 유니온(tagged union) 혹은 식별할 수 있는 유니온 타입(Discriminated Unions)은 타입 좁히기에 널리 사용되는 방식이다. + +## 1) 에러 정의하기 + +유효성 에러가 발생하면 사용자에게 다양한 방식으로 에러를 보여주는 예시로 살펴보자. + +우아한 형제들에서는 "텍스트 에러", "토스트 에러", "얼럿 에러"로 분류한다. 이들 모두 유효성 에러로 에러 코드와 메시지를 가지고 있고 에러 노출 방식에 따라 추가로 필요한 정보가 있을 수 있다고 가정하자. + +```ts +type TextError = { + code: string; + message: string; +}; + +type ToastError = { + code: string; + message: string; + toastShowDuration: number; +}; + +type AlertError = { + code: string; + message: string; + onConfirm: () => void; +}; + +type ErrorType = TextError | ToastError | AlertError; + +// 에러 타입의 유니온 타입을 원소로 하는 배열로 다양한 에러 객체를 관리 가능 +const errorArr: ErrorType[] = [ + { code: "100", message: "텍스트 에러" }, + { code: "200", message: "토스트 에러", toastShowDuration: 3000 }, + { code: "300", message: "얼럿 에러", onConfirm: () => {} }, +]; +``` + +### 위의 배열에 에러 타입별로 정의한 필드를 가지는 에러 객체가 포함되길 원한다면 어떨까? + +ToastError의 toastShowDuration 필드와 AlertError의 onConfirm 필드를 모두 가지는 객체에 대해서 에러를 받아야 한다. + +```ts +const errorArr: ErrorType[] = [ + { code: "100", message: "텍스트 에러" }, + { code: "200", message: "토스트 에러", toastShowDuration: 3000 }, + { code: "300", message: "얼럿 에러", onConfirm: () => {} }, + { + code: "300", + message: "잘못된 에러", + toastShowDuration: 3000, + onConfirm: () => {}, + }, +]; +``` + +> 해당 코드 작성 시 자바스크립트는 덕타이핑 언어이기 때문에 별도의 타입 에러를 뱉지 않는다. + +## 2) 식별할 수 있는 유니온 + +위의 예시처럼 타입 에러가 발생하지 않을 경우 개발 과정에서 무수한 에러 객체가 생겨날 위험성이 커진다. + +이럴 때 에러 타입을 구분할 방법이 필요한데 **각 타입이 비슷한 구조를 가지지만 +
서로 호환되지 않도록 만들어줘야 한다.** + +> "식별할 수 있는 유니온을 활용하는 것"이 그 방법! + +### 식별할 수 있는 유니온이란? + +타입 간의 구조 호환을 막기 위해 타입마다 구분할 수 있는 판별자(= 태그)를 달아주어 포함 관계를 제거하는 것이다. + +### 판별자의 개념으로 errorType 필드를 새로 정의해보자! + +```ts +type TextError = { + type: "TEXT"; + code: string; + message: string; +}; + +type ToastError = { + type: "TOAST"; + code: string; + message: string; + toastShowDuration: number; +}; + +type AlertError = { + type: "ALERT"; + code: string; + message: string; + onConfirm: () => void; +}; // Object literal may only specify known properties, and 'toastShowDuration' does not exist in type 'AlertError'. +``` + +- 각 필드마다 다른 값을 가지도록 해 판별자를 달아준다. +- 이 판별자에 의해 포함 관계를 벗어나게 된다. + +> 정확하지 않은 에러 객체에 대해 타입 에러가 발생하는 것을 확인할 수 있다. + +
+
+ +## 3) 식별할 수 있는 유니온의 판별자 선정 + +식별할 수 있는 유니온을 사용할 때 주의할 점이 있다. + +식별할 수 있는 유니온의 판별자는 유닛 타입(unit type)으로 선언되어야 정상적으로 동작한다. + +### 유닛 타입이란? + +다른 타입으로 쪼개지지 않고 오직 하나의 정확한 값을 가지는 타입이다. + +- null, undefined, 리터럴 타입을 비롯해 true, 1 등 정확한 값을 나타내는 타입이다. +- 반면, 다양한 타입을 할당할 수 있는 void, string, number와 같은 타입은 유닛 타입이 아니다. + +> **식별할 수 있는 유니온의 판별자로 사용할 수 있는 타입은?**
(1) 리터럴 타입이어야 한다,
(2) 판별자로 선정한 값에 적어도 하나 이상의 유닛 타입이 포함되어야 하며, 인스턴스화할 수 있는 타입은 포함되지 않아야 한다. + +### 인스턴스화할 수 있는 타입은 뭘까? + +TypeScript에서 **“인스턴스화할 수 있는 타입”** 이란 new 키워드를 사용하여 객체(인스턴스)를 생성할 수 있는 타입을 의미한다. + +### 인스턴스화할 수 있는 타입 목록 + +| 타입 종류 | 인스턴스화 가능 여부 | 예시 | +| ---------------------------- | -------------------- | ---------------------------------------- | +| **클래스** | ✅ 가능 | `class Person {}` | +| **생성자 함수** | ✅ 가능 | `function Animal() {}` | +| **생성자 인터페이스** | ✅ 가능 | `interface A { new(): B }` | +| **함수 타입 (`new()` 포함)** | ✅ 가능 | `type A = new (...args) => B` | +| **내장 객체** | ✅ 가능 | `Date, RegExp, Map, Set, Promise, Error` | +| **원시 타입** | ❌ 불가능 | `string, number, boolean, symbol` | +| **일반 인터페이스** | ❌ 불가능 | `interface A { name: string; }` | +| **객체 리터럴 타입** | ❌ 불가능 | `type A = { name: string }` | +| **유니온 타입** | ❌ 불가능 | `type A = string \| number` | +| **추상 클래스** | ❌ 불가능 | `abstract class A {}` | + +> “인스턴스화할 수 있는 타입”이란 new 키워드를 사용하여 객체를 생성할 수 있는 타입을 의미한다. 클래스, 생성자 함수, new() 시그니처가 있는 인터페이스, 생성자 함수 타입, 일부 내장 객체가 이에 해당한다. + +
+
+ +### 판별자의 타입에 따라 타입이 달라지는 예시를 알아보자. + +```ts +interface A { + value: "a"; // unit type + answer: 1; +} + +interface B { + value: string; // not unit type + answer: 2; +} + +interface C { + value: Error; // instantiable type + answer: 3; +} + +type Unions = A | B | C; + +function handle(param: Unions) { + param.answer; // 1 | 2 | 3 + + // "a"가 리터럴 타입으로 좁혀지나 string 타입에 포함도 되기 때문에 param은 A | B로 좁혀진다. + if (param.value === "a") { + param.answer; // 1 | 2 return; + } + + // 유닛 타입이 아니거나 인스턴스화할 수 있는 타입일 경우 타입이 좁혀지지 않는다. + if (typeof param.value === "string") { + param.answer; // 1 | 2 | 3 return; + } + + if (param.value instanceof Error) { + param.answer; // 1 | 2 | 3 return; + } + + // 판별자가 answer일 때 + param.value; // string | Error + + // 판별자가 유닛 타입이므로 타입이 좁혀진다. + if (param.answer === 1) { + param.value; // "a" + } +} +``` + +- 해당 코드에서는 판별자가 value일 떄 판별자로 선정한 값 중 "a"만 유일하게 유닛 타입이다. +- **이때만 유닛 타입을 포함하고 있어 타입이 좁혀진다!** +- 판별자가 answer일 때를 보면 판별자가 모두 유닛 타입이라 타입이 정상적으로 좁혀진다. + +
+
diff --git "a/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.4_Exhaustiveness_Checking\354\234\274\353\241\234_\354\240\225\355\231\225\355\225\234_\355\203\200\354\236\205_\353\266\204\352\270\260_\354\234\240\354\247\200\355\225\230\352\270\260/seulgi.md" "b/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.4_Exhaustiveness_Checking\354\234\274\353\241\234_\354\240\225\355\231\225\355\225\234_\355\203\200\354\236\205_\353\266\204\352\270\260_\354\234\240\354\247\200\355\225\230\352\270\260/seulgi.md" index 3beced8..6f4e43e 100644 --- "a/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.4_Exhaustiveness_Checking\354\234\274\353\241\234_\354\240\225\355\231\225\355\225\234_\355\203\200\354\236\205_\353\266\204\352\270\260_\354\234\240\354\247\200\355\225\230\352\270\260/seulgi.md" +++ "b/CH04_\355\203\200\354\236\205_\355\231\225\354\236\245\355\225\230\352\270\260_\354\242\201\355\236\210\352\270\260/4.4_Exhaustiveness_Checking\354\234\274\353\241\234_\354\240\225\355\231\225\355\225\234_\355\203\200\354\236\205_\353\266\204\352\270\260_\354\234\240\354\247\200\355\225\230\352\270\260/seulgi.md" @@ -1 +1,95 @@ - +# Exhaustiveness Checking으로 정확한 타입 분기 유지하기 + +### Exhaustiveness? + +사전적으로 철저함, 완전함을 의미한다. +
Exhaustiveness Checking은 모든 케이스에 대해 철저하게 타입을 검사하는 것이다. +
타입 좁히기에 사용되는 패러다임 중 하나이다. + +때때로 모든 케이스에 대해 분기 처리를 해야 유지보수 측면에서 안전하다하는 상황에서 +
Exhaustiveness Checking을 통해 모든 케이스에 대한 타입 검사를 강제할 수 있다. + +## 1) 상품권 + +선물하기 서비스에 있는 상품권 가격에 따라 상품권 이름을 반환해주는 함수가 있다. + +```ts +type ProductPrice = "10000" | "20000"; + +const getProductName = (productPrice: ProductPrice): string => { + if (productPrice === "10000") { + return "1만원"; + } + + if (productPrice === "20000") { + return "2만원"; + } else { + return "기타"; + } +}; +``` + +### 만약 상품권이 늘어나면 어떨까? + +```ts +type ProductPrice = "10000" | "20000" | "5000"; + +const getProductName = (productPrice: ProductPrice): string => { + if (productPrice === "10000") { + return "1만원"; + } + + if (productPrice === "20000") { + return "2만원"; + } + + if (productPrice === "5000") { + return "5천원"; // 조건 추가 + } else { + return "기타"; + } +}; +``` + +- ProductPrice 타입이 업데이트되었을 때 getProductName 함수도 업데이트 되어야 한다. + +### 업데이트된 타입에 맞게 함수를 수정하지 않아도 별도 에러가 발생하지 않을 수 있다!? + +이럴 때 필요한 것이 모든 타입에 대한 타입 검사를 강제해야 하는 것이다. + +```ts +type ProductPrice = "10000" | "20000" | "5000"; + +const getProductName = (productPrice: ProductPrice): string => { + if (productPrice === "10000") { + return "1만원"; + } + + if (productPrice === "20000") { + return "2만원"; + } else { + exhaustiveCheck(productPrice); // Error! + return "기타"; + } +}; + +const exhaustiveCheck = (param: never) => { + throw new Error("type error!"); +}; +``` + +- exhaustiveCheck(productPrice)부분에 에러가 나는데 5000이라는 값에 대한 분기 처리를 하지 않아 발생한 것이다. + +> 이렇게 모든 케이스에 대한 타입 분기 처리를 해주지 않았을 때, 컴파일 타임 에러가 발생하게 하는 것을 Exhaustiveness Checking이라 한다. + +### exhaustiveCheck 함수를 자세히 알아보자. + +exhaustiveCheck 함수 매개변수를 never로 선언한 것은 매개변수로 그 어떤 값도 받을 수 없다는 것이다. + +만일 값이 들어오면 에러를 내뱉고, 이 함수를 타입 처리 조건문의 마지막 else문에 사용하면 +
앞의 조건문에서 모든 타입에 대한 분기 처리를 강제 할 수 있다. + +> Exhaustiveness Checking을 활용하면 런타임 에러 방지 + 요구사항 변경 시 위험성을 줄일 수 있다. + +
+