TypeScript - 함수

euNung·2022년 7월 18일
0

TypeScript

목록 보기
3/8

함수 선언, 호출

  • 자바스크립트에서 함수는 일급 객체
    : 객체를 다루듯이

    • 함수를 변수에 할당 가능
    • 함수를 다른 함수로 전달 가능
    • 함수에서 함수를 반환 가능
    • 객체와 프로토타입에 할당 가능
    • 함수에 프로퍼티 기록 가능
    • 함수에 기록된 프로퍼티 읽기 가능

  • 타입스크립트 함수

    function add(a: number, b: number) {
    	return a + b
    }
    
    function add(a: number, b: number): number {
    	return a + b
    }
    
    // 함수 선언문
    function 함수이름(매개변수1: 타입1, 매개변수2: 타입2[, ...]): 반환값 타입 {
    	함수 몸통
    }
    
    // 함수 표현식
    const 함수이름 = function(매개변수1: 타입1, 매개변수2: 타입2[, ...]): 반환값 타입 { 
    	함수 몸통 
    }
    
    // 화살표 함수
    const 함수이름 = (매개변수1: 타입1, 매개변수2: 타입2[, ...]): 반환값 타입 => 함수
    • 함수 매개변수의 타입은 보통 명시적으로 정의
    • 반환 타입은 타입스크립트가 자동으로 추론하지만 원하면 명시적으로 정의해도 됨
  • 선택적 매개변수
    : 필수 매개변수를 먼저 지정하고 선택적 매개변수를 뒤에 추가

    // 방법1: 선택적 마크(?)
    function log(message: string, userId?: string) {
      let time = new Date().toLocaleTimeString()
      console.log(time, message, userId || 'Not signed in')
    }
	
	// 방법2: 기본값 기정
	function log(message: string, userId = 'Not signed in') {
      let time = new Date().toLocaleTimeString()
      console.log(time, message, userId)
    }
  • 나머지 매개변수
    : 인수를 여러 개 받는 함수는 그 목록을 배열 형태로 전달 가능
    : 함수의 매개변수 목록 맨 마지막에 위치해야 함
    : 함수는 최대 한 개의 나머지 매개변수만을 가질 수 있음
	function sum(...numbers: number[]): number {
      return number.reduce((total, n) => total + n, 0)
    }

	sum(1, 2, 3)
  • call, apply, bind

    • call
      : 함수 안에서 값을 this로 한정하며 인수를 순서대로 전달된 함수 호출
    • apply
      : 함수 안에서 값을 this로 한정하며 두 번째 인수를 펼쳐 함수에 매개변수로 전달된 함수 호출
    • bind
      : this의 인수를 함수의 인수 목록으로 한정
      : 함수를 호출하지 않고 새로운 함수를 반환
    function add(a: number, b: number): number {
     	 return a + b
    }
    
    add(10, 20)
    add.call(null, 10, 20)
    add.apply(null, [10, 20])
    add.bind(null, 10, 20)()
  • this의 타입
    : 자바스크립트의 this변수는 클래스에 속한 메서드들 뿐만아니라 모든 함수에서 정의됨
    : this의 값은 함수 호출 방법에 따라 달라짐

    function fancyDate() {
     	return `${ this.getDate() }/ ${ this.getMonth() } / ${ this.getFullYear() }`
    }
    
    fancyDate.call(new Date)	// "7/17/2022"
    // 런타임에 예외 발생
    fancyDate()					// TypeError: this.getDate는 함수가 아님 

    ✅ 함수에서 this를 사용할 때는 기대하는 this의 타입을 함수의 첫 번째 매개변수로 선언하기

    function fancyDate(this: Date) {
    	return `${ this.getDate() }/ ${ this.getMonth() } / ${ this.getFullYear() }` 
      
     fancyDate.call(new Date)	// "7/17/2022"
     // 런타임이 아닌 컴파일 타임에 예외 발생
     fancyDate()				// Error: void 타입의 'this'를 메서드에 속한 'Date' 타입의 'this'에 할당할 수 없음
  • 제너레이터 함수
    : 여러 개의 값을 생성하는 편리한 기능 제공
    : 값을 생산하는 속도 정교하게 조절 가능

    function* createFibonacciGenerator() {
     	let a = 0
       	let b = 1
       while (true) {
         yield a;
         [a, b] = [b, a + b]
       }
    }
    
    let fibonacciGenerator = createFibonacciGenerator()	// IterableIterator<number>
    fibonacciGenerator.next() 							// {value: 0, done: false}
    fibonacciGenerator.next() 							// {value: 1, done: false}
    fibonacciGenerator.next() 							// {value: 1, done: false}
    fibonacciGenerator.next() 							// {value: 2, done: false}
    fibonacciGenerator.next() 							// {value: 3, done: false}
    fibonacciGenerator.next() 							// {value: 5, done: false}
    • iterable
      : Symbol.iterator라는 프로퍼티를 가진 모든 객체

    • iterator(반복자)
      : next라는 메서드(value, done 프로퍼티를 가진 객체 반환)를 정의한 객체

      • 객체를 만들어 직접 iterable과 iterator 정의
        let numbers = {
        	*[Symbol.iterator]() {
            	for(let n = 1; n <= 10; n++) {
                	yield n
                }
            }
         }
         
         // number: iterable
         // numbers[Symbol.iterator]() 호출 시 iterator 반환
호출 시그니처

: 값이 아닌 타입 정보만 포함
: 바디를 포함하지 않아 타입스크립트가 값을 추론할 수 없으므로 반환 타입을 명시해야 함

(a: number, b: number) => number

// ex)
type Log = (message: string, userId?: string) => void

let log: Log = (message, userId = 'Not signed in') => {
   let time = new Date().toISOString()
   console.log(time, message, userId)
}
  • 문맥적 타입화
    : 함수의 매개변수 타입을 명시하지 않아도 타입스크립트가 추론 가능
    : ex1) 위와 같은 호출 시그니처 사용
    : ex2) 콜백 함수

    function times(f: (index: number) => void, n: number) {
     	for (let i = 0; i < n; i++) {
         	f(i)
       }
    }
    
    time(n => console.log(n), 4)	// 인수로 전달하는 함수의 타입 명시할 필요x

