diff --git "a/4\354\243\274\354\260\250/\354\236\245\354\247\200\354\235\200/item28~37.md" "b/4\354\243\274\354\260\250/\354\236\245\354\247\200\354\235\200/item28~37.md"
new file mode 100644
index 0000000..762ef7f
--- /dev/null
+++ "b/4\354\243\274\354\260\250/\354\236\245\354\247\200\354\235\200/item28~37.md"
@@ -0,0 +1,864 @@
+# 4장. 타입 설계
+
+## item28. 유효한 상태만 표현하는 타입을 지향하기
+
+타입을 잘 설계하면 코드를 직관적으로 작성할 수 있다.
+
+효과적으로 타입을 설계하려면 유효한 상태만 표현할 수 있는 타입을 만들어내는 것이 좋다.
+
+- 무효한 상태를 포함하도록 타입설계한 CASE
+
+ ```
+ interface State {
+ pageText: string;
+ isLoading: boolean; // loading 상태
+ error?: string; // 에러 메시지
+ }
+
+ function renderPage(state: State) {
+ if (state.error) {
+ return
;
+ }
+ }
+ ```
+ state가 currentRequest.state 하나로 관리되기 때문에, 무효한 상태를 허용하지 않도록 개선되었다.
+ 코드가 길어지거나 표현하기 어려울 수 있지만, 시간을 줄일 수 있기때문에 이 방식을 채택해야한다.
+
+---
+
+## item29. 사용할 때는 너그럽게, 생성할 때는 엄격하게
+
+함수의 매개변수는 타입의 범위가 넓어도 되지만, 결과를 반환할 때는 타입의 범위가 더 구체적이여야 한다.
+
+### 자유로운 타입의 매개변수 받기
+
+```tsx
+declare function setCamera(camera: CameraOptions): void;
+declare function viewportForBounds(bounds: LngLatBounds): CameraOptions;
+
+// 자유로운 타입의 매개변수
+interface CameraOptions {
+ center?: LngLat;
+ zoom?: number;
+ bearing?: number;
+ pitch?: number;
+}
+
+type LngLat =
+ | { lat: number; lng: number }
+ | { lon: number; lat: number }
+ | [number, number];
+
+type LngLatBounds =
+ | { northeast: LngLat; southwest: LngLat }
+ | [LngLat, LngLat]
+ | [number, number, number, number];
+```
+
+- CameraOptions의 값은 모두 선택적이어서, 값을 건드리지 않으며 다른 값 설정 가능
+- LngLat타입도 `{lng, lat}`, `{lon, lat}`, `[lng,lot]` 모두 넣을수 있어 편리함
+
+### 생성될 때의 반환타입도 너무 자유롭다면?
+
+```tsx
+declare function viewportForBounds(bounds: LngLatBounds): CameraOptions;
+
+function focusOnFeature(f: Feature) {
+ const bounds = calculateBoundingBox(f);
+ const camera = viewportForBounds(bounds); // 👎 반환 타입이 CameraOptions로, 너무 자유로움!
+ setCamera(camera);
+
+ const {
+ center: { lat, lng }, // ❌ Property 'lat' does not exist on type 'LangLat | undefined'
+ zoom,
+ } = camera;
+
+ zoom; // 👎 타입이 number | undefined
+ window.location.search = `?lat=${lat}&lng=${lng}&zoom=${zoom}`;
+}
+```
+
+- CameraOption의 center속성이 선택적이기 때문에, center의 타입이 `LangLat | undefined`
+- zoom 또한 `number|undefined`로 추론됨
+
+⇒ viewportForBounds의 타입선언이 사용될 때 뿐 아니라 만들어질 때도 너무 자유로운것이 문제를 일으킨다.
+
+⇒ 매개변수의 타입이 넓으면 사용하기 편리하지만, 반환타입의 범위가 넓으면 불편하다. 따라서 반환타입을 엄격하게 적용해야한다.
+
+### 생성될 때의 반환타입을 엄격하게 적용하기
+
+유니온 타입의 요소별 분기를 위한 방법은, 기본 형식을 따로 구분하는 것이다.
+
+기본 형태를 반환타입으로 쓰고, 느슨한 형태를 매개변수타입으로 사용하는게 좋다.
+
+```tsx
+// 좌표의 기본 형식
+interface LngLat {
+ lat: number;
+ lng: number;
+}
+// 더 자유로운 형식 포함한 느슨한 타입
+type LngLatLike = LngLat | { lon: number; lat: number } | [number, number];
+type LngLatBounds =
+ | { northeast: LngLatLike; southwest: LngLatLike }
+ | [LngLatLike, LngLatLike]
+ | [number, number, number, number];
+```
+
+- “배열같은것”의 사용을 위해 ArrayLike를 사용했던 것 처럼, `LngLat`과 `LngLatLike`로 구분
+ - 🤖GPT설명 - Type & TypeLike
+ TypeScript에서 Type과 TypeLike 패턴은 타입 시스템의 중요한 설계 관례입니다.
+ - **기본관례**
+ 1. **기본 타입 (Type)**:
+ - 정확하고 엄격한 구조를 가진 타입
+ - 명확한 속성과 동작을 정의
+ - 일관된 인터페이스 제공
+ 2. **유연한 타입 (TypeLike)**:
+ - 기본 타입을 포함하는 유니온 타입
+ - 유사하지만 구조가 조금 다른 여러 형태 허용
+ - 더 넓은 범위의 입력값 수용
+ - **주요 특징**
+ - **호환성**: TypeLike는 항상 Type과 호환됨 (Type은 TypeLike의 부분집합)
+ - **방향성**: 엄격한 내부 구현 → 유연한 외부 API
+ - **사용 문맥**: 주로 함수 매개변수에 TypeLike, 반환 값에 Type 사용
+ - **자주 사용되는 예시**
+ 1. **Array와 ArrayLike**
+
+ ```tsx
+ // Array: 모든 배열 메서드 포함*
+ const arr: Array = [1, 2, 3];
+ // ArrayLike: length와 인덱스 접근만 가능*
+ const arrLike: ArrayLike = { 0: 1, 1: 2, 2: 3, length: 3 };
+ ```
+
+ 2. **Promise와 PromiseLike**
+
+ ```tsx
+ // Promise: 완전한 Promise API
+ const p: Promise = new Promise((resolve) => resolve("done"));
+
+ // PromiseLike: then 메서드만 있으면 됨
+ const pLike: PromiseLike = {
+ then(onfulfilled) {
+ return onfulfilled("done");
+ },
+ };
+ ```
+ - 참고: https://jaenny-dev.tistory.com/5
+- LngLatBounds를 생성하는 다른 함수가 있다면, `LngLatBounds`와 `LngLatBoundsLike`로 구분하면 됨
+
+```tsx
+// Camera의 기본 형식
+interface Camera {
+ center: LngLat;
+ zoom: number;
+ bearing: number;
+ pitch: number;
+}
+// 더 자유로운 형식을 포함한 느슨한 타입
+interface CameraOptions extends Omit, "center"> {
+ center?: LngLatLike;
+}
+```
+
+- 완벽하게 정의된 `Camera`와, 부분적으로 정의된 `CameraOptions`를 구분
+ - 🤖GPT설명 - CameraOptions extends Omit, "center">
+ 1. Partial
+ - Partial은 타입의 모든 속성을 선택적(optional)으로 만듭니다.
+ - Camera 인터페이스의 모든 속성(center, zoom, bearing, pitch)이 선택적이 됩니다.
+ ```tsx
+ {
+ center?: LngLat;
+ zoom?: number;
+ bearing?: number;
+ pitch?: number;
+ }
+ ```
+ 2. Omit, "center">
+ - Omit은 타입에서 특정 속성을 제외합니다.
+ - Partial에서 "center" 속성을 제외합니다.
+ ```tsx
+ {
+ zoom?: number;
+ bearing?: number;
+ pitch?: number;
+ }
+ ```
+ 3. interface CameraOptions extends Omit, "center">
+ - Camera 인터페이스의 대부분의 속성을 상속받되, center 속성만 특별하게 처리합니다.
+ - 모든 속성이 선택적(optional)입니다.
+ ```tsx
+ {
+ center?: LngLatLike; // 선택적
+ zoom?: number; // 선택적
+ bearing?: number; // 선택적
+ pitch?: number; // 선택적
+ }
+ ```
+
+```tsx
+declare function setCamera(camera: CameraOptions): void;
+declare function viewportForBounds(bounds: LngLatBounds): Camera; // 👍 반환타입을 Camera로 더 엄격하게!
+
+function focusOnFeature(f: Feature) {
+ const bounds = calculateBoundingBox(f);
+ const camera = viewportForBounds(bounds); // ✅ 반환타입이 Camera
+ setCamera(camera); // Camera타입은 CameraOptions타입에 할당 가능
+
+ const {
+ center: { lat, lng }, // ✅ 정상
+ zoom,
+ } = camera;
+ zoom; // 👍 타입이 number
+ window.location.search = `?lat=${lat}&lng=${lng}&zoom=${zoom}`;
+}
+```
+
+---
+
+## item30. 문서에 타입 정보를 쓰지 않기
+
+주석에 변수명과 타입정보를 적지 말아야 한다. 타입 정보에 모순이 발생할 수 있다.
+
+- 주석
+
+ ```tsx
+ /**
+ * 페이지에 따른 전경색 반환
+ * 매개변수는 0또는 1 // 아님
+ * 매개변수가 없을땐 표준 전경색 문자열 반환 // 아님
+ */
+ function getForegroundColor(page: string) {
+ return page == "login" ? { r: 255, g: 255, b: 255 } : { r: 0, g: 0, b: 0 };
+ }
+ ```
+
+ - 강제하지 않는 이상 주석은 코드와 동기화되지 않는다.
+
+- 코드로 작성
+ ```tsx
+ /**
+ * 페이지에 따른 전경색 반환
+ */
+ function getForegroundColor(page: string): Color {
+ return page == "login" ? { r: 255, g: 255, b: 255 } : { r: 0, g: 0, b: 0 };
+ }
+ ```
+ - 타입구문은 컴파일러가 체크해주기 때문에 구현체와 정합성에 어긋나지 않는다. 따라서 코드가 추후 변경되더라도 정보가 동기화되기때문에 유지보수에 용이하다.
+
++) 변수명에도 타입을 넣지 않는게 좋다. (`ageNum`❌, `age: number`✅)
+
+---
+
+## item31. 타입 주변에 null값 배치하기
+
+### 함수 반환 타입 전체가 Null이거나 Null이 아니게 만들기
+
+값이 전부 null이거나, 전부 null이 아닌 경우로 분명히 구분된다면 값이 섞여있을 때 보다 다루기 쉽다.
+
+따라서 반환타입을 더 큰 객체로 만들고 반환타입 전체가 null이거나 null이 아니게 만들어야 한다.
+
+- 반환타입에 값이 섞여있는 경우
+
+ ```tsx
+ function extent(nums: number[]) {
+ let min, max;
+ for (const num of nums) {
+ if (!min) {
+ min = num;
+ max = num;
+ } else {
+ min = Math.min(min, num);
+ max = Math.max(max, num); // ❌ max의 타입이 number | undefined -> undefined일때 오류 발생함
+ }
+ }
+ return [min, max]; // 👎 반환타입이 (number | undefined)[]
+ }
+
+ const [min, max] = extent([0, 1, 2]);
+ const span = max - min; // ❌ max/min이 undefined일때 오류 발생함
+ ```
+
+ 반환타입에 number와 undefined가 섞여있게된다. 이럴 경우 객체를 사용하는 곳 에서 에러를 발생시킬 수 있다.
+
+- 반환타입이 null이거나 null이 아님
+ ```tsx
+ function extent2(nums: number[]) {
+ let result: [number, number] | null = null;
+ for (const num of nums) {
+ if (!result) {
+ result = [num, num];
+ } else {
+ result = [Math.min(result[0], num), Math.max(result[1], num)];
+ }
+ }
+ return result; // 👍 반환타입이 [number, number] | null
+ }
+
+ const [min2, max2] = extent2([0, 1, 2])!; // null아님 단언(!) 을 사용하여 null 제외하기
+ const span2 = max2 - min2; *// ✅ 오류 발생하지 않음*
+ ```
+ 반환타입이 null이거나 null이 아니게 되어, 결과값으로 단일 객체를 사용할 수 있다.
+
+### 클래스에서 값이 모두 준비되었을 때 값 생성하기
+
+- 데이터를 따로 update
+
+ ```tsx
+ class UserPosts {
+ user: UserInfo | null;
+ posts: Post[] | null;
+ constructor() {
+ this.user = null;
+ this.posts = null;
+ }
+
+ async init(userId: string) {
+ return Promise.all([
+ async () => (this.user = await fetchUser(userId)),
+ async () => (this.posts = await fetchPosts(userId)), // 👎 user와 posts가 따로 업데이트 됨
+ ]);
+ }
+
+ getUserName() {
+ // 👎 user와 posts가 각각 null이거나 null아닐 수 있음 (경우의 수 4가지)
+ return this.user.name;
+ }
+ }
+ ```
+
+ 속성값 두가지가 null이거나 null이 아닐 수 있어, 총 4가지의 경우의 수가 존재한다.
+
+ 이 속성값의 불확실성은 null체크를 난무하게 만들어 버그를 양산하게 된다.
+
+- 데이터가 모두 준비되었을 때 update
+ ```tsx
+ class UserPosts {
+ user: UserInfo | null;
+ posts: Post[] | null;
+ constructor() {
+ this.user = null;
+ this.posts = null;
+ }
+
+ async init(userId: string): Promise {
+ const [user, posts] = await Promise.all([
+ fetchUser(userId),
+ fetchPosts(userId),
+ ]);
+ return new UserPosts(user, posts); // 👍 user와 posts가 동시에 업데이트 됨
+ }
+
+ getUserName() {
+ // 👍 user와 posts가 모두 null이 아니라는 것을 확신할 수 있음
+ return this.user.name;
+ }
+ }
+ ```
+ 필요한 데이터를 한번에 업데이트하게 되면 모두 null이 아니게 된다. 이 경우엔 메서드 작성이 더 쉬워진다.
+
+---
+
+## item32. 유니온의 인터페이스보다는 인터페이스의 유니온 사용하기
+
+유니온의 인터페이스보다는, 인터페이스의 유니온이 더 정확하고 TS가 이해하기 좋다.
+
+### 인터페이스의 유니온으로 속성간의 관계 명확히 하기
+
+- 유니온의 인터페이스
+
+ ```tsx
+ interface Layer {
+ type: "fill" | "line" | "point";
+ layout: FillLayout | LineLayout | PointLayout;
+ paint: FillPaint | LinePaint | PointPaint;
+ }
+ ```
+
+ 만약 layout이 LineLayout이면서, pain가 FillPaint인것은 말이 되지 않는다.
+
+ 이는 라이브러리에서 오류를 일으키고 인터페이스를 다루기 어렵게 만든다.
+
+- 인터페이스의 유니온
+ ```tsx
+ interface FillLayer extends Layer {
+ type: "fill";
+ layout: FillLayout;
+ paint: FillPaint;
+ }
+ interface LineLayer extends Layer {
+ type: "line";
+ layout: LineLayout;
+ paint: LinePaint;
+ }
+ interface PointLayer extends Layer {
+ type: "point";
+ layout: PointLayout;
+ paint: PointPaint;
+ }
+ type Layer = FillLayer | LineLayer | PointLayer;
+ ```
+ 유효한 인터페이스의 유니온으로 Layer를 정의하면 속성들이 잘못된 조합으로 섞이는 경우를 방지할 수 있고, 유효한 상태만을 표기할 수 있다.
+ 이러한 타입정의를 통해 속성관의 관계를 더 명확히 만들 수 있다.
+
+---
+
+## item33. string 타입보다 더 구체적인 타입 사용하기
+
+### 문자열을 남발하여 선언된 코드 피하기
+
+string 타입은 ‘X’부터 모비딕 전체내용 까지, 그 범위가 매우 넓다. 단순히 string을 이용하기보다는 더 좁은 타입이 적절하지 않을지 고민해보아야 한다.
+
+- 문자열을 남발하여 선언된 예시 (Stringly Typed)
+
+ ```tsx
+ interface Album {
+ artist: string;
+ title: string;
+ releaseDate: string; // "2024-01-01"
+ recordingType: string; // "live" 혹은 "studio"
+ }
+ const album: Album = {
+ artist: "The Beatles",
+ title: "Abbey Road",
+ releaseDate: "September 26, 1969", // ❗️ 형식이 다름 - TS에서 잡을 수 없음
+ recordingType: "Studio", // ❗️ "S" 오타 - TS에서 잡을 수 없음
+ };
+ ```
+
+ 포맷이 정해져있는 releaseDate와 “live”혹은 “studio”값만 허용하는 recordingType까지 string으로 지정되었다.
+
+ 다음과 같은 경우에는 의도에 맞지 않는 값이 들어와도 체크할 수 없다.
+
+- 타입의 범위를 좁힌 예시
+ ```tsx
+ type RecordingType = "live" | "studio"; // 👍 두개의 유니온타입으로 제한
+ interface Album {
+ artist: string;
+ title: string;
+ releaseDate: Date; // 👍 Date형식으로 제한
+ recordingType: RecordingType;
+ }
+ const album: Album = {
+ artist: "The Beatles",
+ title: "Abbey Road",
+ releaseDate: new Date("1969-09-26"),
+ recordingType: "studio",
+ };
+ ```
+ 타입을 Date와 특정 문자열의 Union타입으로 제한하였다. 이렇게 바꾸면 TS는 오류를 더 세밀하게 체크할 수 있다.
+
+### string 타입 범위를 좁게 한 장점
+
+1️⃣ 타입을 명시적으로 정의하여 다른 곳으로 값이 전달되어도 타입정보가 유지된다.
+
+```tsx
+function getAlbumsOfType(recordingType: string): Album[] {
+ // 호출하는 곳에서 string타입이어야 한다는것 외에 다른 정보가 없음
+ return albums.filter((album) => album.recordingType === recordingType);
+}
+```
+
+"live" 혹은 "studio"라는 정보는 Album의 정의 안에 숨어있고, 함수를 호출하는 쪽에서는 recordingType에 대해 몰라도 된다.
+
+2️⃣ 타입을 명시적으로 정의하고 해당 타입의 의미를 설명하는 주석을 붙여넣을 수 있다.
+
+```tsx
+/** 이 녹음은 어떤 환경에서 이루어졌는지? */
+type RecordingType = "live" | "studio";
+
+function getAlbumsOfType(recordingType: RecordingType): Album[] {
+ // type RecordingType = "live" | "studio" - 이 녹음은 어떤 환경에서 이루어졌는지? (👍 주석 확인 가능!)
+ return albums.filter((album) => album.recordingType === recordingType);
+}
+```
+
+타입을 `RecordingType`으로 바꾸면, 함수를 사용하는 쪽에서 "live" | "studio" 라는 정보와 주석을 확인할 수 있다.
+
+3️⃣ keyof연산자로 더 세밀하게 객체의 속성 체크가 가능하다.
+
+- any사용
+
+ ```tsx
+ // 어떤 배열에서 한 필드의 값만 추출하는 함수
+ function pluck(records: any[], key: string): any[] {
+ return records.map((record) => record[key]);
+ }
+ ```
+
+ 에러가 나진 않지만, key에 모든 string타입이 허용되고, 리턴타입도 any타입이어서 정밀하지 못하다.
+
+- 제네릭타입 & keyof 사용
+
+ ```tsx
+ // keyof T == "artist" | "title" | "releaseDate" | "recordingType"
+ function pluck(records: T[], key: keyof T): T[keyof T][] {
+ return records.map((record) => record[key]);
+ }
+
+ // releaseDates: (string | Date)[] - 👎 Album객체에서 가질 수 있는 모든 타입
+ const releaseDates = pluck(albums, "releaseDate");
+ ```
+
+ key가 string타입에서 keyof T타입으로 바뀌었기 때문에, Album 키값들의 Union으로 제한된다.
+
+ 하지만 반환타입을 찍어보면, Album객체 내에서 가질 수 있는 모든 값의 타입이 찍혀나오고, 이 또한 범위가 너무 넓어서 적절하지 않다.
+
+- 두번째 제네릭 K 도입
+ ```tsx
+ function pluck(records: T[], key: K): T[K][] {
+ return records.map((record) => record[key]);
+ }
+
+ // 👍 releaseDates: Date[]
+ const releaseDates = pluck(albums, "releaseDate");
+ ```
+ keyof T를 extends한 K제네릭을 도입하면, 반환타입의 값을 적절하게 줄일 수 있다.
+ 이렇게 객체 속성이름을 함수의 매개변수로 받는 경우엔 keyof를 활용한 제네릭을 사용해야한다.
+
+---
+
+## item34. 부정확한 타입보다는 미완성 타입을 사용하기
+
+일반적으론 타입이 구체적일수록 버그를 더 많이 잡고 TS가 제공하는 도구를 활용할 수 있지만, 타입 선언의 정밀도를 높이는 일은 주의를 기울여야 한다.
+
+잘못된 타입은 차라리 타입이 없는 것 보다 못할 수 있다!
+
+- 수정 전 - 미완성의 타입
+
+ ```tsx
+ interface Point {
+ type: "Point";
+ coordinates: number[];
+ }
+ interface LineString {
+ type: "LineString";
+ coordinates: number[][];
+ }
+ interface Polygon {
+ type: "Polygon";
+ coordinates: number[][][];
+ }
+ type Geometry = Point | LineString | Polygon;
+ ```
+
+ 이 좌표에 쓰이는 number[]가 추상적이라, 이 coordinates를 경도와 위도를 나타내는 튜플타입 선언으로 수정한다.
+
+- 수정 후 - 부정확한 타입
+ ```tsx
+ type GeoPosition = [number, number];
+ interface Point {
+ type: "Point";
+ coordinates: GeoPosition;
+ }
+ ```
+ 경도와 위도를 나타내는 number의 튜플타입으로 지정하여 더 정확한 표현이 가능해졌지만,
+ 만약 세번째 요소인 고도를 사용하는 사용자가 있었다면 이는 에러를 유발시킨다.
+ 이렇게 타입을 세밀하게 만들고자 하는 시도가 과해지면 타입을 오히려 부정확하게 만들수 있다.
+
+
+
+---
+
+## item35. 데이터가 아닌, api와 명세를 보고 타입 만들기
+
+### 명세 참고하기
+
+타입을 생성할 때, 예시 데이터가 아니라 명세를 참고하여 타입을 생성해야한다.
+
+```tsx
+// geojson
+interface BoundingBox {
+ lat: [number, number];
+ lng: [number, number];
+}
+import { Feature } from "geojson";
+
+function calculateBoundingBox(f: Feature): BoundingBox | null {
+ let box: BoundingBox | null = null;
+
+ const helper = (coords: any[]) => {
+ // ...
+ };
+
+ const { geometry } = f;
+ if (geometry) {
+ helper(geometry.coordinates); // ❌ Property 'coordinates' does not exist on type 'Geometry'.
+ }
+
+ return box;
+}
+```
+
+위 예시는 geometry에 coordinates 속성이 있다고 가정한 것이 문제다.
+다른 도형과 다르게 GeometryCollection에는 coordinates 속성이 없다.
+
+```tsx
+function calculateBoundingBox(f: Feature): BoundingBox | null {
+ let box: BoundingBox | null = null;
+ const helper = (coords: any[]) => {
+ // ...
+ };
+
+ const geometryHelper = (geometry: Geometry) => {
+ // 👍 모든 타입 지원하는 헬퍼함수
+ if (geometry.type === "GeometryCollection") {
+ geometry.geometries.forEach(geometryHelper);
+ } else {
+ helper(geometry.coordinates); // OK
+ }
+ };
+ const { geometry } = f;
+ if (geometry) {
+ geometryHelper(geometry);
+ }
+
+ return box;
+}
+```
+
+위와 같이 GeometryCollection 조건을 분기해서 헬퍼 함수를 호출하면 모든 타입을 지원할 수 있다.
+
+
+
+### GraphQL
+
+GraphQL의 장점은 특정 쿼리에 대해 타입스크립트 타입을 생성할 수 있다는 것이다.
+
+타입은 단 하나의 원천 정보인 GraphQL 스키마로 부터 생성이 되기 때문에, 타입과 실제 값이 항상 일체한다.
+
+만약 명세나 공식 스키마가 없다면, 데이터를 기반으로 타입을 생성해야하고, quicktype같은 도구를 이용할 수도 있다. 하지만 생성된 타입이 실제 데이터와 일치하지 않을 수 있으므로 주의해야한다.
+https://app.quicktype.io/
+
+---
+
+## item36. 해당 분야의 용어로 타입 이름 짓기
+
+엄선된 타입, 속성, 변수의 이름은 의도를 명확히 하고 코드와 타입의 추상화 수준을 높여준다.
+
+반면에 잘못 선택한 타입 이름은 코드의 의도를 왜곡하고 잘못된 개념을 심어줄 수 있다.
+
+- 잘못된 예시
+
+ ```tsx
+ interface Animal {
+ name: string;
+ endangered: boolean;
+ habitat: string;
+ }
+ ```
+
+ - name이 너무 일반적인 용어라 명확하지 않다.
+ - endangered 속성이 boolean타입이라 애매한 영역이 생긴다 (이미 멸종된 동물은 true?)
+ - habitat은 속성 범위가 너무 넓은 string타입이다.
+
+- 개선
+ ```tsx
+ interface Animal {
+ commonName: string;
+ genus: string;
+ species: string;
+ status: ConservationStatus;
+ climates: KoppenClimate;
+ }
+ type ConservationStatus = "EX" | "EW" | "CR" | "EN" | "VU" | "NT" | "LC";
+ type KoppenClimate = "Af" | "Am" | "As" | "Aw";
+ ```
+ - name을 commonName, genus, species 등 구체적인 용어로 대체했다.
+ - endangered는 IUCN 표준 분류체계인 ConservationStatus타입으로 변경되었다.
+ - habitat은 기후를 뜻하는 climates로 변경되었고, 쾨펜 기후분류를 사용했다.
+
+
+
+---
+
+## item37. 공식 명칭에는 상표를 붙이기
+
+### 상표 사용하기
+
+- 구조적 타이핑에서 발생하는 에러
+
+ ```tsx
+ interface Vector2D {
+ x: number;
+ y: number;
+ }
+ function calculateNorm(p: Vector2D) {
+ return Math.sqrt(p.x * p.x + p.y * p.y);
+ }
+
+ calculateNorm({ x: 3, y: 4 }); // ✅ 정상, 5
+ const vector3d = { x: 3, y: 4, z: 5 }; // 추가된 속성 z
+ calculateNorm(vector3d); // ✅ 정상⁉️ 하지만 calculateNorm은 3차원 벡터의 계산식이 아님!
+ ```
+
+ 구조적 타이핑 특성 때문에 가끔 코드가 이상한 결과를 낼 수 있다.
+
+ 해당 코드는 구조적 타이핑 관점에서 문제를 일으키지 않지만, 수학적으로 따지면 옳지 않은 값을 리턴한다.
+
+ `calculateNorm`이 3차원 벡터를 허용하지 않하는 방법중 하나는 바로 상표(`_brand`)를 사용하는 방식이다
+
+- 생성 함수를 통해 상표(\_brand) 붙이기
+ ```tsx
+ interface Vector2D {
+ _brand: "2d"; // 공식명칭(nominal typing) - 리터럴타입 "2d"
+ x: number;
+ y: number;
+ }
+
+ // ✨ 생성 함수
+ function createVec2(x: number, y: number): Vector2D {
+ return { x, y, _brand: "2d" };
+ }
+
+ // 계산 함수
+ function calculateNorm(p: Vector2D) {
+ return Math.sqrt(p.x * p.x + p.y * p.y);
+ }
+
+ calculateNorm(createVec2(3, 4)); // ✅ 정상, 생성자 함수를 사용하여 Vector2D 보장
+
+ const vec3D = { x: 3, y: 4, z: 5 };
+ calculateNorm(vec3D); // ❌ 오류 - Property '_brand' is missing in type '{ x: number; y: number; z: number; }' but required in type 'Vector2D'
+
+ const vec2D = { x: 3, y: 4, _brand: "2d" }; // _brand가 string타입으로 추론됨
+ calculateNorm(vec2D); // ❌오류 - Types of property '_brand' are incompatible. Type 'string' is not assignable to type '"2d"'.
+ ```
+ `_brand`는 리터럴타입의 ”2d”로, 해당 타입의 고유성을 보장하는 역할을 하고 일반 string과는 구분된다.
+ `createVec2`생성자 함수를 이용하여 `_brand`속성을 부여하면, `Vector2D`라는 타입의 고유성이 보장되고, 구조적 타이핑에 의한 의도치 않은 동작을 방지할 수 있다.
+
+### 타입가드 함수로 런타임에 상표(\_brand) 검사하기
+
+```
+// 절대경로 판단하기
+
+type AbsolutePath = string & { _brand: "absolutePath" }; // string과 브랜드 속성의 교차타입
+function listAbsolutePath(path: AbsolutePath) {
+ return path;
+}
+
+// 🛡️ 타입 가드 함수
+function isAbsolutePath(path: string): path is AbsolutePath {
+ return path.startsWith("/");
+}
+
+function f(path: string) {
+ if (isAbsolutePath(path)) {
+ // 조건문으로 타입 정제
+ listAbsolutePath(path); // ✅ 정상 (path는 AbsolutePath 타입)
+ }
+
+ listAbsolutePath(path); // ❌ path는 string타입이므로 오류
+ listAbsolutePath(path as AbsolutePath); // ✅ 정상 (path는 AbsolutePath 타입으로 강제 형변환) - ❗️하지만 단언문은 지양해야함
+
+}
+```
+
+AbsolutePath는 string 타입과 브랜드 속성의 교차 타입이고, 이런 객체를 만들 순 없기에 온전히 타입시스템의 영역이다.
+
+타입가드 함수 `isAbsolutePath`를 if문에 활용하면 런타임 검사를 통해 타입정제가 가능하다. 이 타입가드를 통해 조건문 안에선 안정적으로 타입을 사용할 수 있다.
+
+로직을 분기하는 대신 as AbsolutePath를 사용해 오류를 제거할 수도 있지만, 타입 단언의 사용은 지양해야한다.
+
+```tsx
+// 이진 검색
+
+type SortedList = T[] & { _brand: "sorted" };
+
+// 🛡️ 타입 가드 함수
+function isSorted(xs: T[]): xs is SortedList {
+ for (let i = 0; i < xs.length - 1; i++) {
+ if (xs[i] > xs[i + 1]) {
+ return false;
+ }
+ }
+ return true;
+}
+// 사용 함수
+function binarySearch(xs: SortedList, x: T): boolean {
+ // 이진검색 - 이미 정려
+ let low = 0;
+ let high = xs.length - 1;
+
+ while (low <= high) {
+ const mid = Math.floor((low + high) / 2);
+ const midItem = xs[mid];
+ if (midItem === x) return true;
+ [low, high] = midItem < x ? [mid + 1, high] : [low, mid - 1];
+ }
+ return false;
+}
+
+function isFoundNum(arr: number[]) {
+ if (isSorted(arr)) {
+ return binarySearch(arr, 3);
+ }
+}
+```
+
+이진검색`binarySearch`함수의 경우 이미 정렬된 상태를 가정하기 때문에, 정렬되어있지 않은 배열이 들어온다면 잘못된 결과가 나올 수 있다.
+
+타입스크립트에서 목록이 정렬되어있다는 의도를 표현하려면 상표기법을 사용할 수 있다.
+
+`binarySearch`를 호출하려면 `isSorted`함수를 통해 `SortedList`타입임을 증명해야한다.
diff --git "a/6\354\243\274\354\260\250/\353\260\234\355\221\234\354\236\220\353\243\214/\353\260\234\355\221\234\354\236\220\353\243\214.md" "b/6\354\243\274\354\260\250/\353\260\234\355\221\234\354\236\220\353\243\214/\353\260\234\355\221\234\354\236\220\353\243\214.md"
index f585162..98244fa 100644
--- "a/6\354\243\274\354\260\250/\353\260\234\355\221\234\354\236\220\353\243\214/\353\260\234\355\221\234\354\236\220\353\243\214.md"
+++ "b/6\354\243\274\354\260\250/\353\260\234\355\221\234\354\236\220\353\243\214/\353\260\234\355\221\234\354\236\220\353\243\214.md"
@@ -1,10 +1,14 @@
+# 7장. 코드를 작성하고 실행하기
+
## Item53. 타입스크립트 기능보다는 ECMAScript기능을 사용하기
-자바스크립트에 새로 추가된 기능은 타입스크립트 초기 버전의 기능과 호환성 문제를 발생시켰다.
+TS가 태동하던 2010년경, JS는 결함과 개선사항이 많은 언어였고 클래스, 데코레이터 등의 기능은 프레임워크나 트랜스파일러로 보완하는게 일반적이었다. 따라서 TS도 초기버전엔 독립적으로 개발한 클래스, 열거형(enum), 모듈시스템을 포함시킬 수 밖에 없었다.
+
+이후 TC39는 이런 내장기능들을 추가했고, 자바스크립트에 새로 추가된 기능은 타입스크립트 초기 버전의 기능과 호환성 문제를 발생시켰다.
타입스크립트 진영은 JS 신규 기능을 그대로 채택하고, TS 초기버전과의 호환성을 포기했다.
-이 전에 사용되고 있던 타입공간(TS)와 값공간(JS)의 경계를 혼란스럽게 만드는 기능들이 있는데, 이는 사용하지 않는것이 좋다.
+초기에 사용되고 있던 타입공간(TS)와 값공간(JS)의 경계를 혼란스럽게 만드는 기능들이 있는데, 이는 사용하지 않는것이 좋다.
### 열거형(enum)
@@ -19,12 +23,12 @@ let flavor = Flavor.CHOCOLATE; // flavor: Flavor
Flavor[0]; // 값이 VANILLA
```
-타입스크립트 열거형은 상황에 따라 다르게 동작한다.
+타입스크립트 열거형은 상황에 따라 다르게 동작하고 몇가지 문제점이 있다.
- 숫자 열거형(Flavor) 에 0,1,2 외의 다른 숫자가 할당되면 위험하다. (원래 비트 플래그 구조를 표현하기 위해 설계되어서)
-→ `let flavor: Flavor = 3;` 같은 케이스는 막힘! (업데이트 된듯)
-- 상수 열거형은 런타임에 완전히 제거된다. 앞의 예제를 const enum Flavor로 바꾸면 컴파일러는 Flavor.CHOCOLATE을 0으로 바꿔버린다.
-- preserveConstEnums 플래그를 설정한 상태의 상수 열거형은 보통의 열거형처럼 런타임 코드에 상수 열거형 정보를 유지한다.
+→ `let flavor: Flavor = 3;` 같은 케이스는 TS에서 에러로 표기됨
+- 상수 열거형은 런타임에 완전히 제거된다. 앞의 예제를 `const enum Flavor`로 바꾸면 컴파일러는 `Flavor.CHOCOLATE`을 0으로 바꿔버린다.
+- `preserveConstEnums` 플래그를 설정한 상태의 상수 열거형은 보통의 열거형처럼 런타임 코드에 상수 열거형 정보를 유지한다.
- 문자열 열거형은 런타임의 타입안정성과 투명성을 제공한다. 하지만 타입스크립트의 구조적 타이핑이 아닌 명목적 타이핑을 사용한다.
```tsx
@@ -58,6 +62,33 @@ scoop(Flavor2.CHOCOLATE);
```
- 이렇게 열거형을 임포트하고 문자열 대신 사용해야한다.
+- 🤖GPT 설명 - 명목적 타이핑이란?
+
+ 타입스크립트는 기본적으로 **구조적 타이핑(Structural Typing)**을 사용합니다. 즉, 타입의 이름이 아니라 **구조(필드와 메서드의 형태)**를 기준으로 타입 호환성을 판단합니다.
+
+ 하지만, **`enum`(열거형)**은 타입스크립트에서 명목적 타이핑처럼 동작합니다. 즉, 같은 구조를 가진 값이라도 열거형의 이름이 다르면 호환되지 않습니다.
+
+ - **예제: 명목적 타이핑처럼 동작하는 `enum`**
+
+ ```tsx
+ enum Flavor1 {
+ VANILLA = 'vanilla',
+ CHOCOLATE = 'chocolate',
+ }
+
+ enum Flavor2 {
+ VANILLA = 'vanilla',
+ CHOCOLATE = 'chocolate',
+ }
+
+ let flavor: Flavor1 = Flavor1.VANILLA;
+
+ // ❌ 다른 열거형은 호환되지 않음
+ flavor = Flavor2.VANILLA; // 오류: Type 'Flavor2' is not assignable to type 'Flavor1'
+ ```
+
+ - `Flavor1`과 `Flavor2`는 구조적으로 동일하지만, 타입스크립트는 **열거형의 이름**을 기준으로 타입 호환성을 판단합니다.
+ - 따라서, `Flavor1`과 `Flavor2`는 서로 다른 타입으로 간주됩니다.
```tsx
// 👍 enum 대신 리터럴타입의 유니온 사용하기
@@ -69,15 +100,10 @@ flavor = 'mint' // ❌ Type '"mint"' is not assignable to type 'Flavor'
enum을 사용하지 말고 리터럴 타입의 유니온을 사용해야 한다.
-| **특징** | **enum** | **유니언 타입** |
-| --- | --- | --- |
-| **런타임 존재 여부** | 런타임에 객체로 존재 (Flavor.VANILLA) | 런타임에 제거됨 |
-| **타입 안정성** | 명목적 타이핑 (정의된 열거형 값만 허용) | 구조적 타이핑 (정의된 문자열 값만 허용) |
-| **가독성** | 열거형 이름으로 값의 의미를 명확히 표현 가능 | 문자열 값만 사용 |
-| **성능** | 런타임에 객체를 유지 (숫자/문자열 열거형) | 런타임에 제거되어 더 가볍게 동작 |
-
### 매개변수 속성
+보통 클래스를 초기화할 때 속성을 할당하기 위해 생성자의 매개변수를 사용하는데, TS에선 매개변수 속성이라는 더 간결한 문법을 제공한다.
+
- JS 문법
```tsx
@@ -234,7 +260,7 @@ function foo(abc: ABC) {
```
-- Object.entries는 복잡한 기교 없이 사용 가능하다.
+- Object.entries는 직관적이진 않지만 복잡한 기교 없이 사용 가능하다.
- 더욱 일반적으로 사용 가능