우아한 타입스크립트 with 리액트 - 컴파일타임에 메모리 공간을 차지할 값의 크기를 알 수 있다

연어코·2025년 10월 14일

기술 탐구

목록 보기
2/4
post-thumbnail

기초부터 실무까지 적용 가능한 팁들이 있어 입문할 때 도움되는 책이라 생각됩니다.

타입스크립트 문법만 보면 어떻게 활용할지 막막할 수 있는데, 우아한형제들의 실무 예제 코드가 있어 이렇게 짤 수도 있구나 싶어 힌트를 얻는 느낌이었습니다.


1) 들어가며

1.1 웹 개발의 역사

  1. 1995년에 넷스케이프의 브랜든 아이크는 웹의 다양한 콘텐츠를 표현하기 위한 새로운 언어가 필요하다고 생각하고 자바스크립트를 만들었다.

  2. 대규모 웹 서비스 개발의 필요성이 커지면서 하나의 웹 페이지를 통으로 개발하는 것이 아니라, 컴포넌트 단위로 개발하는 방식이 생겨났다.

  3. 또한 Ajax로 페이지 전체를 새로고침하지 않아도 자바스크립트의 비동기 요청을 사용해서 페이지의 일부 데이터를 로드할 수 있게 되었다.

...자세히 보기

1.2 자바스크립트의 한계

  1. 자바스크립트는 동적 타입 언어로, 개발자 의도와는 다르게 동작할 수 있다.

  2. 개발자들은 자바스크립트 인터페이스의 필요성을 느끼게 되었고 JSDoc, propTypes, 다트 같은 해결 방안이 등장했다.

  3. 3가지 모두 의미 있는 해결 방안이었으나 자바스크립트 스스로가 인터페이스를 기술할 수 있는 언어로 발전해야 한다는 목소리가 더욱 커졌다. 따라서 자바스크립트의 슈퍼셋인 타입스크립트가 등장했다.

...자세히 보기

2) 타입

2.1 타입이란

  1. 특정 메모리에 값을 효율적으로 저장하기 위해서는 먼저 해당 메모리 공간을 차지할 값의 크기를 알아야 한다.

  2. 개발자는 타입을 사용해서 값의 종류를 명시할 수 있고 메모리를 더욱 효율적으로 사용할 수 있다.

  3. 타입은 값이 가질 수 있는 유효한 범위의 집합을 말한다.

...자세히 보기

2.2 타입스크립트의 타입 시스템

  1. 이름이 다른 객체라도 가진 속성이 동일하다면 타입스크립트는 서로 호환이 가능한 동일한 타입으로 여긴다.

  2. 타입스크립트 문법인 type으로 선언한 내용은 자바스크립트 런타임에서 제거되기 때문에 값 공간과 타입 공간은 서로 충돌하지 않는다.

  3. 값이 사용되는 위치와 타입이 사용되는 위치가 다르기 때문에, 코드가 어디에서 사용되었는지에 따라 타입인지 값인지를 추론할 수 있다.

...자세히 보기

2.3 원시 타입

  1. 자바스크립트에서 값은 타입을 가지지만 변수는 별도의 타입을 가지지 않는다.

  2. 타입스크립트는 이 변수에 타입을 지정할 수 있는 타입 시스템 체계를 구축한다.

  3. 자바스크립트의 7가지 원시 값은 타입스크립트에서 원시 타입으로 존재한다. boolean, undefined, null, number, bigInt, string, symbol

...자세히 보기

2.4 객체 타입

  1. 7가지 원시 타입에 속하지 않는 값은 모두 객체 타입으로 분류할 수 있다.

  2. 흔히 객체를 타이핑하기 위해 자주 사용하는 키워드로 type과 interface가 있다.

  3. 함수 자체의 타입은 어떻게 지정할 수 있을까? 호출 시그니처를 정의하는 방식을 사용하면 된다.

...자세히 보기

3) 고급 타입

3.1 타입스크립트만의 독자적 타입 시스템

  1. unknown 타입으로 할당된 변수는 어떤 값이든 올 수 있음을 의미하는 동시에, 타입이 식별된 후에 사용할 수 있기 때문에 any 타입보다 더 안전하다.

  2. 튜플은 배열 타입의 하위 타입으로 기존 타입스크립트의 배열 기능에 길이 제한까지 추가한 타입 시스템이다.

  3. 열거형(enum)은 관련이 높은 멤버를 모아 문자열 상수처럼 사용하고자 할 때 유용하게 쓸 수 있다.

