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/seongho.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/seongho.md" index 3beced8..f7a0c5c 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/seongho.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/seongho.md" @@ -1 +1,235 @@ - +### 타입 가드에 따라 분기 처리하기 +--- +ts에서 타입 `좁히기(타입 가드)`는 변수 또는 표현식의 타입 범위를 더 작은 범위로 좁혀나가는 과정임. + +이로 인해서 타입의 안정성을 높일 수 있음. + +ts로 개발을 하다보면 여러 타입을 할당할 수 있는 `스코프`에서 특정 타입을 조건으로 만들어서 분기 처리하고 싶을 경우가 있음. + +> [!TIP] +> **스코프(Scope)**
+> 변수가 함수등의 식별자가 유효한 범위를 나타냄.
+> 즉, 변수와 함수를 선언하거나 사용할 수 있는 영역을 말함. + +예를 들어서 어떤 함수가 `A | B`타입의 매개변수를 받고, 이를 구분해서 처리하고 싶다면 어떻게 해야 할까?
+`if문`을 사용해서 처리하면 될 것 같지만 `ts`는 컴파일 시 타입 정보가 모두 제거되기 때문에 타입을 사용해서 조건을 만들 수는 없음. 즉, 컴파일을 해도 사라지지 않는 방법을 사용해야 함. + +***그렇게 하려면 ts가 해당 변수를 타입 A로 추론되게끔 하면서, 런타임에서도 유효한 방법이 필요한데, 이때 `타입 가드`를 사용하면 됨.*** + +타입 가드는 크게 두가지로 분류할 수 있음. +- 타입 가드 + - `js 연산자를 활용한 타입 가드` + - `사용자 정의 타입 가드` + +`js연산자를 활용한 타입 가드`
+: `typeof`, `instanceof`, `in`과 같은 연산자를 사용해서 특정 타입 값을 가질 수밖에 없는 상황을 유도하여 자연스럽게 타입을 좁히는 방식.
+ + + +`사용자 정의 타입 가드`
+: 사용자가 직접 어떤 타입으로 좁힐지 지정하는 방식. + +
+ +### 원시타입을 추론할 때: `typeof` 연산자 활용하기 +--- +`typeof`연산자를 활용하면 원시 타입에 대해 추론이 가능함. 다만, typeof는 js타입 시스템만 대응이 가능함.
+또한 `null`과 `배열`등의 타입이 `object`로 판별되는 등, 복잡한 타입을 검증하기에는 한계가 있음.
+***따라서 `typeof`는 주로 하위에 명시된 원시 타입을 좁히는 용도로만 사용할 것을 권장.*** +- `string` +- `number` +- `boolean` +- `undefined` +- `object` +- `function` +- `bigint` +- `symbol` + +```ts +const Foo:(date: string | Date): string | Date = (date) => { + if (typeof date === 'string') { + date.toUpperCase(); // 여기서 date는 string타입으로 추론. + } + + return date; +}; +``` + +
+ +### 인스턴스화 된 객체 타입을 판별할 경우: `instanceof` 연산자 활용하기 +--- +```ts +interface DateRange { + start: Date; + end: Date; +} + +interface Params { + selectedDate?: Date | DateRange; +} + +const DatePicker = ({selectedDate}: Params) { + const [selected, setSelected] = useState(convertToRange(selectedDate)); +}; + +const convertToRange: (selected: DateRange | Date) => DateRange = (selected) => { + if (selected instanceof Date) { + return { start: selected, end: selected }; + } + + return selected; +}; +``` +`typeof` 연산자를 주로 원시타입을 판별하는데, 사용한다면 `instanceof` 연산자는 인스턴스 화 된 객체 타입을 판별하는 타입 가드로 사용할 수 있음.
+다만 주의해야 할 점은 프로토타입 속성의 변화에 따라서 instanceof 연산자의 결과가 달라질 수 있다는 점임. +> [!NOTE] +> **instanceof**
+> `object instanceof constructor` 형태로 작성하고
+> object의 프로토타입 체인에 constructor.prototype이 존재하는지 판별함 + +
+ +### 객체의 속성이 있는지 없는지에 따른 구분: `in` 연산자 활용하기 +`in`연산자는 객체에 속성이 있는지 확인한 다음에 `true` 또는 `false`를 반환함. + +만약, 내가 `Foo` 또는 `Bar`타입을 인자로 받고, 각각의 타입에 따라서 popup을 다르게 띄워주고 싶다고 해보자.
+그러면 아래와 같이 `in`을 활용해서 작성할 수 있음. + +```ts +interface Foo { + name: string; + age: number; +} + +interface Bar extends Foo { + hasJob: boolean; +} + +const showPopup = (contents: Foo | Bar) => { + if ('hasJob' in contents) { + return openBarPopup(contents); + // hasJob은 Bar타입에만 존재하기 때문에 여기서는 contents가 Bar타입으로 추론 됨. + } + + // 위에서 early return을 했기 때문에, 해당 구문에서는 contents가 Foo타입으로 추론 됨. + return openFooPopup(contents); +} +``` +***js에서 in은 런타임의 값만을 검사하지만, ts에서 in은 객체 타입에 속성이 존재하는지 검사함.*** + +이처럼 여러 객체 타입을 유니온 타입으로 갖고있을 경우, `in` 연산자를 활용해서 타입 가드를 할 수 있음. + +
+ +### is 연산자로 사용자 정의 타입 가드 만들어 활용하기 +--- +직접 `타입 가드 함수`를 만들어서 사용할 수도 있음. 이러한 방식은 반환 타입이 `타입 명제`인 함수를 정의해야 함.
+ +> [!TIP] +> **타입 명제**란?
+> `A is B` 방식으로 작성하는데, 여기서 `A`는 매개변수 이름이고, `B`는 타입임.
+> 해당 함수는 `boolean` 타입을 반환해야 하는데, `true`를 return 할 때 **A의 타입을 B로 취급**함. +> ```ts +> interface Foo { +> name: string; +> age: number; +>} +> +>interface Bar extends Foo { +> hasJob: boolean; +>} +> +>const isBar = (params: Foo | Bar): params is Bar => { +> return 'hasJob' in params; +>} +> +>const showPopup = (contents: Foo | Bar) => { +> if (isBar(contents)) { +> return openBarPopup(contents); // isBar타입가드에 의해서 contents가 Bar타입으로 추론 됨. +> } +> +> // 위에서 early return을 했기 때문에, 해당 구문에서는 contents가 Foo타입으로 추론 됨. +> return openFooPopup(contents); +>} +> ``` + +노티 박스보다 조금 더 심화된 예시를 봐보자 +```ts +const isDestinationCode(x: string): x is DestinationCode => { + return destinationCodeList.includes(x); +} + +const getAvailableDestinationNameList = async (): Priomise => { + const data = await AxiosRequest('get', '.../destinations'); + const destinationNames = DestinationName[] = []; + + data?.forEach((str) => { + if (isDestinationCode(str)) { + + destinationNames.push(DestinationNameSet[str]); + } + }); + + return destinationNames; +} +``` +해당 코드는 if문 내 `isDestination`함수로 `data`의 str이 `destinationCodeList`의 문자열 원소인지 체크하고, 맞다면 `destinationNames`배열에 push를 하는 코드임.
+만약, `isDestinationCode`의 return 타입인 `x is DestinationCode`를 boolean으로 작성했으면 어땠을까? + +개발자라면 `destinationCodeList.includes(x);`를 해석할 수 있기 때문에 이해할 수 있지만, ts는 str의 타입을 좁히지 못함.
+이처럼 ts에게 반환값에 대한 타입 정보를 알려주고 싶을 경우 `is`를 사용할 수 있음. + +
+ +### 나의 실무 예시 +--- +끝으로, 내가 실무에서 실제로 작성한 `커스텀 타입 가드`를 예시로 봐보자 + +`Promise.allSettled`는 여러개의 promise를 배열로 받아서 비동기적으로 실행해주는 메서드임.
+Promise.all도 동일하게 여러개의 promise를 담은 배열을 인자로 받아서 비동기적으로 실행시켜 주지만 +`Promise.all`은 여러개의 promise중 하나의 promise라도 성공적으로 완료되지 못하면 동시에 실행된 모든 promise가 reject되는 반면,
+`Promise.allSettled`는 동시에 실행된 promise들 중에서 성공적으로 완료되지 못한 promise가 발생해도 전부 reject되지 않고, 성공적으로 완료된 promise들을 확인할 수 있음. + +그렇기에 `Promise.allSetted`가 return하는 타입인 `Promise>`를 살펴보면
+수행한 **promise가 성공적으로 마무리 됬을경우** 할당되는 `PromiseFulfilledResult`와,
+**promise가 성공적으로 마무리 되지 못했을 경우** 할당되는 `PromiseRejectedResult`를 `유니언 타입`으로 return하고 있는 것을 볼 수 있음. + + + +
+ +그렇기에 나는 아래와 같은 `커스텀 타입 가드`를 작성해서 유틸 함수로 사용하고 있음. +```ts +const isFulfilled = ( + targetResult: PromiseSettleResult +): targetResult is PromiseFulfilledResult => + targetResult.status === 'fulfilled'; +``` +해당 `커스텀 타입 가드`는 `PromiseSettledResult`의 타입을 가진 `targetResult`라는 파라미터를 받아서 +만약, 해당 파라미터의 status가 `fulfilled`라면 +targetResult의 타입을 `PromiseFulfilledResult`로 취급하도록 하는 `커스텀 타입 가드` + + +해당 타입가드를 어떻게 활용하고 있을까? + + +`Promise.allSettled`를 통해 여러개의 API를 묶어서 병렬적으로 처리한 뒤,
+각각의 응답을 `isFulfilled 커스텀 타입 가드`를 통해 status가 fulfilled인지 체크하여, 만약 status가 fulfilled라면 성공한 프로미스 타입으로 취급시켜 주는 것. + +```ts +const [ + dataCategoryResult, // 여기서 dataCategoryResult는 PormiseSettledResult가 됨 + // ... +] = Promise.allSettled([ + getCagetory(params.categoryId), + // ... +]) + +const dataCategory = isFulfilled(dataCategoryResult) + ? dataCategoryResult.value // 여기서 dataCategoryResult는 타입 가드를 통해서 PormiseFulfilledResult가 됨 + : undefined; +``` + + +이렇게 활용하고 있음. 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/seongho.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/seongho.md" index 3beced8..183321c 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/seongho.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/seongho.md" @@ -1 +1,162 @@ - +종종 `태그된 유니온`으로도 불리는 `식별할 수 있는 유니온`은 타입 가드에 널리 사용되는 방식임.
+예시와 함께 살펴보자 + +
+ +### 에러 정의하기 +--- +배민 서비스에서는 선물을 보낼때 사용자가 필요한 값을 올바르게 입력했는지를 확인하는 유효성 검사를 진행함.
+이때 다양한 방식으로 에러를 보여주는데, 크게 3가지로 분류함. +- `텍스트 에러` +- `토스트 에러` +- `얼럿 에러` + +이들을 모두 동일하게, `errorCode`와 `errorMessage`를 가지고 있지만, 각각 추가로 필요한 정보가 있을 수 있음. 예를 들자면 토스트 에러는 토스트를 얼마나 오래 띄울것인지에 대한 정보가 필요함. +```ts +type TextError = { + errorCode: string; + errorMessage: string; +}; + +type ToastError = { + errorCode: string; + errorMessage: string; + toastShowDuration: number; // 토스트 유지 시간 +}; + +type AlertError = { + errorCode: string; + errorMessage: string; + onConfirm: () => void; // 확인 버튼을 누른 뒤 action +}; +``` + +이 에러 타입의 유니온 타입을 원소로 하는 배열을 정의하면 아래와 같음 +```ts +type ErrorFeedbackType = TextError | ToastError | AlertError; + +const errorArr: ErrorFeedbackType[] = [ + { errorCode: '100', errorMessage: '텍스트 에러' }, + { errorCode: '200', errorMessage: '토스트 에러', toastShowDuration: 300, }, + { errorCode: '300', errorMessage: '얼럿 에러', onConfirm: () => {}, }, +]; +``` + +
+ +여기서 만약 `TextError`, `ToastError`, `AlertError`의 모든 속성을 가지는 에러가 들어온다고 가정해보면 +```ts +const unionError = { + errorCode: '999', + errorMessage: '잘못된 에러', + toastShowDuration: 300, + onConfirm: () => {}, +}; + +errorArr.push(unionError); // ✅ OK +``` +이거는 우리가 기대했던 상황이 아니므로 에러를 뱉어야 하는게 맞지만, 덕타이핑 언어의 특성을 가지는 js이기 때문에 별도의 타입 에러가 발생하지 않음.
+따라서 이러한 상황에서 에러가 발생하지 않으면 앞으로의 개발 과정에서 알수없는 에러가 많이많이 생기게 될게 뻔함. + +
+ +### 식별할 수 있는 유니온 +--- +따라서 에러 타입을 구분할 방법이 필요한데, 각 타입이 비슷한 구조를 가지지만 서로 호환되지 않도록 하기 위해서는 타입들이 서로 포함 관계를 가지지 않도록 정의해야 함.
+***즉, 차별점이 될만한 것들을 정의해두어야 한다는 것.***
+이때 적용할 수 있는 방법이 바로 `식별할 수 있는 유니온`을 활용하는 것임.
+`식별할 수 있는 유니온`이란 ***타입간의 구조 호환을 막기 위해 타입마다 구분할 수 있는 유니크한 식별자(판별자)를 달아줘서 포함 관계를 제거하는 것*** 임. + +위에서 보았던 예시를 식별자의 개념으로 `errorType`이라는 필드를 추가해서 다시 작성 해본다면 +```ts +type TextError = { + errorType: 'TEXT'; // 식별자 + errorCode: string; + errorMessage: string; +}; + +type ToastError = { + errorType: 'TOAST'; // 식별자 + errorCode: string; + errorMessage: string; + toastShowDuration: number; +}; + +type AlertError = { + errorType: 'ALERT'; // 식별자 + errorCode: string; + errorMessage: string; + onConfirm: () => void; +}; +``` +이렇게 작성해 볼 수 있음.
+`errorType`으로 각각 고유한 식별자를 넣어줌으로써 포함관계를 제거했음. +그렇다면 아까는 에러를 뱉지 않았던 예시를 다시 적용해본다면? + + +우리가 기대했던 대로 동작함. +유니크한 식별자를 통해서 ts가 타입 추론을 통해서 타입을 좁힌 것. + +
+ +### 식별할 수 있는 유니온 판별자 선정 +--- +식별할 수 있는 유니온을 사용할 때 주의할 점이 있음.
+식별할 수 있는 유니온의 판별자(식별자)는 `유닛 타입`으로 선언되어야 한다는 것. +> [!TIP] +> **유닛 타입**
+> 다른 타입으로 쪼개지지 않고 오직 하나의 정확한 값을 가지는 타입을 말함.
+> ex) `null`, `undefined`, `true`, `1`, `리터럴 타입` 등등.. + +여러 타입 할당이 가능한 `void`, `string`, `number`, `object`이런 타입들은 유닛 타입으로 분류되지 않음. + +실제 ts 깃헙의 이슈를 찾아보면 식별자로 사용 가능한 타입을 다음과 같이 정의하고 있음 +- ***"리터럴 타입이어야 한다."*** +- ***판별자(식별자)로 선정한 값에 적어도 하나 이상의 유닛 타입이 포함되어야 하며, 인스턴스화할 수 있는 타입은 포함되지 않아야 한다.*** + +```ts +interface A { + value: 'a'; + answer: 1; +} + +interface B { + value: string; + answer: 2; +} + +interface C { + value: Error; + answer: 3; +} + +function checkType(param: A | B | C) { + // 식별자가 param.value일 경우 + if (param.value === 'a') { + param // A | B 타입으로 좁혀짐 + } + + if (typeof param.value === 'string') { + param // A | B 타입으로 좁혀짐 + } + + if (param.value instanceof Error) { + // 인스턴스화 할 수 있는 타입일 경우 타입이 좁혀지지 않음. + param // A | B | C + } + + // 식별자가 param.answer일 경우 + if (param.answer === 1) { + param // A + } + + if (param.answer === 2) { + param // B + } + + if (param.answer === 3) { + param // C + } +} +``` +이 코드에서는 판별자가 `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/seongho.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/seongho.md" index 3beced8..c130791 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/seongho.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/seongho.md" @@ -1 +1,70 @@ - +`Exhaustiveness`는 사전적으로 철저함, 완전함을 의미함.
+따라서 `Exhaustiveness Checking`은 모든 케이스에 대해 철저하게 타입을 검사하는 것을 말함.
+타입 가드를 통해서 타입에 대한 분기 처리를 수행한다면 분기 처리가 필요하다고 생각되는 부분에만 분기처리를 해준다면 요구사항을 만족할 수 있음. 그러나 때로는 모든 케이스에 대해서 분기처리를 해야만 하는 상황이 있을 수 있음.
+ +예를 들어서 살펴보자 + +
+ +### 상품권 +--- +배민 서비스에는 다양한 상품권들이 존재함. 상품권의 가격에 따라 상품권의 이름을 반환해주는 함수를 작성하면 다음과 같음. +```ts +type ProductPrice = 10000 | 20000; + +const getProductName = (productPrice: ProductPrice) => { + if (productPrice === 10000) { + return '배민상품권 1만원'; + } + + if (productPrice === 20000) { + return '배민상품권 2만원'; + } + + else { + return '배민상품권'; + } +}; +``` +해당 코드는 가격에 따라서 이름을 잘 반환하고 있지만, 만약 `ProductPrice`에 `5000`이 새로 추가된다면?
+`getProductName`의 코드에도 새로운 분기처리 코드가 추가가 되어야 할것임. +수정을 하지 않아도 에러가 발생 하는것은 아니기 때문에 실수 할 여지도 있음. + +
+ +이러한 실수를 방지하기 위해서 모든 타입에 대해서 타입 검사를 강제하고 싶다면 아래와 같이 코드를 작성할 수 있음. + +```ts +type ProductPrice = 5000 | 10000 | 20000; + +const getProductName = (productPrice: ProductPrice) => { + if (productPrice === 10000) { + return '배민상품권 1만원'; + } + + if (productPrice === 20000) { + return '배민상품권 2만원'; + } + + else { + exhaustiveTypeCheck(productPrice); + } +}; + +const exhaustiveTypeCheck = (param: never): never => { + throw new Error('처리되지 않은 타입이 존재합니다!!'); +} +``` + + + +-> 이미지를 보면 타입 분기 처리를 빼먹었을 경우 의도적으로 에러를 발생시킴. + +`exhaustiveTypeCheck`의 인자 타입을 `never`로 선언해서 어떤 값도 받지 못하게 한 뒤, 만약 특정 값이 들어오면 에러를 뱉게끔 하는 것임. + + +이런식으로 모든 타입에 대해서 분기 처리를 하지 않았을 경우 의도적으로 에러를 던져서 컴파일 에러를 유도하는 방식을 `Exhaustiveness Checking`이라고 함. + + + + diff --git a/README.md b/README.md index a0c219f..f924f06 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ | ------ | --------------------------------------------------------------------------------------- | ---------- | --------- | | 1주차 | 1.1 웹 개발의 역사 ~ 2.4 객체 타입 | 2025.03.11 | ✅ | | 2주차 | 3.1 타입스크립트만의 독자적 타입 시스템 ~ 3.2 타입 조합 | 2025.03.18 | ✅ | -| 3주차 | 3.3 제네릭 사용법 ~ 4.1 타입 확장하기 | YYYY.MM.DD | | -| 4주차 | 4.2 타입 좁히기 - 타입 가드 ~ 4.4 Exhaustiveness Checking으로 정확한 타입 분기 유지하기 | YYYY.MM.DD | | +| 3주차 | 3.3 제네릭 사용법 ~ 4.1 타입 확장하기 | 2025.03.25 | ✅ | +| 4주차 | 4.2 타입 좁히기 - 타입 가드 ~ 4.4 Exhaustiveness Checking으로 정확한 타입 분기 유지하기 | 2025.04.01 | ✅ | | 5주차 | 5장 타입 활용하기 | YYYY.MM.DD | | | 6주차 | 6.1 자바스크립트의 런타임과 타입스크립트의 컴파일 ~ 7.1 API 요청 | YYYY.MM.DD | | | 7주차 | 7.2 API 상태 관리하기 ~ 7.4 API 모킹 | YYYY.MM.DD | | diff --git a/assets/CH04/discriminant_union_example.png b/assets/CH04/discriminant_union_example.png new file mode 100644 index 0000000..156523d Binary files /dev/null and b/assets/CH04/discriminant_union_example.png differ diff --git a/assets/CH04/exhaustiveness_type_check.png b/assets/CH04/exhaustiveness_type_check.png new file mode 100644 index 0000000..56865ac Binary files /dev/null and b/assets/CH04/exhaustiveness_type_check.png differ diff --git a/assets/CH04/fulfilled_promise_image.png b/assets/CH04/fulfilled_promise_image.png new file mode 100644 index 0000000..e9bb8f4 Binary files /dev/null and b/assets/CH04/fulfilled_promise_image.png differ diff --git a/assets/CH04/promiseSettledResult.png b/assets/CH04/promiseSettledResult.png new file mode 100644 index 0000000..b921363 Binary files /dev/null and b/assets/CH04/promiseSettledResult.png differ diff --git a/assets/CH04/promise_allSettled.png b/assets/CH04/promise_allSettled.png new file mode 100644 index 0000000..4dcaeec Binary files /dev/null and b/assets/CH04/promise_allSettled.png differ