자바스크립트에서 함수는 일급 객체
: 객체를 다루듯이
타입스크립트 함수
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
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 프로퍼티를 가진 객체 반환)를 정의한 객체
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[]
}
=> 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)
}
}
제네릭 타입 기본값
: 기본 타입을 갖는 제네릭은 반드시 마지막에 위치해야 함
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
}
✅ 타입스크립트 프로그램을 구현할 때는 먼저 함수의 타입 시그니처를 정의한 다음 구현을 추가
=> 타입이 이끈다
=> 구현을 시작하기 전에 프로그램을 타입 수준에서 구상해보면 모든 것이 이치에 맞는지를 상위 수준에서 확인할 수 있음