...자세히 보기

3.2 타입 조합

  1. 교차 타입(Intersection)은 기존에 존재하는 다른 타입들을 합쳐서 해당 타입의 모든 멤버를 가지는 새로운 타입을 생성하는 것이다. A & B

  2. 유니온 타입(Union)은 주로 특정 변수가 가질 수 있는 타입을 전부 나열하는 용도로 사용된다. A | B

  3. 인덱스 시그니처(Index Signatures)는 특정 타입의 속성 이름은 알 수 없지만 속성값의 타입을 알고 있을 때 사용하는 문법이다. [Key: K]: T

...자세히 보기

3.3 제네릭 사용법

  1. 제네릭을 사용하면 함수, 타입, 클래스 등 여러 타입에 대해 하나하나 따로 정의하지 않아도 되기 때문에 재사용성이 크게 향상된다.

  2. string 타입으로 제약하려면 타입 매개변수는 특정 타입을 상속(extends)해야 한다. <Key extends string>

  3. 우아한형제들에서는 API 응답 값의 타입을 지정할 때 제네릭을 활용하여 적절한 타입 추론과 코드의 재사용성을 높이고 있다.

...자세히 보기

4) 타입 확장하기·좁히기

4.1 타입 확장하기

  1. 기본적으로 타입스크립트에서는 interface와 type 키워드를 사용해서 타입을 정의하고 extends, 교차 타입, 유니온 타입을 사용하여 타입을 확장한다.

  2. 타입 확장은 중복 제거, 명시적인 코드 작성 외에도 확장성이란 장점을 가지고 있다.

  3. 결과적으로 주어진 타입에 무분별하게 속성을 추가하여 사용하는 것보다 타입을 확장해서 사용하는 것이 좋다.

...자세히 보기

4.2 타입 좁히기 - 타입 가드

  1. 타입 가드는 크게 자바스크립트 연산자를 사용한 타입 가드와 사용자 정의 타입 가드로 구분할 수 있다.

  2. typeof 연산자를 주로 원시 타입을 판별하는 데 사용한다면, instanceof 연산자는 인스턴스화된 객체 타입을 판별하는 타입 가드로 사용할 수 있다.

  3. 반환 값의 타입을 x is DestinationCode로 알려줌으로써 타입스크립트는 if문 스코프의 str 타입을 DestinationCode로 추론할 수 있게 된다.

...자세히 보기

4.3 타입 좁히기 - 식별할 수 있는 유니온

  1. 종종 태그된 유니온(Tagged Union)으로도 불리는 식별할 수 있는 유니온(Discriminated Unions)은 타입 좁히기에 널리 사용되는 방식이다.

  2. 식별할 수 있는 유니온이란 타입 간의 구조 호환을 막기 위해 타입마다 구분할 수 있는 판별자(discriminant)를 달아주어 포함 관계를 제거하는 것이다.

  3. 식별할 수 있는 유니온의 판별자는 유닛 타입(unit type)으로 선언되어야 정상적으로 동작한다.

...자세히 보기

4.4 Exhaustiveness Checking으로 정확한 타입 분기 유지하기

  1. 모든 케이스에 대한 타입 분기 처리를 해주지 않았을 때, 컴파일타임 에러가 발생하게 하는 것을 Exhaustiveness Checking이라고 한다.

  2. exhaustiveCheck라는 함수는 매개변수를 never 타입으로 선언하고 있다. 즉, 매개변수에 그 어떤 값도 받을 수 없으며 만일 값이 들어온다면 에러를 내뱉는다.

  3. 이 함수를 타입 처리 조건문의 마지막 else문에 사용하면 앞의 조건문에서 모든 타입에 대한 분기 처리를 강제할 수 있다.

...자세히 보기

5) 타입 활용하기

5.1 조건부 타입

  1. 삼항 연산자를 사용한 조건문의 형태를 가지는데, extends로 조건을 서술하고 infer로 타입을 추론하는 방식을 취한다.

  2. 인자에 따라 반환되는 타입을 다르게 설정하고 싶다면 extends를 사용한 조건부 타입을 활용하면 된다. T extends U ? X : Y

  3. Promise<infer K>는 Promise의 반환 값을 추론해 해당 값의 타입을 K로 한다는 의미이다.

