diff --git "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.1_\354\241\260\352\261\264\353\266\200_\355\203\200\354\236\205/seulgi.md" "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.1_\354\241\260\352\261\264\353\266\200_\355\203\200\354\236\205/seulgi.md" index 3beced8..08153ca 100644 --- "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.1_\354\241\260\352\261\264\353\266\200_\355\203\200\354\236\205/seulgi.md" +++ "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.1_\354\241\260\352\261\264\353\266\200_\355\203\200\354\236\205/seulgi.md" @@ -1 +1,173 @@ - +# 1. 조건부 타입 + +프로그래밍에서는 다양한 상황을 다루기 위해 조건문을 많이 활용한다. 타입도 마찬가지로 조건에 따라 다른 타입을 반환해야 할 때가 있다. + +### 타입스크립트에서는 조건부 타입을 사용해 조건에 따라 출력 타입을 다르게 도출할 수 있다! + +```ts +Condition ? A : B; +``` + +- A는 Condition이 true일 때 도출되는 타입 +- B는 false일 때 도출되는 타입 + +> 조건부 타입을 활용하면 중복 타입 코드를 제거할 수 있고, 상황에 따라 적절한 타입을 얻을 수 있어 더 정확한 타입 추론이 가능하다. + +
+
+ +## 1) extends와 제네릭을 활용한 조건부 타입 + +`extends` 키워드는 타입스크립트에서 다양한 상황에서 활용된다. + +- 타입을 확장할 때 +- 타입을 조건부로 설정할 때 +- 제네릭 타입에서는 한정자 역할로 사용 + +### 조건부 타입에서 extends를 사용할 때 + +```ts +T extends U ? X : Y +``` + +- 자바스크립트 삼항 연산자를 extends와 함께 쓴다. +- 타입 T를 U에 할당할 수 있으면 X타입 +- 할당할 수 없으면 Y타입으로 결정 + +### 결제 수단과 관련된 타입 예시를 살펴보자. + +```ts +interface Bank { + code: string; + companyName: string; + name: string; + fullName: string; +} + +interface Card { + code: string; + companyName: string; + name: string; + addCardType?: string; +} + +type PayMethod = T extends "card" ? Card : Bank; +type CardPayMethodType = PayMethod<"card">; +type BankPayMethodType = PayMethod<"bank">; +``` + +- PayMethod 타입은 제네릭 타입으로 extends를 사용한 조건부 타입이다. +- 제네릭 매개변수에 "card"가 들어오면 Card타입, 그 외의 값은 Bank 타입으로 결정된다. +- PayMethod를 활용해 CardPayPayMethodType, BankPayMethodType을 도출할 수 있다. + +
+
+ +## 2) 조건부 타입을 사용하지 않았을 때의 문제점 + +조건부 타입을 사용하기 전에 어떤 이슈가 있었는지 react query 활용 예시를 통해 알아보자. +
계좌 / 카드 / 앱 카드 등 3가지 결제 수단 정보를 가져오는 API가 있으며 API의 엔드포인트는 다음과 같다. + +``` +계좌 : www.site.com/.../bank +카드 : www.site.com/.../card +앱 카드 : www.site.com/.../appcard +``` + +3가지 API 엔드포인트가 비슷하기에 서버 응답을 처리하는 공통 함수를 생성하고, 해당 함수에 타입을 전달하여 타입별로 처리 로직을 구현할 것이다. + +```ts +interface PayMethodBaseFromRes { + code: string; + name: string; +} + +interface Bank extends PayMethodBaseFromRes { + fullName: string; +} + +interface Card extends PayMethodBaseFromRes { + appCardType?: string; +} + +type PayMethodInfo = T & PayMethodInterface; +type PayMethodInterface = { + companyName: string; + // ... +}; + +type PayMethodType = PayMethodInfo | PayMethodInfo; + +export const useGetRegisterList = ( + type: "card" | "appcard" | "bank" +): UseQueryResult => { + const url = `/codes/${type === "appcard" ? "card" : type}`; + + const fetcher = fetcherFactory({ + onSuccess: (res) => { + const usablePayMethodList = + res?.filter( + (pocket: PayMethodInfo | PayMethodInfo) => + pocket?.useType === "USE" + ) ?? []; + + return usablePocketList; + }, + }); + + const result = useCommonQuery(url, undefined, fetcher); + + return result; +}; +``` + +- useQuery를 사용해 구현한 커스텀 훅 "useGetRegisteredList" 함수는 useQuery 반환 값을 돌려준다. +- useCommonQuery는 useQuery를 한 번 래핑해서 사용하고 있는 함수로 useQuery의 반환 data를 T타입으로 반환한다. +- fetcherFactory는 axios를 래핑하는 함수로 서버에서 데이터를 받아온 후 onSuccess 콜백 함수를 거친 결괏값을 반환한다. + +> useGetRegisteredList 함수는 타입으로 "card", "appcard", "bank"를 받아서 해당 결제 수단의 결제 수단 정보 리스트를 반환하는 함수다. + +### 위의 코드는 왜 잘못되었을까? + +원래는 useGetRegisteredList 함수가 반환하는 Data 타입은 PayMethodInfo라고 유추할 수 있다. 하지만 useGetRegisteredList 함수가 반환하는 Data 타입은 PayMethodType이기 때문에 사용하는 쪽에서 PayMethodInfo일 수 있다. + +> useGetRegisteredList 함수는 타입을 구분해서 넣는 사용자 의도와 다르게 정확한 타입을 반환하지 못하는 함수가 되었다.
인자로 넣는 타입에 알맞은 타입을 반환하고 싶지만, 타입 설정이 유니온으로만 되어있기 때문에 타입스크립트는 해당 타입에 맞는 Data 타입을 추론할 수 없다. + +이처럼 인자에 따라 반환되는 타입을 다르게 설정하고 싶다면 extends를 사용한 조건부 타입을 활용하면 된다. + +
+
+ +## 3) extends 조건부 타입을 활용하여 개선하기 + +하나의 함수에서 한 번에 API를 관리해야 하는 상황이면 **조건부 타입을 활용하면 하나의 API 함수에서 타입에 따른 정확한 반환 타입을 추론**하게 만들 수 있다. + +### extends의 활용사례를 정리해보자! + +1. 제네릭과 extends를 함께 사용해 제네릭으로 받는 타입을 제한했다. 따라서 개발자는 잘못된 값을 넘길 수 없기 때문에 휴먼 에러를 방지할 수 있다. +2. extends를 활용해 조건부 타입을 설정했다. 조건부 타입을 사용해서 반환 값을 사용자가 원하는 값으로 구체화할 수 있었다. 이에 따라 불필요한 타입 가드, 타입 단언 등을 방지할 수 있다. + +
+
+ +## 4) infer를 활용해 타입 추론하기 + +- extends를 사용할 때 infer 키워드를 사용할 수 있다. +- infer는 "추론하다"라는 의미를 지니고 있다. +- 타입스크립트에서는 단어 의미처럼 타입을 추론하는 역할을 한다. +- 삼항 연산자를 사용한 조건문의 형태를 가지는데, extends로 조건을 서술하고 infer로 타입을 추론하는 방식을 취한다. + +### 예시로 infer를 알아보자 + +```ts +type UnpackPromise = T extends Promise[] ? K : any; + +const promises = [Promise.resolve("Chu"), Promise.resolve(11)]; + +type Expected = UnpackPromise; // string | number +``` + +- UnpackPromise 타입은 제네릭으로 T를 받아 T가 Promise로 래핑된 경우면 K를 반환하고, 아니면 any를 반환한다. +- Promise는 Promise 반환 값을 추론해 해당 값의 타입을 K로 한다. + +> extends, infer, 제네릭을 활용하면 타입을 조건에 따라 더 세밀하게 사용할 수 있게 된다. diff --git "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.2_\355\205\234\355\224\214\353\246\277_\353\246\254\355\204\260\353\237\264_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.2_\355\205\234\355\224\214\353\246\277_\353\246\254\355\204\260\353\237\264_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" index 3beced8..fd8c950 100644 --- "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.2_\355\205\234\355\224\214\353\246\277_\353\246\254\355\204\260\353\237\264_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" +++ "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.2_\355\205\234\355\224\214\353\246\277_\353\246\254\355\204\260\353\237\264_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" @@ -1 +1,25 @@ - +# 2.템플릿 리터럴 타입 활용하기 + +유니온 타입을 사용해 변수 타입을 특정 문자열로 지정할 수 있다. + +```ts +type HeaderTag = "h1" | "h2" | "h3"; +``` + +- 해당 기능을 사용하면 컴파일타임 변수에 할당되는 타입을 특정 문자열로 정확하게 검사할 수 있다. +- 휴먼 에러를 방지하고, 자동 완성 기능으로 개발 생산성을 높일 수 있다. + +### 템플릿 리터럴 타입? + +타입스크립트의 템플릿 리터럴 타입은 자바스크립트의 템플릿 리터럴 문법을 사용해
+특정 문자열에 대한 타입을 선언할 수 있는 기능이다. + +```ts +type HeadingNumber = 1 | 2 | 3; +type HeaderTag = `h${HeadingNumber}`; +``` + +- 템플릿 리터럴 타입을 사용하면 더 읽기 쉬운 코드로 작성 가능하다. +- 또한, 코드 재사용성과 수정에 용이한 타입을 선언할 수 있다. +- 다만, 컴파일러가 유니온 추론에 시간이 오래 걸리면 비효율적이므로 템플릿 리터럴 타입에 삽입된
+ 유니온 조합의 경우의 수가 너무 많지 않게 적절히 나누어 타입을 정의하는 것이 좋다. diff --git "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.3_\354\273\244\354\212\244\355\205\200_\354\234\240\355\213\270\353\246\254\355\213\260_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.3_\354\273\244\354\212\244\355\205\200_\354\234\240\355\213\270\353\246\254\355\213\260_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" index 3beced8..c1a1c77 100644 --- "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.3_\354\273\244\354\212\244\355\205\200_\354\234\240\355\213\270\353\246\254\355\213\260_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" +++ "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.3_\354\273\244\354\212\244\355\205\200_\354\234\240\355\213\270\353\246\254\355\213\260_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" @@ -1 +1,144 @@ - +# 3.커스텀 유틸리티 타입 활용하기 + +## 1) 유틸리티 함수를 활용해 styled-components의 중복 선언 피하기 + +컴포넌트의 스타일 관련 props는 styled-components에 전달되며, +styled-components에도 해당 타입을 정확하게 작성해줘야 한다. + +styled-component로 만든 컴포넌트에 넘겨주는 타입은 props에서 받은 타입과 동일할 때가 대부분이다. +이럴 경우, **Pick, Omit과 같은 유틸리티 타입을 활용해 코드를 간결히 작성**할 수 있다. + +```tsx +// HrComponent.tsx +type Props = { + height?: string; + color: keyof typeof colors; + isFull: boolean; + className: string; +}; + +const HR: VFC = ({ height, color, isFull, className }) => { + // ... + + return ( + + ); +}; + +// style.ts +import { Props } from "..."; + +type StyledProps = Pick; + +const HrComponent = styled.hr` + height: ${(height) => height || "10px"}; + margin: 0; + background-color: ${(color) => colors[color || "gray7"]}; + border: none; + + ${(isFull) => + isFull && + css` + margin: 0 -15px; + `} +`; +``` + +- Hr 컴포넌트 Props의 height, color, isFull 속성은 styled-components 컴포넌트인 HrComponent에 바로 연결되며 타입도 같다. +- StyledProps를 따로 정의하려면 Props와 똑같은 타입임에도 새로 작성해야해 중복 코드가 생긴다. + +> 컴포넌트가 커질수록 또 styled-components로 만든 컴포넌트가 늘어날수록 중복되는 타입이 많아져 관리 포인트가 늘어난다. + +### 이런 문제를 Pick, Omit 타입을 사용해 styled-components 컴포넌트 타입을 작성해본다면? + +```ts +type StyledProps = Pick; +``` + +- Pick 유틸리티 타입을 활용해 props에서 필요한 부분만 선택해 styled-components 타입을 정의하면 중복된 코드를 작성하지 않아도 된다. +- 이외에도 상속받는 컴포넌트 혹은 부모 컴포넌트에서 자식 컴포넌트로 넘겨주는 props 등에도 Pick, Omit 같은 유틸리티 타입을 활용할 수 있다. + +
+
+ +## 3) NonNullable 타입 검사 함수를 사용하여 간편하게 타입 가드하기 + +타입 가드는 타입스크립트에서 많이 사용된다. + +특히 null을 가질 수 있는 값의 null 처리는 자주 사용되는 타입 가드 패턴의 하나이다. +if문을 일반적으로 타입 가드로 적용하기도 하지만, is + NonNullable타입으로 타입 검사를 위한 유틸 함수를 만들어 사용할 수 있다. + +### NonNullable 타입이란? + +타입스크립트에서 제공하는 유틸리티 타입으로 제네릭으로 받는 T가 null 또는 undefined일 때 never 또는 T를 반환하는 타입이다. NonNullable을 사용하면 null이나 undefined가 아닌 경우를 제외할 수 있다. + +```ts +type NonNullable = T extends null | undefined ? never : T; +``` + +### null, undefined를 검사해주는 NonNullable 함수 + +NonNullable 타입을 사용해 null, undefined를 검사해주는 타입 가드 함수를 만들어 쓸 수 있다. + +```ts +function NonNullable(value: T): value is NonNullable { + return value !== null && value !== undefined; +} +``` + +> Nullable 함수를 사용하는 쪽에서 true가 반환된다면 넘겨준 인자는 null이나 undefined가 아닌 타입으로 타입 가드가 된다. + +### Promise.all을 사용할 때 NonNullable 적용하기 + +Promise.all을 사용할 때 NonNullable를 적용한 예시를 살펴보자. + +```ts +// AdCampaignAPI 클래스: 광고 캠페인 관련 API를 제공 +class AdCampaignAPI { + // operating 메서드: 상점 번호(shopNo)를 인자로 받아 해당 상점의 광고 캠페인 정보를 비동기적으로 가져옴 + static async operating(shopNo: number): Promise { + try { + // 주어진 상점 번호에 해당하는 광고 캠페인 정보를 가져오기 위해 HTTP 요청을 보냄 + return await fetch(`/ad/shopNumber=${shopNo}`); + } catch (error) { + // 오류 발생 시 null을 반환 + return null; + } + } +} + +// shopList 배열: 상점 정보 +const shopList = [ + { shopNo: 100, category: "chicken" }, + { shopNo: 101, category: "pizza" }, + { shopNo: 102, category: "noodle" }, +]; + +// shopAdCampaignList 변수: 각 상점의 광고 캠페인 정보를 비동기적으로 가져와 저장 +const shopAdCampaignList = await Promise.all( + shopList.map((shop) => AdCampaignAPI.operating(shop.shopNo)) +); +``` + +> 위의 코드는 원하는 것처럼 Array타입으로 추론되는 것이 아니라 null이 될 수 있는 상태인 Array로 추론된다. + +```ts +const shopList = [ + { shopNo: 100, category: "chicken" }, + { shopNo: 101, category: "pizza" }, + { shopNo: 102, category: "noodle" }, +]; + +const shopAdCampaignList = await Promise.all( + shopList.map((shop) => AdCampaignAPI.operating(shop.shopNo)) +); + +const shopAds = shopAdCampaignList.filter(NonNullable); +``` + +> 해당 코드처럼 shopAdCampaignList를 필터링하면 Array타입으로 추론 가능하다. diff --git "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.4_\353\266\210\353\263\200_\352\260\235\354\262\264_\355\203\200\354\236\205\354\234\274\353\241\234_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.4_\353\266\210\353\263\200_\352\260\235\354\262\264_\355\203\200\354\236\205\354\234\274\353\241\234_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" index 3beced8..eacfa27 100644 --- "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.4_\353\266\210\353\263\200_\352\260\235\354\262\264_\355\203\200\354\236\205\354\234\274\353\241\234_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" +++ "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.4_\353\266\210\353\263\200_\352\260\235\354\262\264_\355\203\200\354\236\205\354\234\274\353\241\234_\355\231\234\354\232\251\355\225\230\352\270\260/seulgi.md" @@ -1 +1,127 @@ - +# 4.불변 객체 타입으로 활용하기 + +상숫값을 관리할 때 객체를 사용하기도 한다. + +아래처럼 키 타입을 해당 객체에 존재하는 키값으로 설정하는 것이 아니라 string으로 설정하면
+색상 컬러를 가져오는 getColorHex 함수의 반환 값은 any가 된다. + +```ts +const colors = { + red: "#45452", + green: "#0C952A", +}; + +const getColorHex = (key: string) => color[key]; +``` + +- 여기서 as const 키워드로 객체를 불변 객체로 선언한 후, keyof 연산자를 사용해
getColorHex 함수 인자로 실제 colors 객체에 존재하는 키값만 받도록 설정할 수 있다. + +> keyof, as const로 객체 타입을 구체적으로 설정하면 타입에 맞지 않는 값을 전달할 경우
타입 에러가 반환되기 때문에 컴파일 단계에서 발생할 수 있는 실수를 방지할 수 있다. + +## 1) Atom 컴포넌트에서 theme style 객체 활용하기 + +Atom 단위의 작은 컴포넌트의 스타일들을 다양하게 사용할 수 있게 구현하려면
+대부분 프로젝트에서는 해당 프로젝트의 스타일 값을 관리해주는 theme 객체를 두고 관리한다. + +theme 객체로 타입을 구체화하려면 keyof, typeof 연산자가 타입스크립트에서 어떻게 사용되는지 알아야 한다. + +### 타입스크립트 keyof 연산자로 객체의 키값을 타입으로 추출하기 + +타입스크립트에서 keyof 연산자는 객체 타입을 받아 해당 객체의 키값을 string 또는 number의 리터럴 유니온 타입을 반환한다. +객체 타입으로 인덱스 시그니처가 사용되었다면 keyof는 인덱스 시그니처 키 타입을 반환한다. + +```ts +interface ColorType { + red: string; + green: string; +} + +type ColorKeyType = keyof ColorType; // "red" | "green" +``` + +> ColorType 객체 타입의 keyof ColorType을 사용하면 객체의 키값인 "red" | "green"가 유니온으로 나오게 된다. + +### 타입스크립트 typeof 연산자로 값을 타입으로 다루기 + +타입스크립트의 typeof 연산자는 typeof가 변수 혹은 속성 타입을 추론하는 역할을 하고,
+단독으로 사용되기보다 주로 ReturnType 같이 유틸리티 타입이나 keyof 연산자 같이 타입을 받는 연산자와 함께 쓰인다. + +```ts +const colors = { + red: "#45452", + green: "#0C952A", +}; + +type ColorsType = typeof colors; // { red: string; green: string } +``` + +### 객체의 타입을 활용해서 컴포넌트 구현하기 + +keyof, typeof 연산자를 사용해서 theme 객체 타입을 구체화하고, string으로 타입을 설정했던 Button 컴포넌트를 개선해보자. + +```ts +import { FC } from "react"; +import styled from "styled-components"; + +const colors = { + black: "#000000", + gray: "#222222", + white: "#FFFFFF", + mint: "#2AC1BC", +}; + +const theme = { + colors: { + default: colors.gray, + ...colors, + }, + backgroundColor: { + // ... + }, +}; + +const colors = { + default: colors.white, + gray: colors.gray, + mint: colors.mint, + black: colors.black, +}; + +const fontSize = { + default: "16px", + small: "14px", + large: "18px", +}; + +type ColorType = keyof typeof colors; +type BackgroundColorType = keyof typeof theme.backgroundColor; +type FontSizeType = keyof typeof fontSize; + +interface Props { + color?: ColorType; + backgroundColor?: BackgroundColorType; + fontSize?: FontSizeType; + onClick: (event: React.MouseEvent) => void | Promise; +} + +const Button: FC = ({ fontSize, backgroundColor, color, children }) => { + return ( + + {children} + + ); +}; + +const ButtonWrap = styled.button>` + color: ${({ color }) => theme.colors[color ?? "default"]}; + background-color: ${({ backgroundColor }) => + theme.backgroundColor[backgroundColor ?? "default"]}; + font-size: ${({ fontSize }) => theme.fontSize[fontSize ?? "default"]}; +`; +``` + +> 여러 상숫값을 인자나 props로 받은 다음에 객체의 키값을 추출한 타입을 활용하면 객체에 접근할 때 타입스크립트의 도움을 받아 실수를 방지할 수 있다. diff --git "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.5_Record_\354\233\220\354\213\234_\355\203\200\354\236\205_\355\202\244_\352\260\234\354\204\240\355\225\230\352\270\260/seulgi.md" "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.5_Record_\354\233\220\354\213\234_\355\203\200\354\236\205_\355\202\244_\352\260\234\354\204\240\355\225\230\352\270\260/seulgi.md" index 3beced8..7fc3b32 100644 --- "a/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.5_Record_\354\233\220\354\213\234_\355\203\200\354\236\205_\355\202\244_\352\260\234\354\204\240\355\225\230\352\270\260/seulgi.md" +++ "b/CH05_\355\203\200\354\236\205_\355\231\234\354\232\251\355\225\230\352\270\260/5.5_Record_\354\233\220\354\213\234_\355\203\200\354\236\205_\355\202\244_\352\260\234\354\204\240\355\225\230\352\270\260/seulgi.md" @@ -1 +1,104 @@ - +# 5.Record 원시 타입 키 개선하기 + +객체 선언 시 키가 어떤 값이 명확하지 않다면 Record의 키를 string이나 number 값은 원시 타입으로 명시하곤 한다. +이때 타입스크립트는 키가 유효하지 않더라도 타입상으로는 문제가 없기 때문에 오류를 표시하지 않는다. + +Record를 명시적으로 사용하는 방안에 대해 알아보자. + +## 1) 무한한 키를 집합으로 가지는 Record + +다양한 음식 분류를 키로 사용하는 음식 배열이 담긴 객체를 만들었다고 보자. +key의 타입이 string일 경우 아래와 같은 문제가 있다. + +```ts +type Category = string; + +interface Food { + name: string; + // ... +} + +const foodByCategory: Record = { + 한식: [{ name: "제육 덮밥" }, { name: "뚝배기 불고기" }], + 일식: [{ name: "초밥" }, { name: "텐동" }], +}; + +foodByCategory["양식"]; // Food[]로 추론 + +foodByCategory["양식"].map((food) => console.log(food.name)); // 오류가 발생하지 않는다. +``` + +그러나, foodByCategory["양식"]은 런타임에서 undefined가 되어 오류를 반환한다. + +```ts +foodByCategory["양식"].map((food) => console.log(food.name)); // Uncaught TypeError: +// Cannot read properties of undefined (reading 'map') + +// 이때 자바스크립트의 옵셔널 체이닝 등을 사용해 런타임 에러를 방지할 수 있다. +foodByCategory["양식"]?.map((food) => console.log(food.name)); +``` + +- 어떤 값이 undefined인지 매번 판단해야하는 번거로움이 있을 수 있다. +- 실수로 undefined일 수 있는 값을 인지하지 못하고 작성 시 런타임 에러가 발생할 수 있다. + +> 이럴 때는 개발 중에 유효하지 않은 키가 사용되었는 지 또는 undefined일 수 있는 값이 있는 지 등 사전에 타입스크립트 기능을 활용해 파악하는 것이 중요하다. + +> **옵셔널 체이닝(optional chaining)?** >
객체의 속성을 찾을 때 중간에 null 또는 undefined가 있어도 오류 없이 안전하게 접근하는 방법이다.
+> ?. 문법으로 표현되며 옵셔널 체이닝을 사용할 때 중간에 null 또는 undefined인 속성이 있는지 검사한다.
+> 속성이 존재하면 해당 값을 반환하고, 존재하지 않으면 undefined를 반환한다. + +
+
+ +## 2) 유닛 타입으로 변경하기 + +키가 유한한 집합이라면 유닛 타입(다른 타입 쪼개기가 아닌 오직 하나의 적확한 값을 가지는 타입)을 사용할 수 있다. +(키가 무한해야 하는 상황에는 적합하지 않음) + +```ts +type Category = "한식" | "중식"; + +interface Food { + name: string; + // ... +} + +const foodByCategory: Record = { + 한식: [{ name: "제육 덮밥" }, { name: "뚝배기 불고기" }], + 일식: [{ name: "초밥" }, { name: "텐동" }], +}; + +foodByCategory["양식"]; // 존재하지 않는 타입이라는 에러 발생! +``` + +
+
+ +## 3) Partial을 활용해 정확한 타입 표현하기 + +키가 무한한 상황에서는 Partial을 활용해 해당 값이 undefined일 수 있는 상태임을 표현할 수 있다. + +```ts +type PartialRecord = Partial>; + +type Category = string; + +interface Food { + name: string; + // ... +} + +// ... + +const foodByCategory: PartialRecord = { + 한식: [{ name: "제육 덮밥" }, { name: "뚝배기 불고기" }], + 일식: [{ name: "초밥" }, { name: "텐동" }], +}; + +foodByCategory["양식"]; // Food[] 또는 undefined 타입으로 추론 +foodByCategory["양식"].map((food) => console.log(food.name)); // Object is possibly 'undefined' +foodByCategory["양식"]?.map((food) => console.log(food.name)); // OK +``` + +- 타입스크립트는 Food[] 또는 undefined 타입으로 추론해 개발자에게 이 값은 undefined값일 수 있으니 해당 값에 대한 처리가 필요하다고 표시해준다. +- 개발자는 안내를 보고 옵셔널 체이닝 사용이나 조건문을 사용해 런타임 오류를 줄일 수 있다.