시그니처 오버로딩

// 단축형 호출 시그니처
type Log = (message: string, userId?: string) => void

// 전체 호출 시그니처
type Log = {
  (message: string, userId?: string): void
}

✅ 간단한 상황이라면 단축형, 복잡한 함수라면(=> 오버로드 된 함수) 전체 시그니처를 활용하는 것이 좋음
- 오버로드 된 함수 : 호출 시그니처가 여러 개인 함수

  • 함수 표현식 오버로드
type Reserve = {
  (from: Date, to: Date, destination: string): Reservation
  (from: Date, destination: string): Reservation
}

// 조합된 시그니처는 직접 구현해야 함
let reserve: Reserve = (from: Date, toOrDestination: Date | string, destination?: string) => {
  // 두 가지 방식으로 reserve를 호출할 수 있으므로 
  // 어떤 방식으로 reserve가 호출되는지 확인시켜주어야 함
  if (toOrDestination istanceof Date && destination !== undefined) {
    // 편도 여행 예약
  } else if (typeof toOrDestination === 'string') {
    // 왕복 여행 예약
  }
}
  • 함수 선언 오버로드
type CreateElement = {
  (tag: 'a'): HTMLAnchorElement
  (tag: 'canvas'): HTMLCanvasElement
  (tag: 'table'): HTMLTableElement
  (tag: string): HTMLElement
}

// 조합된 시그니처는 직접 구현해야 함
function createElement(tag: 'a'): HTMLAnchorElement
function createElement(tag: 'canvas'): HTMLCanvasElement
function createElement(tag: 'table'): HTMLTableElement
function createElement(tag: string): HTMLElement {
  ...
}
  • 객체 프로퍼티 할당에 전체 타입 시그니처 사용
type WarnUser = {
  (warning: string): void
  wasCalled: boolean
}

function warnUser(warning) {
  if (warnUser.wasCalled) {
    return 
  }
  warnUser.wasCalled = true
  alert(warning)
}
warnUser.wasCalled = false

