[Day 41] TypeScript 기본 문법 (4)

송히·2023년 11월 14일
post-thumbnail

Today I Learn📖

  • 모듈의 타입 선언 (강의)
  • 타입 가져오기 / 내보내기 (강의)
  • tsconfig.json 옵션 (강의)
  • 내장 유틸리티 타입 (강의)

모듈의 타입 선언

외부 패키지의 타입 선언

  • TS에서 타입스크립트로 만들어진 게 아닌 패키지를 사용하려면 타입 정의 파일을 따로 선언해줘야 함
    -> ex) lodash 등의 자바스크립트로 된 외부 라이브러리
    1. 패키지 설치할 때 npm i @types/패키지명 -D을 통해 타입 정의 파일도 같이 설치
      => node_modules - @types 안에 설치됨
    2. 패키지명.d.ts 파일을 생성해 사용하려는 패키지에 대한 타입 정보를 따로 알려주기

  • .d.ts파일: 타입 선언 전용 파일, 로직 없이 타입 정보만 정의함
    -> declare를 사용해서 타입 선언
    => 패키지의 타입 선언 / 전역 타입 선언 용도

    declare const version: string
    declare function greet(name: string): void
    
    // myPackage.d.ts -> 타입 정보가 없는 외부 모듈의 타입 선언
    declare module "myPackage" {
      export function doSomething(x: number): string
    }
    
  • 모듈 import 하려는데 기본 내보내기 방식이 아닐 경우

    1. 와일드카드 사용: CommonJS 방식 모듈을 import * as로 가져올 수 있음
    2. tsconfig.json의 컴파일 옵션에 "esModuleInterop": true 옵션 사용 (보편적인 방법)
    import _ from '패키지명' // 에러 발생 -> 기본 내보내기가 아니기 때문
    
    import * as _ from '패키지명' // 와일드 카드
    
    // tsconfig.json
    {
      "compilerOptions": {
        "esModuleInterop": true
      }
    }
    
    import _ from '패키지명' // 컴파일 옵션 추가시 사용 가능
    
  • lodash 패키지
    : 반복문, 배열 정리, 객체 다루기 등을 더 짧고 쉽게 할 수 있는 함수 라이브러리
import _ from 'lodash'; // 패키지 설치 후 불러오기
⠀ 
_.camelCase('I love Coffee~') // iLoveCoffee
_.uniq([1, 2, 3, 2, 2, 4]); // [1, 2, 3, 4] (배열 중복 제거)
_.cloneDeep(obj); // 객체 깊은 복사
_.isEmpty([]); // true (배열이 비었는지 확인)
_.debounce(func, 300); // 디바운스 함수

해당 패키지가 타입 선언 파일이 존재하는지 확인하는 방법
1. 구글에 패키지명 npm 검색
2. 결과 중 npmjs.com 사이트 접속
3. 패키지명 옆에 버튼 확인
-> DT 있으면 자바스크립트로 만들어졌지만 타입 선언 파일도 존재하는 것
-> TS 있으면 애초에 타입스크립트로 만들어진 것 (타입 에러 발생 X)


프로젝트 내부에 존재하는 JS 모듈의 타입 선언

  • allowJs: true옵션 추가: TS 프로젝트 안에서 JS 파일도 인식하도록 설정
    -> JS에는 타입 정보가 없기 때문에 기본적으로 any로 추론됨
    => .d.ts 파일을 통해 타입 보강 필요

    // tsconfig.json
    {
      "compilerOptions": {
        "allowJs": true
      }
    }
    
  • d.ts파일 생성 방법

    1. JS 모듈과 같은 파일에 JS모듈파일명.d.ts 파일 생성

    2. types 파일 생성 -> 그 안에 d.ts 파일 생성
      -> tsconfig.json에 "paths": [] 옵션으로 경로 설정
      -> 그 이름을 전역적으로 사용 (상세 위치 없이)

      // tsconfig.json
      {
        "compilerOptions": {
          "paths": {
            "myUtils": ["./js/myUtils.js"] // myUtils라는 이름으로 []에 들어있는 위치에서 파일을 참조하겠다는 뜻
          }
        }
      }
      
      // import 할 때
      import utils, { add, subtract } from 'myUtils' // myUtils라는 경로별칭으로 그냥 사용
      
      // myUtils.d.ts
      declare module 'myUtils' { // 모듈 불러오듯 declare module 사용
        // 내용 작성
        interface Add {
          (a: number, b: number): number
        }
        export const add: Add
      }

타입 가져오기 / 내보내기

  • 타입 내보내기도 마찬가지로 앞에 export 붙임
  • 타입 가져올 때는 명시적으로 import type {} from "./"으로 쓸 수 있음