...자세히 보기

5.2 템플릿 리터럴 타입 활용하기

  1. 템플릿 리터럴 타입은 자바스크립트의 템플릿 리터럴 문법을 사용해 특정 문자열에 대한 타입을 선언할 수 있는 기능이다.

  2. 템플릿 리터럴 타입을 사용하면 더욱 읽기 쉬운 코드로 작성할 수 있게 되며, 코드를 재사용하고 수정하는 데 용이한 타입을 선언할 수 있다.

  3. 템플릿 리터럴 타입에 삽입된 유니온 조합의 경우의 수가 너무 많지 않게 적절하게 나누어 타입을 정의하는 것이 좋다.

...자세히 보기

5.3 커스텀 유틸리티 타입 활용하기

  1. StyledProps를 따로 정의하려면 Props와 똑같은 타입임에도 새로 작성해야 하므로 불가피하게 중복된 코드가 생긴다. 이런 문제를 Pick, Omit 같은 유틸리티 타입으로 개선할 수 있다.

  2. 유틸리티 타입만으로는 원하는 타입을 추출하기 어려울 때 커스텀 유틸리티 타입을 구현한다.

  3. 선택하고자 하는 하나의 속성을 제외한 나머지 값을 옵셔널 타입 + undefined로 설정하면 원하고자 하는 속성만 받도록 구현할 수 있다.

...자세히 보기

5.4 불변 객체 타입으로 활용하기

  1. keyof, as const로 객체 타입을 구체적으로 설정하면 타입에 맞지 않는 값을 전달할 경우 타입 에러가 반환되기 때문에 컴파일 단계에서 발생할 수 있는 실수를 방지할 수 있다.

  2. 자바스크립트에서는 typeof가 타입을 추출하기 위한 연산자로 사용된다면, 타입스크립트에서는 typeof가 변수 혹은 속성의 타입을 추론하는 역할을 한다.

  3. theme뿐만 아니라 여러 상숫값을 인자나 props로 받은 다음에 객체의 키값을 추출한 타입을 활용하면 객체에 접근할 때 타입스크립트의 도움을 받아 실수를 방지할 수 있다.

...자세히 보기

5.5 Record 원시 타입 키 개선하기

  1. 키가 유한한 집합이라면 유닛 타입(다른 타입으로 쪼개지지 않고 오직 하나의 정확한 값을 가지는 타입)을 사용할 수 있다.

  2. 키가 무한한 상황에서는 Partial을 사용하여 해당 값이 undefined일 수 있는 상태임을 표현할 수 있다.

  3. 개발자는 안내를 보고 옵셔널 체이닝을 사용하거나 조건문을 사용하는 등 사전에 조치할 수 있게 되어 예상치 못한 런타임 오류를 줄일 수 있다.

...자세히 보기

6) 타입스크립트 컴파일

6.1 자바스크립트의 런타임과 타입스크립트의 컴파일

  1. 소스코드의 컴파일이 완료되면 프로그램이 메모리에 적재되어 실행되는데 이 시간을 런타임이라고 한다.

  2. 일반적으로 컴파일은 추상화 단계가 다른 고수준 언어에서 저수준 언어로 변환되는 과정을 가리킨다.

  3. 타입스크립트는 고수준 언어가 저수준 언어로 변환되는 것이 아니라 고수준 언어(타입스크립트)가 또 다른 고수준 언어(자바스크립트)로 변환되는 것이기 때문에 컴파일이 아닌 트랜스파일이라고 부르기도 한다.

...자세히 보기

6.2 타입스크립트 컴파일러의 동작

  1. 타입스크립트 컴파일러의 역할을 크게 2가지로 나눌 수 있다. 1) 코드의 타입 오류를 검사한다. 2) 최신 버전의 타입스크립트·자바스크립트 코드를 구버전의 자바스크립트로 트랜스파일한다.

  2. 타입스크립트 컴파일러의 target 옵션을 사용하여 특정 버전의 자바스크립트 소스코드로 컴파일할 수 있다.

  3. 타입스크립트 코드가 자바스크립트로 컴파일되는 과정에서 모든 인터페이스, 타입, 타입 구문이 제거되어 버리기 때문에 런타임에서는 타입을 사용할 수 없다.

...자세히 보기

