🌆
Sunset Archive
GitHubLinkedInBlog
  • litsynp
  • Archives
    • Projects
      • Let's Parking!
      • 맛집몬스터 (JMT Monster)
      • spring-x-demo Projects
    • Articles
      • My Thoughts on GitBook
      • Wake-on-LAN & RDP Setup
    • Learning
      • Languages
        • Rust
          • Useful Links
          • Syntax
          • Pointers
          • Dependencies
          • Logging
          • Phantom Types
          • Iterable
            • Optional Skip
        • Go
          • Useful Links
          • Structures
          • Zero Values
          • Error Handling
          • Dependency Injection
          • Project Layout
        • JavaScript/TypeScript
          • Basics
            • Types
              • [JS] Falsy Values
              • [TS] Types - unknown, any, never
            • Rest Parameters (...)
            • Spread Syntax (...)
            • Destructuing Assignment
            • CJS, AMD, UMD, ESM
          • Advanced
            • Conditional Destructuring Assignment
            • Type Guards
          • Miscellaneous
            • Dependency Injection in JS?
            • ESLint, Prettier, TypeScript
          • Node
            • Useful Links
            • General
              • V8 Engine
              • Version Management: NVM
              • Environment Variables
            • Database
              • Knex
        • C
          • Dynamic Multi-dimensional Arrays
        • Spring
          • General
            • @Import v.s. @ContextConfiguration
            • MessageSource
          • Kotlin+Spring
            • Kotlin Annotations (Use-Site Targets)
            • Handling Null in Request DTO
            • Handling Null in URL
          • Reactive Stack
            • Reactive API with Spring WebFlux
          • Spring Security
            • Google OAuth Setup
          • Spring Batch
            • Bulk Insert
        • Kotlin
          • val/var in Constructor
          • Initializer Blocks
          • Inheritance Modifiers (final, open, abstract)
          • Delegate Pattern
        • Java
          • Serialization
          • Random Number Generation
            • (1) Math.random() v.s. Random
            • (2) Random v.s. ThreadLocalRandom
        • Python
          • Version Management
        • Ruby
          • Installation
          • Getters & Setters
        • Elixir
        • Erlang
        • Flutter
        • AWS
          • AWS CLI
        • Terraform
          • Installation
          • Basics
      • Code
        • OOP
          • The 4 Basic Concepts of OOP
          • The SOLID Principles
          • GRASP
          • Spring POJO
          • Others
        • Functional Programming
          • Currying
          • Higher-Order Function
          • Closure
          • Monad
        • 공변성, 반공변성, 무공변성
        • Others
          • UUID
          • GraphQL
          • Multimedia
            • Streaming
          • Geography
            • 위도 (Latitude), 경도 (Longitude)
      • Tools
        • Nix
        • Mermaid
          • Flowchart
          • Sequence Diagram
          • Class Diagram
          • Entity Relationship Diagrams
        • VSCode
          • VSCode CLI
          • VSCode Extensions
        • JetBrains
          • IntelliJ - Open Projects in Tabs
          • Delete Leftover IDE Directories
        • vim
          • Commands
      • Books
        • 다재다능 코틀린 프로그래밍 (Programming Kotlin)
          • Pt 01. 코틀린으로 스크립팅 하기
            • Ch 01. 코틀린 시작하기
            • Ch 02. Java 개발자를 위한 코틀린 필수 사항
            • Ch 03. 함수를 사용하자
            • Ch 05. 콜렉션 사용하기
        • 오브젝트 (Object)
          • Ch.0 들어가며 - 프로그래밍 패러다임
          • Ch.1 객체, 설계
          • Ch.2 객체지향 프로그래밍
          • Ch.3 역할, 책임, 협력
          • Ch.4 설계 품질과 트레이드오프
          • Ch.5 책임 할당하기
          • Ch.6 메시지와 인터페이스
          • Ch.7 유연한 설계
          • Ch.8 의존성 관리하기
          • Ch.9 유연한 설계
          • Ch.10 상속과 코드 재사용
          • Ch.11 합성과 유연한 설계
          • Ch.12 다형성
          • Ch.13 서브클래싱과 서브타이핑
          • Ch.14 일관성 있는 협력
          • Ch.15 디자인 패턴과 프레임워크
          • End. 마치며 - 나아가기
          • 후기
        • 헤드 퍼스트 디자인 패턴 (Head First Design Patterns)
          • Ch.1 디자인 패턴 소개와 전략 패턴
          • Ch.2 옵저버 패턴
          • Ch.3 데코레이터 패턴
          • Ch.4 팩토리 패턴
          • Ch.5 싱글턴 패턴
          • Ch.6 커맨드 패턴
          • Ch.7 어댑터 패턴과 퍼사드 패턴
          • Ch.8 템플릿 메소드 패턴
          • Ch.9 반복자 패턴과 컴포지트 패턴
          • Ch.10 상태 패턴
          • Ch.11 프록시 패턴
          • Ch.12 복합 패턴
        • 이펙티브 타입스크립트 (Effective TypeScript)
          • Ch.1 타입스크립트 알아보기
            • Item 1 타입스크립트와 자바스크립트의 관계 이해하기
            • Item 2 타입스크립트 설정 이해하기
            • Item 3 코드 생성과 타입이 관계없음을 이해하기
            • Item 4 구조적 타이핑에 익숙해지기
            • Item 5 any 타입 지양하기
          • Ch.2 타입스크립트의 타입 시스템
            • Item 6 편집기를 사용하여 타입 시스템 탐색하기
            • Item 7 타입이 값들의 집합이라고 생각하기
            • Item 1-7 Study Summary
            • Item 8 타입 공간과 값 공간의 심벌 구분하기
            • Item 9 타입 단언보다는 타입 선언을 사용하기
            • Item 10 객체 래퍼 타입 피하기
            • Item 11 잉여 속성 체크의 한계 인지하기
            • Item 12 함수 표현식에 타입 적용하기
            • Item 13 타입과 인터페이스의 차이점 알기
            • Item 14 타입 연산과 제너릭 사용으로 반복 줄이기
            • Item 15 동적 데이터에 인덱스 시그니처 사용하기
            • Item 16 number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기
            • Item 17 변경 관련된 오류 방지를 위해 readonly 사용하기
            • Item 18 매핑된 타입을 사용하여 값을 동기화하기
          • Ch.3 타입 추론
            • Item 19 추론 가능한 타입을 사용해 장황한 코드 방지하기
            • Item 20 다른 타입에는 다른 변수 사용하기
            • Item 21 타입 넓히기
            • Item 22 타입 좁히기
            • Item 23 한꺼번에 객체 생성하기
            • Item 24 일관성 있는 별칭 사용하기
            • Item 25 비동기 코드에는 콜백 대신 async 함수 사용하기
            • Item 26 타입 추론에 문맥이 어떻게 사용되는지 이해하기
            • Item 27 함수형 기법과 라이브러리로 타입 흐름 유지하기
          • Ch.4 타입 설계
            • Item 28 유효한 상태만 표현하는 타입을 지향하기
            • Item 29 사용할 때는 너그럽게, 생성할 때는 엄격하게
            • Item 30 문서에 타입 정보를 쓰지 않기
            • Item 31 타입 주변에 null 값 배치하기
            • Item 32 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기
            • Item 33 string 타입보다 더 구체적인 타입 사용하기
            • Item 34 부정확한 타입보다는 미완성 타입을 사용하기
            • Item 35 데이터가 아닌, API와 명세를 보고 타입 만들기
            • Item 36 해당 분야의 용어로 타입 이름 짓기
            • Item 37 공식 명칭에는 상표를 붙이기
          • Ch.5 any 다루기
            • Item 38 any 타입은 가능한 한 좁은 범위에서만 사용하기
            • Item 39 any를 구체적으로 변형해서 사용하기
            • Item 40 함수 안으로 타입 단언문 감추기
            • Item 41 any의 진화를 이해하기
            • Item 42 모르는 타입의 값에는 any 대신 unknown을 사용하기
            • Item 43 몽키 패치보다는 안전한 타입을 사용하기
            • Item 44 타입 커버리지를 추적하여 타입 안전성 유지하기
          • Ch.6 타입 선언과 @types
            • Item 45 devDependencies에 typescript와 @types 추가하기
            • Item 46 타입 선언과 관련된 세 가지 버전 이해하기
            • Item 47 공개 API에 등장하는 모든 타입을 익스포트하기
            • Item 48 API 주석에 TSDoc 사용하기
            • Item 49 콜백에서 this에 대한 타입 제공하기
            • Item 50 오버로딩 타입보다는 조건부 타입을 사용하기
            • Item 51 의존성 분리를 위해 미러 타입 사용하기
            • Item 52 테스팅 타입의 함정에 주의하기
          • Ch.7 코드를 작성하고 실행하기
            • Item 53 타입스크립트 기능보다는 ECMAScript 기능을 사용하기
            • Item 54 객체를 순회하는 노하우
            • Item 55 DOM 계층 구조 이해하기
            • Item 56 정보를 감추는 목적으로 private 사용하지 않기
            • Item 57 소스맵을 사용하여 타입스크립트 디버깅하기
          • Ch.8 타입스크립트로 마이그레이션하기
            • Item 58 모던 자바스크립트로 작성하기
            • Item 59 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기
            • Item 60 allowJS로 타입스크립트와 자바스크립트 같이 사용하기
            • Item 61 의존성 관계에 따라 모듈 단위로 전환하기
            • Item 62 마이그레이션의 완성을 위해 noImplicitAny 설정하기
        • Dive Into Design Patterns
          • 디자인 패턴 소개
          • 소프트웨어 디자인 원칙들
          • 디자인 패턴 목록
          • 유용한 링크
        • 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (System Design Interview)
          • Key Points
          • Real Life Systems
          • Engineering Blogs
        • Node.js 디자인 패턴 바이블 (Node.js Design Patterns 3rd Edition)
        • 리팩터링 2판 (Refactoring: 2nd Ed.)
          • 1장 리팩터링: 첫 번째 예시
          • 2장 리팩터링 원칙
          • 3장 코드에서 나는 악취
          • 4장 테스트 구축하기
