diff --git "a/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.1_\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\353\237\260\355\203\200\354\236\204\352\263\274_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\354\273\264\355\214\214\354\235\274/seulgi.md" "b/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.1_\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\353\237\260\355\203\200\354\236\204\352\263\274_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\354\273\264\355\214\214\354\235\274/seulgi.md" index 3beced8..e498801 100644 --- "a/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.1_\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\353\237\260\355\203\200\354\236\204\352\263\274_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\354\273\264\355\214\214\354\235\274/seulgi.md" +++ "b/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.1_\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\353\237\260\355\203\200\354\236\204\352\263\274_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230_\354\273\264\355\214\214\354\235\274/seulgi.md" @@ -1 +1,79 @@ - +# 1. 자바스크립트의 런타임과 타입스크립트의 컴파일 + +## 1) 런타임과 컴파일타임 + +프로그래밍 언어는 일반적으로 고수준 언어 / 저수준 언어로 구분할 수 있다. + +
+ +### 고수준 언어? 저수준 언어? + +- 고수준 언어 : 사람이 이해하기 쉬운 형식으로 작성 +- 저수준 언어 : 컴퓨터가 이해하기 쉬운 형식으로 작성 + +자바스크립트는 대표적인 고수준 언어로, 컴파일러나 인터프리터에 의해
+저수준 프로그래밍 언어, 즉 기계가 이해할 수 있는 언어로 번역되어 실행된다. + +### 컴파일 타임? + +소스코드(개발자가 작성)가 컴파일 과정을 거쳐 컴퓨터가 인식할 수 있는
+기계어 코드(바이트 코드)로 변환되어 실행할 수 있는 프로그램이 되는 과정이다. + +### 런타임? + +소스코드의 컴파일이 완료되면 프로그램이 메모리에 적재되어 실행되는데
+이 시간을 런타임이라고 하는데 즉 런타임은 컴파일 과정을 마친 응용 프로그램이
+사용자에 의해 실행되는 과정이다. + +
+
+ +## 2) 자바스크립트 런타임 + +자바스크립트 런타임은 자바스크립트가 실행되는 환경이다. + +자바스크립트 런타임의 주요 구성 요소로
+자바스크립트 엔진, 웹 API, 콜백 큐, 이벤트 루프, 렌더 큐가 있다. + +> 자바스크립트는 대표적인 인터프리터 언어로 별도의 컴파일 과정이 존재하지 않는다고 알려져 있다.
+ +> 하지만 엄밀히 말해 컴파일 단계가 존재하는데 자바스크립트를 해석하고 실행하는 역할을 하는
+> V8 엔진은 때때로 자바스크립트 코드를 최적화하기 위해 컴파일 단계를 거친다.(실행 속도 향상을 위한 목적)
+> 이 과정에서 자바스크립트 코드를 캐싱해 실행 시간을 단축시킨다. + +
+
+ +## 3) 타입스크립트의 컴파일 + +타입스크립트는 **tsc**라고 불리는 컴파일러를 통해 자바스크립트 코드로 변환된다. + +하지만, 타입스크립트는 고수준언어 -> 고수준 언어로 변환되는 것이기 때문에
+컴파일이 아닌 트랜스파일로 불리거나 소스코드 -> 다른 소스코드로 변환하는 것이기에
+소스 대 소스 컴파일러라고 지칭하기도 한다. + +> 타입스크립트 컴파일러는 소스코드를 해석해 AST를 만들고 이후 타입 확인을 거친 다음 결과 코드를 생성한다. + +
+ +### 컴파일러가 소스코드를 컴파일해 프로그램으로 실행되기까지의 과정을 알아보자! + +1. 타입스크립트 소스코드를 타입스크립트 AST로 만든다.(tsc) +2. 타입 검사기가 AST를 확인해 타입을 확인한다.(tsc) +3. 타입스크립트 AST를 자바스크립트 소스로 변환한다.(tsc) +4. 자바스크립트 소스코드를 자바스크립트 AST로 만든다.(런타임) +5. AST가 바이트 코드로 변환된다.(런타임) +6. 런타임에서 바이트 코드가 평가되 프로그램이 실행된다.(런타임) + +### AST(Abstract Syntax Tree)? + +컴파일러가 소스코드를 해석하는 과정에서 생성된 데이터 구조다.
+컴파일러는 어휘적 분석과 구문 분석을 통해 소스코드를 노드 단위의 트리 구조로 구성한다. + +### 타입스크립트 === 정적 검사기? + +타입스크립트를 컴파일타임에 에러를 발견할 수 있는 정적 검사기라고 부르는데 +컴파일타임에 타입을 검사하기 때문이다.(런타임 전 에러 발견 가능) + +
+
diff --git "a/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.2_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\353\217\231\354\236\221/seulgi.md" "b/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.2_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\353\217\231\354\236\221/seulgi.md" index 3beced8..46f1cb0 100644 --- "a/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.2_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\353\217\231\354\236\221/seulgi.md" +++ "b/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.2_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\353\217\231\354\236\221/seulgi.md" @@ -1 +1,94 @@ - +# 2. 타입스크립트 컴파일러의 동작 + +## 1) 코드 검사기로서의 타입스크립트 컴파일러 + +타입스크립트는 컴파일타임에 문법 에러와 타입 관련 에러를 모두 검출한다. + +```js +const developer = { + work() { + console.log("working"); + }, +}; + +developer.work(); +developer.sleep(); // TypeError; +``` + +> 해당 코드를 자바스크립트로 작성하는 시점에는 에러가 발생하지 않으나, 실제 실행 시 에러가 난다. + +```ts +const developer = { + work() { + console.log("working"); + }, +}; + +developer.work(); +developer.sleep(); // Property "sleep" does not exist on type "{work(): void}" +``` + +> 타입스크립트는 코드를 실행 전에 에러를 사전에 발견해 알려준다.
+> 즉, 런타임에 발생할 수 있는 문법 오류 등의 에러뿐 아니라 타입 에러도 잡을 수 있다. + +타입스크립트 컴파일러는 tsc binder를 사용하여 타입 검사를 하며, 컴파일타임에 타입 오류를 발견한다.
+타입 검사를 거쳐 코드를 안전하게 만든 이후에는 타입스크립트 AST를 자바스크립트 코드로 변환한다. + +
+
+ +## 2) 코드 변환가로서의 타입스크립트 컴파일러 + +타입스크립트 코드를 각자의 런타임 환경에서 동작할 수 있도록
+구버전의 자바스크립트로 트랜스파일한다.
+이것이 타입스크립트 컴파일러의 두 번째 역할이다. + +- 타입스크립트 컴파일러의 target 옵션을 사용하여 특정 버전의 자바스크립트 소스코드로 컴파일할 수 있다. +- 타입스크립트 컴파일러는 타입 검사를 수행한 후 코드 변환을 시작한다. +- 이때 타입 오류가 있더라도 일단 컴파일을 진행한다. + +> 타입스크립트 코드가 자바스크립트 코드로 변환되는 과정은
+> 타입 검사와 독립적으로 동작하기 때문이다.
+ +> 타입스크립트 코드 타이핑이 잘못되서 발생하는 에러도 런타임 에러로 처리된다. 자바스크립트 실행 과정에서 런타임 에러로 처리된다. + +### 다만 문제가 있다..! + +타입스크립트 소스코드에 타입 에러가 있더라도 +자바스크립트로 컴파일되어 타입 정보가 모두 제거된 후에는 +타입이 아무런 효력을 발휘하지 못한다. + +```ts +interface Square { + width: number; +} + +interface Rectangle extends Square { + height: number; +} + +type Shape = Square | Rectangle; + +function calculateArea(shape: Shape) { + // 에러! Rectangle이 타입이기 때문에 런타임은 해당 코드를 이해하지 못함 + if (shape instanceof Rectangle) { + return shape.width * shape.height; + } else { + return shape.width * shape.width; + } +} +``` + +> 타입스크립트 코드가 자바스크립트로 컴파일되는 과정에서 모든 인터페이스, 타입, 타입 구문이 제거되어 버리기 때문에 런타임에서는 타입을 사용할 수 없다. + +### 타입스크립트 컴파일러의 역할? + +1. 최신 버전의 타입스크립트 / 자바스크립트 코드를 구버전의 자바스크립트로 트랜스파일한다. +2. 코드의 타입 오류를 검사한다. + +### 바벨 (Babel)? + +ECMAScript 2015 이후의 코드를 현재 또는 오래된 브라우저와 호환되는 버전으로 변환해주는 자바스크립트 컴파일러이다. + +
+
diff --git "a/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.3_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\352\265\254\354\241\260/seulgi.md" "b/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.3_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\352\265\254\354\241\260/seulgi.md" index 3beced8..b91a80a 100644 --- "a/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.3_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\352\265\254\354\241\260/seulgi.md" +++ "b/CH06_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274/6.3_\355\203\200\354\236\205\354\212\244\355\201\254\353\246\275\355\212\270_\354\273\264\355\214\214\354\235\274\353\237\254\354\235\230_\352\265\254\354\241\260/seulgi.md" @@ -1 +1,184 @@ - +# 3. 타입스크립트 컴파일러의 구조 + +타입스크립트 컴파일러가 동작하는 데 중요한 몇 가지 중요한 구성 요소가 있다. +타입스크립트 컴파일러의 구체적인 동작을 살펴보자. + +| 순서 | 단계 | 설명 | +| ---- | ------ | ----------------------------- | +| 1 | 스캐너 | .ts 토큰화 | +| 2 | 파서 | 토큰 기반 AST 생성 | +| 3 | 바인더 | AST 노드 기반 심볼 생성 | +| 4 | 체커 | AST + 심볼 기반 타입 검사 | +| 5 | 이미터 | AST + 코드 생성 기반 .js 생성 | + +> 위의 5단계를 거쳐 타입 검사와 자바스크립트 소스 변환을 진행한다. + +
+
+ +## 1) 프로그램(Program) + +타입스크립트 컴파일러는 tsc명령어로 실행된다.
+컴파일러는 tsconfig.json에 명시된 컴파일 옵션을 기반으로 컴파일을 수행한다. + +전체적인 컴파일 과정을 관리하는 프로그램 객체(인스턴스)가 생성되고,
+객체 내 컴파일할 타입스크립트 소스 파일과 소스 파일 내 임포트된 파일을 불러오는데
+가장 최초 불러온 파일을 기준으로 컴파일 과정이 시작된다. + +
+
+ +## 2) 스캐너(Scanner) + +스캐너는 타입스크립트 소스 파일을 어휘적으로 분석하여 토큰을 생성하는 역할을 한다.
+소스코드를 작은 단위로 나누어 의미 있는 토큰으로 변환하는 작업을 수행한다. + +```ts +// 코드는 스캐너에 의해 다음과 같은 토큰으로 분석된다. +const woowa = "bros"; +``` + +- ConstKeyword: const +- WhitespaceTrivia: (공백) +- Identifier: woowa +- WhitespaceTrivia: (공백) +- EqualsToken: = +- WhitespaceTrivia: (공백) +- StringLiteral: "bros" +- SemicolonToken: ; + +
+
+ +## 3) 파서(Parser) + +스캐너가 소스 파일을 토큰으로 나눠주면, 파서는 그 토큰 정보를 이용하여 AST를 생성한다. + +파서는 생성된 토큰 목록을 활용하여 구문적 분석을 수행한다. + +코드의 실질적인 구조를 노드 단위의 트리 형태로 표현하고,
+각각의 노드는 코드상의 위치, 구문 종류, 코드 내용과 같은 정보를 담는다. + +### AST? + +컴파일러가 동작하는 데 핵심 기반이 되는 자료 구조로,
+소스코드의 구조를 트리 형태로 표현한다. + +AST의 최상위 노드는 타입스크립트 소스 파일이며,
+최하위 노드는 파일의 끝 지점으로 구성된다. + +
+
+ +## 4) 바인더(Binder) + +체커 단계에서 타입 검사를 할 수 있도록 기반을 마련하는 역할은 한다.
+바인더는 타입 검사를 위해 심볼이라는 데이터 구조를 생성한다. + +심볼은 이전 단계의 AST에서 선언된 타입의 노드 정보를 저장한다. + +```ts +export interface Symbol { + flags: SymbolFlags; // Symbol flags + escapedName: string; // Name of Symbol + declarations?: Declaration[]; + // ... +} + +// src/compiler/types.ts +export const enum SymbolFlags { + NONE = 0, + FunctionScopedVariable = 1 << 0, // Variable (var) or parameter + BloackScopedVariable = 1 << 1, // A block-scoped variable (let or const) + Property = 1 << 2, // Property or enum member + EnumMember = 1 << 3, // Enum member + Function = 1 << 4, // Function + Class = 1 << 5, // Class + Interface = 1 << 6, // Interface + ... + .. + . +} +``` + +- flags 필드는 AST에서 선언된 타입의 노드 정보를 저장하는 식별자이다. +- 심볼 인터페이스의 declarations 필드는 AST 노드의 배열 형태를 보인다. + +> 바인더는 심볼을 생성하고 해당 심볼과 그에 대응하는 AST 노드를 연결하는 역할을 한다. + +```ts +type SomeType = string | number; +interface SomeInterface { + name: string; + age?: number; +} + +let foo: string = "LET"; +const obj = { + name: "이름", + age: 10, +}; + +class MyClass { + name; + age; + + constructor(name: string, age?: number) { + this.name = name; + this.age = age ?? 0; + } +} + +const arrowFunction = () => {}; +function func() {} + +arrowFunction(); +func(); + +const colin = new Class("colin"); +``` + + + +
+
+ +## 5) 체커(Checker)와 이미터(Emitter) + +### 체커? + +체커는 파서가 생성한 AST와 바인더가 생성한 심볼을 활용해 타입 검사를 수행한다. + +체커의 주요 역할은 AST의 노드를 탐색하면서
+심볼 정보를 불러와 주어진 소스 파일에 대해 타입 검사를 진행하는 것이다.
+체커의 타입 검사는 다음 컴파일 단계인 이미터에서 실행된다. + +checker.ts의 getDiagnostics() 함수를 사용해 타입을 검증하고
+타입 에러에 대한 정보를 보여줄 에러 메시지를 저장한다. + +### 이미터? + +타입스크립트 소스 파일을 변환하는 역할을 한다.
+즉, 타입스크립트 소스를 자바스크립트 파일(.js)과 타입 선언 파일(d.ts)로 생성한다. + +타입스크립트 소스 파일을 변환하는 과정에서
+개발자가 설정한 타입스크립트 설정파일을 읽어오고,
+체커를 통해 코드에 대한 타입 검증 정보를 가져온다. + +emitter.ts 소스 파일 내부의 emitFiles() 함수를 사용해
+타입스크립트 소스 변환을 진행한다. + +
+
+ +## 6) 타입스크립트의 컴파일 과정 + +1. tsc 명령어를 실행해 프로그램 객체가 컴파일 과정을 시작한다. +2. 스캐너는 소스 파일을 토큰 단위로 분리한다. +3. 파서는 토큰을 이용해 AST를 생성한다. +4. 바인더는 AST의 각 노드에 대응하는 심볼을 생성한다. 심볼은 선언된 타입의 노드 정보를 담고 있다. +5. 체커는 AST를 탐색하며 심볼 정보를 활용해 타입 검사를 수행한다. +6. 타입 검사 결과 에러가 없다면 이미터를 사용해서 자바스크립트 소스 파일로 변환한다. + +
+
diff --git "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.1_API_\354\232\224\354\262\255/seulgi.md" "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.1_API_\354\232\224\354\262\255/seulgi.md" index 3beced8..c697d7a 100644 --- "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.1_API_\354\232\224\354\262\255/seulgi.md" +++ "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.1_API_\354\232\224\354\262\255/seulgi.md" @@ -1 +1,365 @@ - +# 1. API 요청 + +## 1) fetch로 API 요청하기 + +장바구니 조회 기능을 만들기 위해 외부 데이터베이스에 접근해 +사용자가 장바구니에 추가한 정보를 호출하는 코드를 작성한다고 해보자. + +아래는 fetch 함수를 이용해 사용자가 담은 장바구니 물품 개수를 배지로 만든 예시이다. + +```tsx +const CartBadge = () => { + const [cartCount, setCartCount] = useState(0); + + useEffect(() => { + fetch("").then.((response) => response.json()).then(({cartItem} => { + setCartCount(cartItem.length); + })) + },[]); + + return ( + // 렌더링 로직 + ); +} +``` + +위의 경우처럼 구현해 다른 컴포넌트에서도 사용할 경우에
+ +백엔드에서 기능 변경을 해야해 API URL을 수정해야 한다면,
+이미 컴포넌트 내부 깊숙이 자리 잡은 비동기 호출 코드는 이런 변경 요구에 취약하다.
+
+그 외에도 커스텀 헤더, 타임아웃 설정과 같은 정책이 추가될 때마다 수정의 번거로움이 생긴다. + +
+
+ +## 2) 서비스 레이어로 분리하기 + +여러 API 변경으로 코드가 변경될 수 있다는 걸 감안해,
+비동기 호출 코드는 컴포넌트 영역에서 분리되 서비스 레이어에서 처리되어야 한다. + +단순히, fetch 함수 호출 부분을 서비스 레이어로,
+컴포넌트는 서비스 레이어 비동기 함수를 호출해 그 결과를 받아
+렌더링하는 흐름만으로는 API 요청 정책이 추가되는 것을 해결하기 어렵다. + +예를 들어, fetch함수에서 타임아웃을 설정하기 위해서는 다음 같이 구현해야 한다. + +```tsx +async function fetchCart() { + const controller = new AbortController(); + + const timeoutId = setTimeout(() => controller.abort(), 5000); + + const response = await fetch("url", { + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + return response; +} +``` + +
+
+ +## 3) Axios 활용하기 + +fetch는 내장 라이브러리라 설치 필요는 없지만, 많은 기능을 사용하려면 직접 구현해야 한다. +이런 번거로움 때문에 fetch 함수를 직접 쓰는 대신 Axios 라이브러리를 사용한다. + +```tsx +const apiRequester: AxiosInstance = axios.create({ + baseURL: "url", + timeout: 5000, +}); + +const fetchCart = (): AxiosPromise => + apiRequester.get("cart"); + +const postCart = ( + postCartRequest: PostCartRequest +): AxiosPromise => + apiRequester.post("cart", postCartRequest); +``` + +### API Entry가 2개 이상일 경우 + +```tsx +const apiRequester: AxiosInstance = axios.create(defaultConfig); + +const orderApiRequester: AxiosInstance = axios.create({ + baseURL: "url", + ...defaultConfig, +}); +const orderCartApiRequester: AxiosInstance = axios.create({ + baseURL: "url", + ...defaultConfig, +}); +``` + +각 서버의 기본 URL을 호출하도록 orderApiRequester, orderCartApiRequester 같이
+2개 이상의 API 요청을 처리하는 인스턴스를 따로 구성해야 한다. + +이후, 다른 URL로 서비스 코드를 호출할 때는 각각의 apiRequester를 사용하면 된다. + +
+
+ +## 4) Axios 인터셉터 사용하기 + +서로 다른 역할을 담당하는 다른 서버이기 때문에 +requester별로 다른 헤더를 설정해줘야 하는 로직이 필요할 수 있다. + +이때, 인터셉터 기능을 사용하여 requester에 따라 비동기 호출 내용을 추가해서 처리할 수 있다.
+또한 API 에러를 처리할 때 하나의 에러 객체로 묶어서 처리할 수도 있다. + +이와 달리 요청 옵션에 따라 다른 인터셉터를 만들기 위해 빌더 패턴을 추가하여 +APIBuilder 같은 클래스 형태로 구성하기도 한다. + +> **빌더 패턴(Builder Pattern)?**
+> 객체 생성을 더 편리하고 가독성 있게 만들기 위한 디자인 패턴 중 하나다.
+> 주로 복잡한 객체의 생성을 단순화하고, 객체 생성 과정을 분리하여 객체를 조립하는 방법을 제공한다. + +다만, 이런 APIBuilder 클래스는 보일러플레이트 코드가 많다는 단점을 갖고 있기 때문에
+옵션이 다양한 경우 인터셉터를 설정값에 따라 적용하고,
+필요 없는 인터셉터를 선택적으로 사용할 수 있다는 장점도 갖고 있다. + +
+
+ +## 5) API 응답 타입 지정하기 + +API 응답 값은 하나의 Response 타입으로 묶일 수 있다. + +```ts +interface Response { + data: T; + status: string; + serverDataTime: string; + errorCord?: string; + errorMessage?: string; +} + +const fetchCart = (): AxiosPromise => + apiRequester.get < Response < FetchCartResponse >> "cart"; + +const postCart = ( + postCartRequest: PostCartRequest +): AxiosPromise => + apiRequester.post>("cart", postCartRequest); +``` + +이와 같이 서버에서 오는 응답을 통일할 때 주의할 점은
+Response 타입을 apiRequester 내에서 처리한다면
+UPDATE, CREATE 같이 응답이 없을 수 있는 API 처리가 까다로워질 수 있다. + +따라서, Response 타입은 apiRequester가 모르게 관리되어야 하고,
+해당 값에 어떤 응답이 들어있는지 알 수 없거나 값의 형식이 달라지더라도
+로직에 영향을 주지 않는 경우에는 unknown 타입을 사용하여 알 수 없는 값임을 표현한다. + +```ts +interface response { + data: { + cartItems: CartItem[]; + forPass: unknown; + }; +} +``` + +다만, 이미 설계된 프로덕트에서 쓰고 있는 값이라면 +프로트 로직에서 써야 하는 값에 대해서만 타입을 선언한 다음에 사용하는 것이 좋다. + +```ts +type ForPass = { + type: "A" | "B" | "C"; +}; + +const isTargetValue = () => (data.forPass as ForPass).type === "A"; +``` + +
+
+ +## 6) 뷰 모델 사용하기 + +새로운 프로젝트는 서버 스펙이 자주 바뀌기 때문에 +뷰 모델을 사용하여 API 변경에 따른 범위를 한정해줘야 한다. + +```tsx +interface ListResponse { + items: ListItem[]; +} + +const fetchList = async (filter?: ListFetchFilter): Promise => { + const { data } = await api + .params({ filter }) + .get("/api/get-list") + .call>(); + + return { data }; +}; + +const ListPage = () => { + const [itemCount, setItemCount] = useState(0); + const [items, setItems] = useState([]); + + useEffect(() => { + fetchList(filter).then(({items}) => { + setItemCount(items.length); + setItems(items); + }) + },[]); + + return ( + // 렌더링 로직 + ); +}; +``` + +흔히 좋은 컴포넌트는 변경될 이유가 하나뿐인 컴퍼넌트라 한다. + +API응답의 items 인자를 좀 더 정확한 개념으로 나타내기 위해
+각 역할에 맞는 컴포넌트명으로 바꾼다면 해당 컴포넌트도 수정해야 하는데
+같은 API를 사용하는 기존 컴포넌트들 모두 수정해야하는 문제가 생긴다. + +이 문제를 해결하기 위한 방법으로 뷰 모델을 도입할 수 있다. + +API 응답에는 없는 ItemCount와 같은 도메인 개념을 넣을 때도
+백엔드나 UI에서 로직을 추가하여 처리할 필요 없이 간편하게 새로운 필드를 뷰 모델에 추가할 수 있다. + +### 뷰 모델에도 문제는 있다?! + +추상화 레이어 추가는 결국 코드를 복잡하게 만들며 레이어를 관리하고 개발하는 비용이 든다. + +```tsx +interface JobListResponse { + jobItems: JobListItemResponse[]; +} + +class JobListItem { + constructor(item: JobListItemResponse) { + // JobListItemResponse => JobListItem 객체로 변환 + } +} + +class JobList { + readonly itemCount: number; + readonly items: JobListItemResponse[]; + + constructor({ jobItems }: JobListResponse) { + this.itemCount = jobItem.length; + this.items = jobItems.map((item) => new JobListItem(item)); + } +} + +const fetchJobList = async ( + filter?: ListFetchFilter +): Promise => { + const { data } = await api + .params({ filter }) + .get("/api/get-list") + .call>(); + + return new JobList(data); +}; +``` + +### 도메인의 일관성 + 코드 수정 비용의 절충안을 찾자! + +1. 필요한 곳에만 뷰 모델을 부분적으로 만들어서 사용하기 +2. 백엔드와 클라이언트 개발자가 충분히 소통 후에 개발해 API 응답 변화 줄이기 +3. 뷰 모델에 필드를 추가하기 대신에 getter 등 함수를 추가해 실제 어떤 값이 뷰 모델에 추가한 값인지 알기 쉽게 하기 + +### AI로 찾아본 각 예시들 + +1. 필요한 곳에만 뷰 모델(ViewModel)을 부분적으로 만들어서 사용하기 + +- 전체 도메인 객체를 뷰에 억지로 끼워 맞추기보다는, 필요한 정보만 추려서 가볍게 사용하자. + +```ts +// 전체 도메인 모델 +interface User { + id: number; + name: string; + email: string; + birthday: string; + createdAt: string; + updatedAt: string; +} + +// 뷰 모델 (뷰에서 필요한 정보만 선택) +interface UserProfileViewModel { + name: string; + birthday: string; +} + +// 사용처에서 가볍게 매핑 +const toUserProfileViewModel = (user: User): UserProfileViewModel => ({ + name: user.name, + birthday: user.birthday, +}); +``` + +
+ +2. API 응답 스펙의 변경을 줄이기 위해 백엔드/프론트 간 소통 강화 + +- 프론트와 백엔드가 협의해서 “이건 프론트에서 계산할게요” 또는 “백엔드가 계산해서 내려줄게요” 협의한다고 가정하자. + +```ts +{ +"price": 1000, +"discount": 100 +} + +// 클라이언트에서 totalPrice 계산 +const totalPrice = price - discount; +``` + +- 백엔드에서 계산 요청하고 응답 스펙을 바꾸지 않음 (기존 유지) +- 프론트에서는 필요한 값을 뷰 모델에서 따로 계산 + +
+ +3. 뷰 모델에 필드를 직접 추가하지 말고 함수(getter)를 통해 표현 + +- 어떤 값이 기존 도메인에서 온 값인지, 아니면 UI용 가공값인지 명확히 하자. + +```ts +interface Product { + name: string; + price: number; + discount: number; +} + +// 뷰 모델 형태로 가공 +class ProductViewModel { + constructor(private product: Product) {} + + get name() { + return this.product.name; + } + + get price() { + return this.product.price; + } + + get finalPrice() { + return this.product.price - this.product.discount; + } + + // UI에서만 사용하는 값 + get isOnSale() { + return this.product.discount > 0; + } +} +``` + +- 이 방식은 실제 데이터는 그대로 두고, 추가된 값들이 어떤 용도인지 명확하게 드러나고, +- finalPrice, isOnSale은 UI 전용이라는 게 getter로 구분되어 이해하기 쉽다. + +> 다만, 타입스크립트는 정적 검사 도구라 런타임에 API 응답의 타입 오류를 방지하려면 Superstruct, zod같은 라이브러리를 사용하면 된다. + +
+
diff --git a/assets/CH06/kind_of_symbol.png b/assets/CH06/kind_of_symbol.png new file mode 100644 index 0000000..e124044 Binary files /dev/null and b/assets/CH06/kind_of_symbol.png differ