6.3 타입스크립트 컴파일러의 구조

  1. 스캐너가 소스 파일을 어휘적으로 분석(lexical analysis)하여 토큰으로 나눠주면 파서는 그 토큰 정보를 이용하여 AST를 생성한다.

  2. 바인더는 심볼을 생성하고 해당 심볼과 그에 대응하는 AST 노드를 연결하는 역할을 수행한다. 체커는 파서가 생성한 AST와 바인더가 생성한 심볼을 활용하여 타입 검사를 수행한다.

  3. 이미터는 타입스크립트 소스를 자바스크립트(js) 파일과 타입 선언 파일(d.ts)로 생성한다.

...자세히 보기

7) 비동기 호출

7.1 API 요청

  1. 여러 API 요청 정책이 추가되어 코드가 변경될 수 있다는 것을 감안한다면, 비동기 호출 코드는 컴포넌트 영역에서 분리되어 다른 영역(서비스 레이어)에서 처리되어야 한다.

  2. 같은 서버에서 오는 응답의 형태는 대체로 통일되어 있어서 API의 응답 값은 하나의 Response 타입으로 묶일 수 있다.

  3. 뷰 모델을 만들면 API 응답이 바뀌어도 UI가 깨지지 않게 개발할 수 있다.

...자세히 보기

7.2 API 상태 관리하기

  1. 비동기 API 호출하기 위해서는 API 성공·실패에 따른 상태가 관리되어야 하므로 상태 관리 라이브러리의 액션이나 훅과 같이 재정의된 형태를 사용해야 한다.

  2. 상태 관리 라이브러리에서는 비동기로 상태를 변경하는 코드가 점점 추가되면 전역 상태 관리 스토어가 비대해진다.

  3. react-query나 useSwr 같은 훅은 캐시를 사용하여 비동기 함수를 호출하며, 상태 관리 라이브러리에서 발생했던 의도치 않은 상태 변경을 방지하는 데 도움이 된다.

...자세히 보기

7.3 API 에러 핸들링

  1. isAxiosError 타입 가드를 직접 사용할 수도 있지만, 서버 에러임을 명확하게 표시하고 서버에서 내려주는 에러 응답 객체에 대해서도 구체적으로 정의함으로써 에러 객체가 어떤 속성을 가졌는지를 파악할 수 있다.

  2. 실제 요청을 처리할 때 단순한 서버 에러도 발생하지만 인증 정보 에러, 네트워크 에러, 타임아웃 에러 같은 다양한 에러가 발생하기도 한다. 이를 더욱 명시적으로 표시하기 위해 서브클래싱을 활용할 수 있다.

  3. 서브클래싱을 활용하면 에러 인스턴스가 무엇인지에 따라 에러 처리 방식을 다르게 구현할 수 있다.

...자세히 보기

7.4 API 모킹

  1. 프론트엔드 개발을 하다보면 서버 API가 완성되기 전에 개발을 진행해야 하는 일이 종종 생긴다.

  2. 이럴 때 모킹(Mocking)이라는 방법을 활용할 수 있다.

  3. 플래그를 사용하여 목업으로 개발할 때와 개발하지 않을 때를 구분하면 프로덕션에서 사용되는 코드와 목업을 위한 코드를 분리할 필요가 없다.

...자세히 보기

8) JSX에서 TSX로

8.1 리액트 컴포넌트의 타입

  1. ReactElement 타입은 JSX의 createElement 메서드 호출로 생성된 리액트 컴포넌트를 객체 형태로 저장하기 위한 포맷이다. JSX.Element는 ReactElement의 특정 타입으로 props와 타입 필드를 any로 가지는 타입이다.

  2. JSX 형태의 문법을 때로는 string, number, null, undefined같이 어떤 타입이든 children prop으로 지정할 수 있게 하고 싶다면 ReactNode 타입으로 children을 선언하면 된다.

  3. HTML 속성을 확장하는 props를 설계할 때는 ComponentPropsWithoutRef 타입을 사용하여 ref가 실제로 forwardRef와 함께 사용될 때만 props로 전달되도록 타입을 정의하는 것이 안전하다.

...자세히 보기