Powered by GitBook
On this page
  • About
  • Summary

Was this helpful?

Edit on GitHub
  1. Archives
  2. Learning
  3. Books
  4. 이펙티브 타입스크립트 (Effective TypeScript)
  5. Ch.4 타입 설계

Item 33 string 타입보다 더 구체적인 타입 사용하기

About

string 타입의 범위는 너무 넓다.

'x', 'y' 같은 한 글자부터, 약 120만 글자에 달하는 소설 '모비 딕'(Moby Dick)의 전체 내용도 모두 string 타입이다.

예를 들면,

interface Album {
  artist: string
  title: string
  releaseDate: string   // YYYY-MM-DD
  recordingType: string // e.g., 'live', 'studio'
}

const kindOfBlue: Album = {
  artist: 'Miles Davis',
  title: 'Kind of Blue',
  releaseDate: 'August 17th, 1959',  // 날짜 형식이 다릅니다.
  recordingType: 'Studio'            // 오타 (대문자 S)
}                                    // 그러나... 타입에 문제 없으므로 정상!

function recordRelease(title: string, date: string) { /* ... */ }
recordRelease(kindOfBlue.releaseDate, kindOfBlue.title) // 오류여야 하지만 정상
  • string 타입이 남발된 모습이다.

  • 게다가 주석에 타입 정보도 넣어 두었다. (Item 30 문서에 타입 정보를 쓰지 않기)

  • 근데 타입에 오류는 없어서 정상적으로 실행된다.

  • string의 범위가 넓어서 함수의 매개변수 순서가 잘못된 것이 오류로 드러나지 않는다.

  • 이렇게 string이 남발된 코드를 "문자열을 남발하여 선언되었다(stringly typed)"라고 표현하기도 한다.

