반응형
TL;DR
- 타입스크립트에서 함수를 선언하고 실행하는 방법
- 함수 선언 시에 보통 매개변수의 타입은 명시적으로 정의하고, 반환 타입은 자동으로 추론하게 한다.
- 타입스크립트는 함수 생성자(Function)를 제외한 함수 선언문, 함수 표현식, 화살표 함수 문법을 안전하게 지원한다.
- this, 제네레이터, 이터레이터 타입을 지정할 수 있다.
- 시그니처 오버로딩 : 호출 시그니처가 여러 개인 경우
함수 선언과 호출
- 함수 선언 시에 보통 매개변수의 타입은 명시적으로 정의하고, 반환 타입은 자동으로 추론하게 한다.
- function add(a: number, b: number) { return a + b }
- 매개변수 타입을 추론하는 경우는 문맥적 타입화를 할 때이다. 여기에서 문맥적 타입화는 함수 타입을 별개로 선언해 함수에 명시적으로 해당 타입을 지정헀을 때, 함수 구현부에서 매개변수 타입을 지정해주지 않아도 타입스크립트가 추론하는 기능을 뜻한다.
- 함수 선언 방식 : 타입스크립트는 함수 생성자(Function)를 제외한 모든 문법을 안전하게 지원한다.
- // 함수 선언문 function greet(name: string) { return 'hello ' + name } // 함수 표현식 let greet2 = function (name: string) { return 'hello ' + name } // 화살표 함수 let greet3 = (name: string) => { return 'hello ' + name } // 함수 생성자 : Dangerous !! let greet5 = new Function('name', 'return "hello " + name')
- 함수를 호출할 때는 타입을 명시할 필요가 없다.
- 인수를 전달할 때 함수의 매개변수 타입과 인수의 타입이 잘 맞는지 확인한다. 인수를 전달하지 않거나 잘못된 타입의 인수를 전달하면 에러가 발생한다.
선택적 매개변수와 기본 매개변수
- 선택적 매개변수
- 필수 매개변수 뒤에 선택적 매개변수를 지정할 수 있다.
function log(message:string, userId?: string) { console.log(message, userId || 'Not signed in') } log('Page Loaded') // "Page Loaded", "Not signed in" log('User signed in', '123ab') // "User signed in", "123ab"
- 기본 매개변수
- 매개변수에 기본값을 지정할 수 있다.
- 의미상 선택적 매개변수와 같지만 어디에나 추가할 수 있다는 점이 다르다.
function log(message: string, userId = 'Not signed in') { console.log(message, userId) } log('Page Loaded') // "Page Loaded", "Not signed in" log('User signed in', '123ab') // "User signed in", "123ab"
- 보통 선택적 매개변수보다 기본 매개변수를 더 많이 사용한다.
나머지 매개변수
- 인수의 개수가 달라질 수 있는 가변 인자 함수의 경우, 나머지 매개변수를 사용할 수 있다.
- 나머지 매개변수는 하나만 사용할 수 있고, 함수의 매개변수 목록 마지막에 위치해야 한다.
function sum(...numbers: number[]) {
return numbers.reduce((total, n) => total + n, 0)
}
console.log(sum(1, 2, 3)) // 6
- 이전에는 arguments 객체로 가변 인자 함수의 매개변수를 전달 받았다. 하지만 arguments는 유사 배열 객체로, 안전하지 않다는 문제가 있었고, 따라서 나머지 매개변수를 사용하는 것이 좋다. ar
- 예를 들어, arguments 객체를 사용할 경우, reduce의 콜백 함수 매개변수 타입을 any로 추론하는 문제가 있다. 이 뿐만 아니라 arguments 객체는 자바스크립트가 만들어내는 객체이므로 따로 매개변수를 전달할 필요가 없어 타입 에러가 발생한다.
- function sum() { return Array.from(arguments).reduce((total, n) => total + n, 0) } sum(1, 2, 3) // Expected 0 arguments, but got 3.
call, apply, bind
- 함수를 호출하는 또 다른 방법으로 call, apply, bind가 있다.
- 모두 함수 안에서 값을 this로 한정(bind)하면서 호출한다는 특징이 있다.
- apply : 인수를 배열로 전달
- call : 인수를 목록 리스트로 전달
- bind : 새로운 함수를 반환
function add(a: number, b: number) {
return a + b
}
add(10, 20) // 30
add.apply(null, [10, 20]) // 30
add.call(null, 10, 20) // 30
add.bind(null, 10, 20)() // 30
strictBindCallApply
코드에서 call, apply, bind를 안전하게 사용하려면 해당 옵션을 활성화해야 한다. strict 모드에 포함된 옵션이다.
this의 타입
- 함수에서 this를 사용할 때는 this 타입을 함수의 첫 번째 매개변수로 선언하고, 타입을 지정할 수 있다. 함수 시그니처에 사용한 this는 예약어이므로 다른 매개변수와 다르게 처리된다.
- function formatDate(this: Date) { return `${this.getDate()} / ${this.getMonth()}` } formatDate.call(new Date) // 25 / 4 formatDate() // void를 this 타입에 할당할 수 없음 에러
this
- this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다.
- this가 가리키는 값인 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
- 일반 함수 호출 : 전역 객체 window
- 메서드 호출 : 호출한 객체
- 생성자 함수 호출 : 생성자 함수가 생성할 인스턴스
- apply/call/bind 메서드에 의한 간접 호출 : 함수에 전달한 인수
- this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조할 수 있다.
noImplicitThis
함수에서 항상 this 타입을 명시적으로 설정할 수 있도록 강제할 수 있는 옵션. 클래스와 객체의 함수에는 this를 지정하라고 강제하지 않는다.
제네레이터 함수
- 이터러블이면서 이터레이터인 객체. 함수 호출자가 함수 실행 제어권을 가지고 있는 함수이다.
- 타입 : IterableIterator 타입
- yield를 통해 방출된 값을 기반으로 타입스크립트가 추론하거나
- IterableIterator<number>와 같이 명시할 수 있음.
function* createFibonacciGenerator() : IterableIterator<number> { let a = 0 let b = 0 while (true) { yield a; [a, b]= [b, a+b] } } let fibonacciGenerator = createFibonacciGenerator() fibonacciGenerator.next() // {value: 0, done; false } -> 이터레이터 객체 fibonacciGenerator.next() // {value : 1, done: false }
이터러블과 이터레이터
- 이터러블은 Symbol.iterator() 메서드를 직접 구현하거나 상속받은 객체를 말한다. 이터러블은 for …of 문으로 순회할 수 있고, 스프레드 문법과 배열 디스트럭처링 할당의 대상이다
- 이터레이터는 nvalue, done의 프로퍼티를 가진 이터레이터 리절트 객체를 반환하는 next 메서드를 가진 객체를 말한다.
반복자(iterator)
- 제네레이터로 순회 가능한 자료 구조를 만들고, 이터레이터로 생성된 값을 사용할 수 있다.
- Symbol.iterator를 호출하면 이터레이터가 반환된다.
let numbers = {
*[Symbol.iterator]() {
for (let n=1;n<=10;n++) {
yield n
}
}
}
for (let a of numbers) {
console.log(a) // 1, 2, 3...
}
let allNums = [...numbers]
let [one, two, ...rest] = numbers
호출 시그니처 : 함수 전체의 타입
- 함수 전체의 타입은 화살표 함수처럼 생긴 **호출 시그니처(=타입 시그니처)**로 지정한다.
- 함수의 인수나 반환 타입을 지정할 수 있다.
function sum(a: number, b: number) {
return a + b
}
// 호출 시그니처 또는 타입 시그니처
(a: number, b: number) => number
- 함수 호출 시그니처는 타입 수준 코드이다. 즉, 값이 아닌 타입 정보만 포함한다.
- 따라서 신경 써줘야 할 부분이 2가지가 있다.
- 값은 표현할 수 없기 때문에 기본 값은 표현할 수 없다.
- 바디를 포함하지 않아 반환 타입을 추론할 수 없기 때문에 반환 타입을 명시해야 한다.
<aside> 📍 **타입 수준 코드와 값 수준 코드
타입 수준 코드**는 타입과 타입 연산자를 ****포함하는 코드를 의미하고, 값 수준 코드는 그 밖의 모든 것을 가리킨다.
즉, 어떤 코드가 유효한 자바스크립트 코드라면 값 수준 코드이고, ****유효한 자바스크립트 코드는 아니지만 유효한 타입스크립트 코드라면 ****타입 수준으로 구분할 수 있다.
function area(radius: number): number | null {
if (radius < 0) return null
return Math.PI * (radius ** 2)
}
- 매개변수 타입, 반환 타입은 타입 수준 코드. 이외는 값 수준 코드 </aside>
- 호출 시그니처와 구현의 관계
- 호출 시그니처 → 구현
- Log 타입임을 명시
- 매개변수의 타입은 구현부에서 다시 지정하지 않는다. 문맥적 타입화를 통해 타입스크립트가 매개변수의 타입을 추론할 수 있다.
- 기본값 지정
- // 호출 시그니처 type Log = (message: string, userId?: string) => void // 함수 구현 let log: Log = (message, userId = 'Not signed in') => { console.log(message, userId) }
- 호출 시그니처 → 구현
문맥적 타입화
- 타입스크립트의 강력한 추론 기능.
- 예
- 호출 시그니처로 함수 전체 타입을 지정했을 때 매개변수 타입을 명시하지 않아도 되는 경우
- 콜백 함수의 타입을 지정하고, 콜백 함수 선언을 인라인으로 한 경우
- // 타입 선언 function times( f: (index: number) => void, n: number ) { for (let i=0;i<n;i++) { f(i) } } // 콜백 함수 선언 인라인으로 times(n => console.log(n), 4)
오버로드된 함수 타입
- 오버로드는 말 그대로 여러 개를 겹쳐 쓰는 걸 말한다. 즉, 오버로드 된 함수는 호출 시그니처가 여러 개인 함수이다.
- 호출 시그니처는 간단한 단축 시그니처와 전체 시그니처가 있다. 간단한 단축 시그니처를 두고 왜 전체 시그니처를 쓰겠냐고 생각할 수 있다. 하지만 1) 함수가 복잡하거나 2) 함수 타입이 오버로드 된 경우, 또는 3) 함수의 프로퍼티를 만들 때 전체 시그니처를 사용하는 것이 좋다.
// 단축형 호출 시그니처
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
) => {
if (toOrDestination instanceof Date && destination !== undefined) {
// 왕복 예약
} else if (typeof toOrDestination === 'string') {
// 편도 예약
}
}
- 두 가지 방식으로 reserve를 호출 할 수 있으므로 함수 구현 시 reserve가 어떤 방식으로 호출되는지를 확인하는 과정이 필요하다.
반응형
'FrontEnd > TypeScript' 카테고리의 다른 글
[TypeScript] 비동기 프로그래밍, 동시성과 병렬성 (0) | 2023.06.07 |
---|---|
[TypeScript] 에러 처리 (0) | 2023.06.05 |
[TypeScript] 가변성(슈퍼타입/서브타입 파악하기) & 할당성 & 타입 넓히기 (0) | 2023.06.03 |
[TypeScript] 클래스와 인터페이스 (0) | 2023.05.31 |
[TypeScript/타입스크립트 프로그래밍] 타입스크립트 타입 선언 & 종류 (2) | 2023.05.25 |