TypeScript - 고급 타입

euNung·2022년 7월 25일
0

TypeScript

목록 보기
6/8

타입 넓히기

  • 타입 넓히기

    • 값을 바꿀 수 있는 변수 : 리터럴 타입 => 기본 타입
      let a = 'x'		// string
       let b = 3			// number
       var c = true 		// boolean
       const d = {x: 3}	// {x: number}
       
       enum E {X, Y, Z}
       let e = E.X		// E
    • 타입이 넓혀지지 않은 변수에 값 할당
      const a = 'x'		// 'x'
       let b = a			// string
    • null, undefined로 초기화된 변수 => any
      let a = null		// any
       a = 3				// any
       a = b				// any
  • 타입을 넓히지 않기

    • 값을 바꿀 수 없는 변수 사용

       const a = 'x'			// 'x'
        const b = 3			// 3
        const c = true 		// true
         
        enum E {X, Y, Z}
        const e = E.X			// E.X
    • 명시적으로 타입 어노테이션 추가

       const a: 'x' = 'x'		// 'x'
        let b = a				// 'x'
    • unll, undefined로 초기화된 변수가 선언 범위를 벗어난 경우

       function x() {
        	let a = null		// any
           a = 3				// any
        	a = b				// any
        	return a
        }
      
        x()					// string
    • const 타입 사용: 타입 넓히기 중지 & 멤버들이 자동으로 readonly가 됨

      let a = {x: 3} as const	4	// {readonly x: 3}
       let b = [1, {x: 2}] as const 	// readonly [1, {readonly x: 2}]

      정제

      : 타입을 정제할 때 typeof, instanceof, in 등의 타입 질의뿐만 아니라 if, ?, ||, switch 같은 제어 흐름 문장을 고려함

      type Unit = 'cm' | 'px' | '%'
      
      let units = Unit[] = ['cm', 'px', '%']
      
      function parseUnit(value: string): Unit | null {
        for (let i = 0; i < units.length; i++) {
          if (value.endsWith(units[i])) {
            return units[i]
          }
        }
        return null
      }
      
      type Width = {
       	unit: Unit,
       	value: number
      }
      
      function parseWidth(width: number | string | null | undefined): Width | null {
        if (width == null) {
          return null
        }
        
        if (typeof width == 'number') {
          return { unit: 'px', value: width }
        }
        
        let unit = parseUnit(width)
        if (unit) {
          return { unit, value: parseFloat(width) }
        }
        
        return null
      }

차별된 유니온 타입

type UserTextEvent = { value: string, target: HTMLInputElement }
type UserMouseEvnet = { value: [number, number], target: HTMLElement }

type UserEvent = UserTextEvent | UserMouseEvent