이렇게 바꿔보자.

type RecordingType = 'studio' | 'live'
interface Album {
  artist: string
  title: string
  releaseDate: Date
  recordingType: RecordingType
}

const kindOfBlue: Album = {
  artist: 'Miles Davis',
  title: 'Kind of Blue',
  releaseDate: new Date('1959-08-17')
  recordingType: 'Studio'            
               // ~~~~~~ '"Studio"' 형식은 'RecordingType' 형식에 할당할 수 없습니다.
}

이러한 방식에는 세 가지 장점이 있다.

첫 번째, 타입을 명시적으로 정의함으로써 다른 곳으로 값이 전달되어도 타입이 유지된다. 예를 들어, 특정 레코딩 타입의 앨범을 찾는 함수를 작성한다면 다음과 같이 작성할 수 있다.

function getAlbumsOfType(recordingType: string): Album[] { /* ... */ }

두 번째, 타입을 명시적으로 정의하고 해당 타입의 의미를 설명하는 주석을 붙일 수 있다. (Item 48)

/** 이 녹음은 어떤 환경에서 이루어졌는지? */
type RecordingType = 'studio' | 'live'

이제 getAlbumsOfType이 받는 매개변수를 RecordingType으로 바꾸면, IDE/편집기 차원에서 함수를 사용하는 곳에서 주석을 볼 수 있게 된다.