// myUtils.ts
export interface Add { // 타입 내보내기
  (a: number, b: number): number
}
export interface Subtract { // 타입 내보내기
  (a: number, b: number): number
}
export const add: Add = (a, b) => a + b // 값 내보내기
export const subtract = (a: number, b: number) => a - b // : Subtract를 풀어쓴 것, add: Add처럼 타입으로 갖다써도 됨

//main.ts
import { add, subtract } from './myUtils' // 데이터 가져오기
import type { Add, Subtract } from './myUtils' // 타입 가져오기 -> 직관적으로 타입임을 명시
// import { add, subtract, Add, Subtract } from './myUtils' 처럼 하나로 써도 되긴 함

const a = add(4, 7)
const b = subtract(9, 6)

const newAdd: Add = (x, y) => x + y // 가져온 타입 이용해서 데이터 생성
newAdd(1, 2)
const newSubtract: Subtract = (x, y) => x - y
newSubtract(1, 2)

import할 때 .ts 확장자를 명시적으로 안 붙이는 이유
: ES 모듈 방식과 호환되게 하기 위해 확장자를 생략
-> 명시적으로 확장자를 지정해버리면 파일을 탐색할 때 그 확장자만 찾게 되기 때문 (지정 안 하면 여러 가지를 시도함)


  • namespace: 여러 타입, 함수 등을 이름으로 묶어 그룹화하는 문법
    -> 이름의 오염을 막기 위해 사용 (옛날 방법)
// myUtils.ts
export namespace MyUtils { // MyUtils라는 네임스페이스 지정 후 내보내기
  export interface Result { // 네임스페이스 안에서 내보내기 할 기능은 또 export 붙여야함
    value: number
  }
  export function add(a: number, b: number): number {
    return a + b
  }
  export interface Subtract { 
    (a: number, b: number): number
  }
}

export const subtract: MyUtils.Subtract = (a, b) => a + b // Subtract 타입이 MyUtils 안에 있으니까 앞에 써야함

// main.ts
import { subtract } from './myUtils' // 데이터 가져오기
import type { MyUtils } from './myUtils' // 타입 가져오기

const newSubtract: MyUtils.Subtract = (x, y) => x - y // 네임스페이스를 앞에 써줘야함
newSubtract(1, 2) // → -1

tsconfig.json 옵션

{
  "compilerOptions": {
    "strict": true, // 엄격한 타입 검사, 기본 false, TS는 true로 써야함
    // 아래 옵션들이 전부 적용됨
	// noImplicitAny: 암시적 any 금지
	// noImplicitThis: 암시적 this 금지
	// strictNullChecks: 엄격한 null, undefined 체크
	// strictFunctionTypes: 엄격한 함수 매개변수 타입 검사 검사
	// strictPropertyInitialization: 엄격한 클래스 속성 초기화 검사
	// strictBindCallApply: bind, call, apply 메서드 인수 검사
    "target": "ES2015", // 컴파일될 ES(JS) 버전
    // 권장: `ES2015`
	// 선택: `ES5`, `ES6` / `ES2015`, `ES2016`, `ESNext`, ...
    "lib": ["ESNext", "DOM", "DOM.Iterable"], // 컴파일에서 사용할 라이브러리
	// `target` 옵션에 따라 자동으로 지정됨
	// 추천: `["ESNext", "DOM", "DOM.Iterable"]`
    "module": "ESNext", // 사용할 모듈 방식
	// 선택: `CommonJS`, `ES6` / `ES2015`, `ES2020`, `ESNext`
	// `ES2020`: `import()`, `import.meta.url` 등 사용 가능
	// `ESNext`: 가장 최신의 모듈 방식 사용
    "moduleResolution": "bundler", // 컴파일러가 사용할 모듈 해석 방식
	// 추천: `node` / `bundler` (최신은 bundler, 이전은 node 많이 사용)
	// 선택: `classic`, `node`, `nodenext`, `bundler` 등
	// `nodenext`: Node.js의 ES 모듈 지원
	// `bundler`: Vite, esbuild, Webpack, Parcel 등 최신 번들러를 사용하는 경우 (>=5.0)
    "paths": { // 경로 별칭 지정
      "@utils/*": ["src/utils/*"] // 별칭: [위치]
    }
    "jsx": "react-jsx", // JSX 처리 방식
	// `react-jsx`: JSX 변경, `.js` 파일로 출력 (`_jsx`), React@17 이상
	// `react-jsxdev`: JSX 변경, `.js` 파일로 출력 (`_jsxDEV`), React@17 이상
	// `react`: JSX 변경, `.js` 파일로 출력 (`React.createElement`)
	// `react-native`: JSX 변경 없이, `.js` 파일로 출력
	// `preserve`: JSX 변경 없이, `.jsx` 파일로 출력
    "outDir": "dist", // 컴파일된 JS 파일 저장 위치
  },

  "files": ["./files/index.ts"], // 우선순위 1. 꼭 포함할 파일
  "excludes": ["./node_modules", "./dist"], // 우선순위 2. 제외할 파일 및 폴더
  "include": ["./src/**/*.ts"], // 우선순위 3. 포함할 파일 및 폴더
  "extends": ["./config/tsconfig.base.json"] // 우선순위 4. 확장할 compilerOptions (같은 내용 있으면 기존 compilerOptions이 우선됨)
}