function handle(event: UserEvent) {
  if (typeof event.value === 'string') {
    event.value		// string
    event.target	// HTMLInputElement | HTMLElement
    return
  }
  event.value		// [number, number]
  event.target		// HTMLInputElement | HTMLElement
}
// 문제: event.target이 제대로 정제되지 않음
  • 리터럴 타입을 이용해 유니온 타입이 만들어낼 수 있는 각각의 경우를 태그하는 방식으로 문제 해결

    // TextEvent라는 리터럴 타입 생성
    type UserTextEvent = { type: 'TextEvent', value: string, target: HTMLInputElement }
    // MouseEvent라는 리터럴 타입 생성
    type UserMouseEvnet = { type: 'MouseEvent', value: [number, number], target: HTMLElement }
    
    type UserEvent = UserTextEvent | UserMouseEvent
    
    function handle(event: UserEvent) {
     if (event.type === 'TextEvent') {
       event.value		// string
       event.target	// HTMLInputElement
       return
     }
     event.value		// [number, number]
     event.target		// HTMLElement
    }
    // 문제: event.target이 제대로 정제되지 않음

    고급 객체 타입

  • 키인 연산자

    type APIresponse = {
     	user: {
       		userId: string
       		friendList: {
         		count: number
         		friends: {
           			firstName: string
           			lastname: string
         		}[]
    		}
    	}
    }
    
    type FriendList = APIResponse['user']['friendList']
    
    function renderFriendList(friendList: FriendList) {
      ...
    }
      
    // 친구 한 명의 타입
    type Friend = FriendList['friends'][number]
  • keyof 연산자

    • 객체의 키를 문자열 리터럴 타입 유니온으로 얻는 방법

      type ResponseKeys = keyof APIResponse							// 'user'
       type UserKeys = keyof APIResponse['user']						// 'userId' | 'friendList'
       type FriendListKeys = keyof APIResponse['user']['friendList'] 	// 'count' | 'friends'
    • 객체에서 주어진 키에 해당하는 값 반환

      function get<O extends object, K extends keyof O>(o: O, k: K): O[K] {
      	  return o[k]
      }
      
      type ActivityLog = {
      	lastEvent: Date
      	events: {
         		id: string
         		timestamp: Date
         		type: 'Read' | 'Write'
      	}[]
      }
      
      let activityLog: ActivityLog = ...	
      let lastEvent: get(activityLog, 'lastEvent')	// Date 타입 반환
      
      // 키를 세 개까지 받을 수 있도록 get 오버로드
      type Get = {
      			<O extends object, K1 extends keyof O>(o: O, k1: K1): O[K1]
      			<O extends object, K1 extends keyof O, K2 extends keyof O[K1]>(o: O, k1: K1, k2: K2): O[K1][K2]
      			<O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2]>(o: O, k1: K1, k2: K2, k3: K3): O[K1][K2][K3]
      			}
      
      let get: Get = (object: any, ...keys: string[]) => {
        let result = object
        keys.forEach(k => result = result[k])
        return result
      }
      
      get(activityLog 'events', 0, 'type')	// 'Read' | 'Write'
      get(activityLog, 'bad')					// Error: 인수 'bad' 타입은 매개변수 'LastEvent' | 'events' 타입에 할당할 수 없음 
  • 매핑된 타입

    type Weekday = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'
     type Day = Weekday | 'Sat' | 'Sun'
    
     let nextDay: { [K in Weekday]: Day } = {	// Error: '{Mon: "Tue"}' 타입은
     	Mon: 'Tue'								// '{Mon: Weekday; Tue: Weekday; Wed: Weekday; Thu: Weekday; Fri: Weekday}' 타입이 정의한 프로퍼티 중
     }											// Tue, Wed, Thu, Fri를 포함하지 않음
    • 활용 예제

      type Account = {
      	  id: number
      	  isEmployee: boolean
      	  notes: string[]
      }
      
      // 모든 필드를 선택형으로 만듦
      type OptionalAccount = {
      	[K in keyof Account]?: Account[k]
      }
      
      // 모든 필드를 nullable로 만듦
      type NullableAccount = {
      	[K in keyof Account]: Account[k] | null
      }
      
      // 모든 필드를 읽기 전용으로 만듦
      type ReadonlyAccount = {
      	readonly [K in keyof Account]: Account[k]
      }
      
      // 모든 필드를 다시 쓸 수 있도록 만듦(= Account)
      type Account2 = {
      	- readonly [K in keyof ReadonlyAccount]: Account[k]
      }
      
      // 모든 필드를 다시 필수형으로 만듦(= Account)
      type Account3 = {
      	[K in keyof OptionalAccount]-?: Account[k]
      }
    • 내장 매핑된 타입

      Record<Keys, Values>		// Keys 타입의 키와 values 타입의 값을 갖는 객체
       Partial<Object>			// Object의 모든 필드를 선택형으로 표시
       Required<Object>			// Object의 모든 필드를 필수형으로 표시
       Readonly<Object>			// Object의 모든 필드를 읽기 전용으로 표시
       Pick<Object, Keys>			// 주어진 Keys에 대응하는 Object의 서브타입을 반환

고급 함수 타입

  • 튜플의 타입 추론 개선
    : 타입스크립트는 튜플의 길이, 타입 위치를 무시하고 제공할 수 있는 가장 일반적인 타입으로 튜플의 타입 추론

    let a = [1, true] 						// (number | boolean)[]
    
    // 타입 어서션, as const를 사용하지 않는다면
    // 나머지 매개변수의 타입을 추론하는 기법 이용
    function tuple<T extends unknown[]>(...ts: T):T {
      return ts
    }
    
    let b = tuple(1, true)					// [number, boolean]
  • 사용자 정의 타입 안전 장치
    : 타입 정제는 유효범위에 속한 변수만을 처리 가능
    : 한 영역에서 다른 영역으로 이동하면 기존의 정제 결과물은 사라짐

    function isString(a: unknown): boolean {
     	return typeof a === 'string'
    }
    
    isString('a')			// true
    isString([7])			// false
    
    function parseInput(input: string | number) }
    	let formattedInput: string
        // 타입스크립트는 isString이 boolean을 반환한다는 사실만 알 수 있음
    	if (isString(input)) {
       		formattedInput = input.toUpperCase()		// Error: 'number' 타입에 'toUpperCase' 프로퍼티가 존재하지 않음
       }
    }
    
    // 사용자 안전 장치 기법으로 해결 가능
    function isString(a: unknown): a is string {
     	return typeof a === 'string'
    }
    • 복합 타입에 적용
      type LegacyDialog = ...
       type Dialog = ...
       
       function isLegacyDialog(dialog: LegacyDialog | Dialog): dialog is LegacyDialog {
         ...
       }
profile
프론트엔드 개발자

0개의 댓글