세 번째, keyof 연산자로 더욱 세밀하게 객체 속성 체크가 가능해진다.

언더스코어(Underscore) 라이브러리의 pluck 함수를 살펴보자.

function pluck(records, key) {
  return records.map(r => r[key])
}

위는 아래와 같이 타입을 부여할 수 있다.

function pluck(records: any[], key: string): any[] {
  return records.map(r => r[key])
}

타입 체크가 되긴 하지만 any 타입이 있어 정밀하지 못하다. 특히 Item 38에 나오겠지만 반환 값에 any를 쓰는 건 좋지 않은 설계이다.

타입 시그니처를 개선하기 위해 제너릭 타입을 도입하자.

function pluck<T>(records: T[], key: string): any[] {
  return records.map(r => r[key])
                       // ~~~~~~ '{}' 형식에 인덱스 시그니처가 없으므로
                       // 요소에 암시적으로 'any' 형식이 있습니다.
}

이제 타입스크립트는 key의 타입이 string이라 범위가 너무 넓다는 오류가 발생한다. Album의 배열을 매개변수로 전달하면 기존의 string 타입의 넓은 범위와 반대로, key는 단 네 개의 값('artist', 'title', 'releaseDate', 'recordingType')만이 유효하다.

keyof의 설명을 돕기 위해 준비된 다음 예시는 keyOf Album 타입으로 얻는 예시이다.

type K = keyof Album // 'artist' | 'title' | 'releaseDate' | 'recordingType'이 된다

이제 string을 keyof T로 바꾸면 된다.

function pluck<T>(records: T[], key: keyof T) { // T[keyof T][]로 추론된다.(객체 내 가능한 모든 타입)
  return records.map(r => r[key])               // 마우스를 올려보면 추론된 타입을 볼 수 있다.
}

const releaseDate = pluck(album, 'releaseDate') // 타입이 (string | Date)[]

이 코드는 타입 체커를 통과하며, 타입스크립트가 반환 타입을 추론할 수 있다.

하지만 keyof T는 string에 비하면 범위가 좁긴 하지만 여전히 넓다.

범위를 좁히기 위해 keyof T의 부분 집합으로 제너릭 매개변수를 추가하자. 더 좁혀보자.

function pluck<T, K extends keyof T>(records: T[], key: K): T[K][] {
  return records.map(r => r[key])
}

pluck(albums, 'releaseDate') // 타입이 Date[]
pluck(albums, 'artist') // 타입이 string[]
pluck(albums, 'recordingType') // 타입이 RecordingType[]
pluck(albums, 'recordingDate')
            // ~~~~~~~~~~~~~ '"recordingDate"' 형식의 인수는
            //               ... 형식의 매개변수에 할당될 수 없습니다.

이제 타입 시그니처가 완벽해졌다. 언어 서비스는 Album의 키에 자동 완성 기능을 제공할 수 있게 되었다. (Item 42)

보다 정확한 타입을 사용하면 오류를 방지하고 코드의 가독성을 향상시킬 수 있다.

Summary

  • 'stringly typed' 코드를 피하자. 모든 문자열을 할당할 수 있는 string 타입보다는 더 구체적인 타입을 사용하자.

  • 변수의 범위를 보다 정확하게 표현하고 싶다면 string 타입보다는 문자열 리터럴 타입의 유니온을 사용하자.

    • 타입 체크를 더 엄격히 하고 생산성을 향상시킬 수 있다.

  • 객체의 속성 이름을 함수 매개변수로 받을 때는 string이 아닌 keyof T를 사용하는 것이 좋다.

PreviousItem 32 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기NextItem 34 부정확한 타입보다는 미완성 타입을 사용하기

Last updated 2 years ago

Was this helpful?