개발의존성 vs 일반의존성

  • 개발의존성 (-D) : 개발자가 코드 작성할 때 필요한 기능 ex) eslint, webpack, vite, ...
    -> 코드 작성 / 빌드 / 테스트에만 필요하므로 배포시 포함 X
  • 일반의존성: 사용자가 서비스 사용할 때 필요한 기능 ex) react, axios, lodash, ...
    -> 코드 실행, 서비스 동작에 필요하기 때문에 배포시 포함됨

내장 유틸리티 타입

타입스크립트가 가지고 있는 타입 변형 / 재구성 도구
-> 여러 유틸리티 타입을 조합해서 사용 가능


  • Partial<T>: 모든 속성을 선택적(Optional)으로 바꿔줌

    type A = { name: string; age: number }
    type B = Partial<A>  // { name?: string; age?: number }
  • Required<T>: 모든 속성을 필수(Required)로 바꿔줌

    type A = { name?: string; age?: number }
    type B = Required<A>  // { name: string; age: number }
  • Readonly<T>: 모든 속성을 읽기 전용(Readonly)으로 바꿔줌

    interface User { name: string }
    
    const A: User = { name: "A" }
    const A: Readonly<User> = { name: "B" }
    
    B.name = "BB" // 에러 발생 -> 읽기 전용이니까 수정 안 됨
  • Record<K, T>: K에 해당하는 key들이 T 타입의 속성들을 가지는 객체 타입 생성

    type A = Record<'a' | 'b', number>  // { a: number; b: number }
  • Pick<T, K>: 타입 T에서 K에 해당하는 속성만 골라서 새로운 타입 생성

    type A = { name: string; age: number; email: string }
    type B = Pick<A, 'name' | 'email'>  // { name: string; email: string }
    
  • Omit<T, K>: 타입 T에서 K에 해당하는 속성을 제외한 새로운 타입 생성

    type A = { name: string; age: number; email: string }
    type B = Omit<A, 'age'>  // { name: string; email: string }
  • Exclude<T, U>: T에서 U에 해당하는 타입을 제외한 타입 반환

    type T = string | number | boolean
    const a: Exclude<T, number | boolean> = 1234 // 에러 발생 -> 남은 T는 string이라서 타입 불일치
    
    type A = Exclude<'a' | 'b' | 'c', 'b'>  // 'a' | 'c', 직접 사용도 가능
  • Extract<T, U>: T에서 U에 공통되는 타입만 추출

    type T = string | number | boolean
    type T = number | boolean | string[]
    const a: Extract<T, U> = "스트링입니다" // 에러 발생 -> 공통 타입은 number, boolean뿐
    
    type A = Extract<'a' | 'b' | 'c', 'a' | 'c' | 'd'>  // 'a' | 'c'
  • ReturnType<T>: 함수 타입 T의 반환 타입을 추출

    function hello(msg: string) {
      return `Hello ${msg}`
    }
    
    const a: ReturnType<typeof hello> = 'Only string'
    const b: ReturnType<typeof hello> = 1234 // 에러 발생 -> hello의 반환 타입은 string임
    
  • Awaited<T>: Promise의 결과값 타입을 추출 (중첩된 Promise<Promise<T>>도 T로 한번에 추출 가능)

    type A = Awaited<Promise<string>>  // string
    type B = Awaited<Promise<Promise<number>>>  // number
    


😊오늘의 느낀점😊

이번 강의에서는 이론적인 것보다 실제 개발을 하면서 상식으로 알아두어야하는 내용들을 익힐 수 있었다.
외부 패키지의 타입 설치, tsconfig.json의 설정법, 타입 import / export 등 당연히 알고 있을 거라고 생각하지만 제대로 배운 적은 없는 정보들을 깊게 살펴볼 수 있어서 가려운 곳을 제대로 긁은 기분 !!
내장 유틸리티 타입들도 중요한 것들을 싹 훑어봐서 앞으로 계속 도움이 될 것 같다 ㅎㅎ

profile
데브코스 프론트엔드 5기

0개의 댓글