8.2 타입스크립트로 리액트 컴포넌트 만들기

  1. JSDocs를 활용하면 각 속성의 대략적인 타입과 어떤 역할을 하는지 파악할 수 있지만, options가 어떤 형식의 객체를 나타내는지나 onChange의 매개변수 및 반환 값에 대한 구체적인 정보를 알기 쉽지 않아서 잘못된 타입이 전달될 수 있는 위험이 있다.

  2. 이러한 문제를 해결하기 위해 타입스크립트를 사용하여 좀 더 정교하고 구체적인 타입을 지정할 수 있다.

  3. className, id와 같은 리액트 컴포넌트의 기본 props를 추가하려면 SelectProps에 직접 className?: string; id?: string;을 넣어도 되지만 리액트에서 제공하는 타입을 사용하면 더 정확한 타입을 설정할 수 있다.

...자세히 보기

9) 훅

9.1 리액트 훅

  1. useLayoutEffect를 사용하면 useEffect와 달리 화면에 해당 컴포넌트가 그려지기 전에 콜백 함수를 실행하기 때문에 첫 번째 렌더링 때 빈 이름이 뜨는 경우를 방지할 수 있다.

  2. useMemo와 useCallback 모두 이전에 생성된 값 또는 함수를 기억하며, 동일한 값과 함수를 반복해서 생성하지 않도록 해주는 훅이다.

  3. useRef로 관리되는 변수는 값이 바뀌어도 컴포넌트의 리렌더링이 발생하지 않으며 값을 설정한 후 즉시 조회할 수 있다.

...자세히 보기

9.2 커스텀 훅

  1. 리액트에서 기본적으로 제공하는 useState, useRef 등의 훅에 더해, 사용자 정의 훅을 생성하여 컴포넌트 로직을 함수로 뽑아내 재사용할 수 있다.

...자세히 보기

10) 상태 관리

10.1 상태 관리

  1. 리액트 애플리케이션에서의 상태는 렌더링에 영향을 줄 수 있는 동적인 데이터 값을 말한다.

  2. 리액트 앱 내의 상태는 지역 상태, 전역 상태, 서버 상태로 분류할 수 있다.

  3. 어떤 값을 상태로 정의할 때는 다음 2가지 사항을 고려해야 한다. 1) 시간이 지나도 변하지 않는다면 상태가 아니다. 2) 파생된 값은 상태가 아니다.

...자세히 보기

10.2 상태 관리 라이브러리

  1. 객체 지향 스타일로 코드를 작성하는 데 익숙하다면 MobX를 사용하는 것을 추천한다. Redux는 단순한 상태 설정에도 많은 보일러플레이트가 필요하고, 사용 난도가 높다는 단점이 있다.

  2. Recoil은 상태를 저장할 수 있는 Atom과 해당 상태를 변형할 수 있는 순수 함수 selector를 통해 상태를 관리하는 라이브러리다.

  3. Zustand는 클로저를 활용하여 스토어 내부 상태를 관리함으로써 특정 라이브러리에 종속되지 않는 특징이 있다.

...자세히 보기

11) CSS-in-JS

11.1 CSS-in-JS란

  1. 인라인 스타일은 DOM 노드에 속성으로 스타일을 추가한 반면에 CSS-in-JS는 DOM 상단에 <style> 태그를 추가했다.

  2. CSS-in-JS를 사용하면 실제로 CSS가 생성되기 때문에 미디어 쿼리, 슈도 선택자 등과 같은 CSS 기능을 손쉽게 누릴 수 있다.

  3. CSS-in-JS를 적용하기 위해서는 별도의 라이브러리를 설치해야 하고 런타임에 스타일을 생성하기 위한 동작이 필요하기 때문에 CSS-in-CSS에 비해 성능적인 측면에서 뒤떨어질 수도 있다.

...자세히 보기

11.2 유틸리티 함수를 활용하여 styled-components의 중복 타입 선언 피하기

  1. props에서 받은 타입을 styled-components로 넘겨서 활용할 때는 Pick, Omit 같은 유틸리티 타입을 활용할 수 있다.

  2. 상속받는 컴포넌트나 부모 컴포넌트에서 자식 컴포넌트로 넘겨주는 props 등의 경우에도 Pick이나 Omit 같은 유틸리티 타입을 활용하면 중복되는 타입을 피할 수 있어 유지보수적인 측면에서 긍정적인 효과를 얻을 수 있다.

...자세히 보기

12) 타입스크립트 프로젝트 관리