다형성

: 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미

💛 어떤 타입을 사용할지 미리 알 수 없는 상황에서 제네릭 타입을 사용하여 해결

  • 제네릭 타입
    : 꺽쇠괄호(<>)로 제네릭 타입 매개변수임을 선언
    : 꺽쇠 기호를 추가하는 위치에 따라 제네릭의 범위가 결정됨
// T의 범위를 개별 시그니처로 한정
type Filter = {
  <T>(array: T[], f: (item: T) => boolean): T[]
}
  1. 타입스크립트가 전달된 array의 타입을 보고 T의 타입을 추론
  2. filter에 정의된 모든 T를 추론한 타입으로 대체

=> filter 타입의 함수를 호출할 때 이 시그니처의 T를 구체 타입으로 한정

// T의 범위를 모든 시그니처로 한정
type Filter = {
  <T>(array: T[], f: (item: T) => boolean): T[]
}

=> Filter 타입의 함수를 선언할 때 T를 한정

  • 다형적 타입 별칭
    : 타입 별칭에서는 타입 별칭명과 할당 기호(=) 사이에만 제네릭 타입을 선언할 수 있음
    : 타입이 자동으로 추론되지 않으므로 타입 매개변수를 명시적으로 한정해야 함
type MyEvent<T> = {
  target: T
  type: string
}

// 버튼 이벤트 표현
type ButtonEvent = MyEvent<HTMLButtonElement>

// 타입 매개변수 한정
let myEvent: MyEvent<HTMLButtonElement | null> = {
  target: document.querySelector('#myButton'),
  type: 'click'
}

// 함수 시그니처에 제네릭 타입 별칭 사용
function triggerEvent<T>(event: MyEvent<T>): void {
  ...
}
  
triggerEvent({		// T는 Element | null
  target: document.querySelector('#myButton'),
  type: 'mouseover'
})
  • 한정된 다형성
    Q. TreeNode를 인수로 받아 value에 매핑 함수를 적용해 새로운 TreeNode를 반환하는 mapNode 함수 구현

      type TreeNode = {
        value: string
      }
    
      type LeafNode = TreeNode & {
        isLeaf: true
      }
    
      type InnerNode = TreeNode & {
        children: [TreeNode] | [TreeNode, TreeNode]
      }
    
      let a: TreeNode = {value: 'a'}
      let b: LeafNode = {value: 'b', isLeaf: true}
      let c: InnerNode = {value: 'c', children: [b]}
      
      let a1 = mapNode(a, _ => _.toUpperCase())
      let b1 = mapNode(b, _ => _.toUpperCase())
      let c1 = mapNode(c, _ => _.toUpperCase())

    A.

    function mapNode<T extends TreeNode>(node: T, f: (value: string) => string): T {
    	return {
       ...node, value: f(node.value)
    	}
    }
    • extends TreeNode를 생략하고 T 라고만 쓰면 node.value에 대한 정보가 없으므로 컴파일 타임 에러가 발생할 것
    • T를 사용하지 않고 (node: TreeNode, f: (value: string) => string) => TreeNode 처럼 선언하면 매핑되면서 타입 정보가 날아가서 a1, b1, c1이 모두 TreeNode가 됨
  • 제네릭 타입 기본값
    : 기본 타입을 갖는 제네릭은 반드시 마지막에 위치해야 함

type MyEvent<T = HTMLElement> = {
  target: T
  type: string
}

// T의 값 한정하여 기본값 설정
type MyEvent<T extends HTMLElement = HTMLElement> {
  target: T
  type: string
}

let myEvent: MyEvent = {
  target: myElement,
  type: string
}

✅ 타입스크립트 프로그램을 구현할 때는 먼저 함수의 타입 시그니처를 정의한 다음 구현을 추가
=> 타입이 이끈다
=> 구현을 시작하기 전에 프로그램을 타입 수준에서 구상해보면 모든 것이 이치에 맞는지를 상위 수준에서 확인할 수 있음

profile
프론트엔드 개발자

0개의 댓글