12.1 앰비언트 타입 활용하기

  1. 값을 포함하는 일반적인 선언과 구별하기 위해 .d.ts 확장자를 가진 파일에서 하는 타입 선언을 앰비언트 타입 선언이라고 부른다.

  2. 앰비언트 타입 선언으로 값을 정의할 수는 없지만 declare라는 키워드를 사용하여 어딘가에 자바스크립트 값이 존재한다는 사실을 선언할 수 있다.

  3. 타입스크립트로 작성된 라이브러리일지라도 자바스크립트 파일과 .d.ts 파일로 배포되는 것이 일반적이다.

...자세히 보기

12.2 스크립트와 설정 파일 활용하기

  1. 다음 스크립트를 사용하여 실시간으로 에러를 확인할 수 있다. yarn tsc --noEmit --incremental -w

  2. 프로젝트의 모든 부분이 타입스크립트 통제하에 돌아가고 있는지를 정량적으로 판단하기 위해 다음과 같은 스크립트를 사용할 수 있다. npx type-coverage --detail

  3. tsconfig의 incremental 속성을 true로 설정하면 증분 컴파일이 활성화되어 매번 모든 대상을 컴파일하는 것이 아니라 변경된 부분만 컴파일하게 된다.

...자세히 보기

12.3 타입스크립트 마이그레이션

  1. 타입스크립트 개발 환경을 설정하고, 빌드 파이프라인에 타입스크립트 컴파일러를 통합한다.

  2. 타입을 점진적으로 추가하는 과정에서는 오류가 발생하지 않도록 tsconfig.json 파일에서 allowJS를 true로 noImplicitAny를 false로 설정해야 한다.

  3. 기존 자바스크립트 파일을 모두 타입스크립트로 변환하는 작업이 완료되었다면 tsconfig.json 파일을 수정한다.

...자세히 보기

12.4 모노레포

  1. 여러 프로젝트를 관리하는 경우에는 일반적으로 개별 프로젝트마다 별도의 레포지토리를 생성하여 관리한다.

  2. 하지만 공통된 요소를 찾아낼 수 있다면 이를 통합하여 조금 더 효율적으로 관리할 수 있다.

  3. 이러한 상황에서 모노레포를 사용하면 개발 환경 설정도 통합할 수 있어서 더 효율적인 관리가 가능해진다.

...자세히 보기

13) 타입스크립트와 객체 지향

13.1 타입스크립트의 객체 지향

  1. 객체 지향을 따르기 위해서는 객체 간의 협력과 역할에 집중해야 한다.

  2. 컴포넌트 간의 협력 관계를 표현하는 것이 prop이다.

  3. 특히 레이아웃은 예상치 못한 변동 사항이 생길 가능성이 높기 때문에 미확정 영역(gray area)으로 두고 공통으로 사용되는 컴포넌트와 비즈니스 영역에서 객체 지향 원칙을 적용하여 설계하면 좋은 구조를 개발할 수 있을 것이다.

...자세히 보기

13.2 우아한형제들의 활용 방식

  1. 우아한형제들의 한 팀에서는 다음과 같은 설계 방식을 사용한다. 1) 컴포넌트 영역, 2) 커스텀 훅 영역, 3) 모델 영역, 4) API 레이어 영역

  2. 클래스는 객체를 표현하는 방법의 도구일 뿐이다. 컴포넌트를 함수형으로 선언하든 클래스형으로 선언하든 모두 객체를 나타낸다.

  3. 애플리케이션의 설계는 트레이드오프의 결과물이며 우리는 둘 중 상황에 맞는 적절한 방법을 선택해야 한다.

...자세히 보기

13.3 캡슐화와 추상화

  1. 캡슐화란 다른 객체 내부의 데이터를 꺼내와서 직접 다루지 않고, 해당 객체에게 처리할 행위를 따로 요청함으로써 협력하는 것이다.

  2. 결국 컴포넌트 내의 상태와 prop을 잘 다루는 것도 캡슐화의 개념에 부합하는 것이다.

  3. 객체 지향 패러다임에 매몰되기보다는 어떻게 하면 더 유기적인 협력 관계를 만들어낼 수 있을지, 명확하게 도메인을 분리할 수 있을지에 집중해보자.

...자세히 보기

전체 요약본 보러가기
원문: https://product.kyobobook.co.kr/detail/S000210716282

profile
Invisible Treasure

0개의 댓글