이번에 살펴볼 내용은 타입스크립트에서 가장 중요한 '함수'이다
자바스크립트에서 함수란 객체를 다루듯 함수를 변수에 할당하거나, 함수를 다른 함수로 전달하거나, 함수에서 함수를 반환하거나, 객체와 프로토타입에 할당하거나, 함수에 프로퍼티를 기록하거나, 함수에 기록된 프로퍼티를 읽거나 등의 작업이 가능 타입스크립트도 마찬가지
반환타입은 자동으로 추론하지만 원한다면 명시할 수 있음
실무에서는 타입스크립트가 반환 타입을 추론하도록 하는 게 다반사
타입스크립트가 할 수 있는 일은 개발자가 직접 할 필요가 없기 때문
타입스크립트는 최소 다섯 가지의 함수 선언 방법을 지원
// 단축형 화살표 함수식 표현
타입스크립트는 함수 생성자 (안전 X)을 제외한 모든 문법을 안전하게 지원하며 모든 문법은 보통 매개변수 타입의 필수 어노테이션, 반환 타입의 선택형 어노테이션에 적용하는 것과 같은 규칙을 따름
타입스크립트에서 함수를 호출할 때 타입 정보를 따로 제공하지 않아도 되고 인수를 전달하면 타입스크립트가 함수의 매개변수와 인수의 타입이 호환되는지 확인
물론 그러한 경우가 아니라 인수를 전달하지 않거나 잘못된 타입의 인수를 전달하면 타입스크립트가 에러를 발생시킴
물론, 일반 매개변수에 타입을 지정하는 것처럼 기본 매개변수에도 타입 명시가 가능
(JS에서 가능한 문법은 무조건 TS도 사용이 가능하기 때문)
type Context = {
appId? : string
userId? : string
}
보통 실무에서는 선택적 매개변수보다 기본 매개변수를 더 자주 사용하게 됨
인수를 여러개 받는 상황이라면 그 목록을 배열로도 건넬 수 있다.
예전 자바스크립트에서 사용하던 arguments가 아닌 최신 문법인 Rest Parameter를 적극 활용하면 된다.
arguments는 배열이 아닌 유사배열 객체이므로 배열로의 변환 과정이 필수적인데 TS에서는 매개변수의 타입 할당이 필수적이므로 다음처럼 에러가 난다.
(인자의 타입 할당이 없으므로)
그렇기에 우리는 아래처럼 Rest 파라미터를 활용하는 것이 더욱 좋다
여기서 잠깐!, call의 예시
obj.yell()로 호출한 경우는 obj의 있는 string인 'zero'를 가리키지만
obj.yell.call(obj2)를 호출한 경우는 obj2의 string인 'what'을 가지고 출력을 하게 됩니다
즉, yell은 obj메소드인데도 zero 대신에 what이 alert 됐습니다.
결론적으로 call을 써서 this를 정의해준다면 다른 객체의 파라미터나 메소드를 자기 것마냥 사용할 수 있는 겁니다.
원래 함수의 arguments들은 유사배열이기 때문에 배열의 메소드를 쓸 수 없지만 여기서 call이나 apply를 활용하여 Array.prototype.join.call(arguments)
물론, join외에도 slice, concat등 모든 메소드를 활용 가능
apply의 예시
대표적인 용도도 유사 배열 객체에 배열 메소드를 사용하는 경우 입니다.
마찬가지로 this는 arguments 객체로 바인딩하라는 의미
여기서 call와 apply의 차이는 call()은 여러 인자를 나열해서 받고 apply()는 여러 인자를 한번에 받는다.
bind의 예시
bind의 용도는 함수가 가리키는 this만 바꾸고 호출하진 않는 것
정확히 이야기하자면 this를 정의하고 나서 그 함수를 복사해 새로운 함수를 만들어 리턴 하는 것 그렇기에 ( ) 가 붙어있는 것임 바로 활용하려고
obj.yell.bind를 해주었더니 yell함수가 this의 obj2로 바뀐 걸 확인할 수 있음즉, call, apply와 비슷하지만 호출은 하지 않고 함수만 반환하는 것
call(this, 1, 2, 3)은 bind(this)(1, 2, 3)과 같음
함수에 this 키워드를 사용하면 기대하는 this 타입을 함수의 첫 번째 매개변수로 선언하자
함수안에 등장하는 모든 this가 의도한 this가 됨을 TS는 보장
this의 범위를 제한시키려는 용도라고 추측
또한, 함수 시그니처 (매개변수에 사용한) this는 예약어이므로 다른 매개변수와 다른 방식으로 처리됨
타입스크립트에 많은 정보를 제공한 덕분에 런타임 에러 대신 컴파일 타임에 경고를 해주었다.
1 함수앞에 붙은 *는 이 함수가 제네레이터임을 의미
3 제네레이터는 yield라는 키워드로 값을 방출
4 피보나치 숫자를 계산하기 위해 a에 b를, b에 a + b를 한번에 다시 할당
이 함수는 IterableIterator를 반환하고 next를 호출할 때마다 결과를 계속 방출 (그렇기에 done이 false)
이 방법 혹은 IterableIterator< number >라는 제네레이터의 타입 명시도 존재
반복자와 제네레이터는 상생 관계
이 sum 함수의 타입을 Function 타입이라고 부를 수 있지만 객체 타입을 object라고 표기하는 게 좋지 않듯이 함수도 Function 타입이 아닌 호출 시그니처 타입으로 표현하는것이 좋음
화살표 함수와 매우 유사한 형태는 사실 의도된 것
함수 시그니처로 매개변수 타입, this 타입, 반환 타입, rest 타입, 조건부 타입을 표현할 수 있으나 기본값은 표현할 수 없음 (기본값은 타입이 아닌 값이기 때문)
호출 시그니처의 두 가지 사용법 (타입 명세 후 추후 함수를 호출시에 붙여주는 것)
호출 시그니처는 바디를 포함하지 않아 타입스크립트가 타입을 추론할 수 없으므로 반환 타입을 명시해야함
호출 시그니처와 구현코드 (거의 유사, 언어 설계상 의도한 결정)
타입스크립트의 강력한 타입 추론 기능
times의 시그니처에서 f의 인수 index를 number로 선언 했으므로 타입스크립트는 문맥상 n이 number임을 추론할 수 있음
단축형과 전체형에서 단축형을 주로 활용하되, 전체 시그니처를 사용하는 것이 좋을 떄도 있다
바로 함수 타입의 오버로딩이 좋은 예이다
오버로드된 함수?
호출 시그니처가 여러 개인 함수
자바스크립트는 동적 언어이므로 어떤 함수를 호출하는 방법이 여러 가지 이고 인수 입력 타입에 따라 반환 타입이 달라질 때도 있다. 그렇기에 타입스크립트는 입력 타입에 따라 달라지는 함수의 출력 타입을 정적 시스템으로 각각 제공 (고오급 기능임)
앞에 2가지 예처럼 독립적인 함수로 구현하면 정상적으로 동작하지 않음을 볼 수 있음
-> 함수 f에 여러 개의 오버로드 시그니처를 선언하면 호출자 관점에서 f의 타입은 이들 오버로드 시그니처의 유니온 (둘 다 합친 것) 이 되기 때문
그렇기에 여러가지 경우의 수를 두는, 하나의 함수에 오버로드된 함수를 구현해야함
Type Guard 또는 refine(정제)라는 기법을 활용해야함
*항상 오버로드 시그니처는 구체적 (좁게)으로 유지해야함
매개변수와 리턴값 등에서 기대하는 타입을 정확하게 알고 있지 못한 경우에는 아래와 같이 모든 타입에 해당하는 함수를 만들어줘야만 함
다음의 예시를 보면, 두 함수를 독립적으로 써놓았더라도 독립적인 구현이 되지 않으므로 type guard를 통해 값을 막아줘야합니다만. 우리는
(number | stinrg, number | string) : number | string을 구현해야 하는 상황이므로 두 함수의 모양이 달라 에러가 나옴
그렇기에 우리는 반반같은 모든 경우의 수를 다 고려해야합니다 (공변성, 반공변성)
그렇다고 우리가 인터섹션을 통해 "둘 중 한 개만 해줘도 되는 거 아니야?" 할 수도 있지만 어떻게든 결과는 number | string 이므로 제대로된 추론을 할 수 없게 됨
결론적으로, 우리의 해결법은 타입 주도권을 개발자에게 넘겨주는 제네릭을 사용하면 됨
(라이브러리를 사용할 때에 편함 같은 것)
-> 이 타입이 지금은 무엇인지 알 수 없으니 누군가 Filter를 쓸 때 멋있게 추론 해주세요!
T는 단지 이름 타입으로 A, Zebra등 어떤 것이든 사용할 수 있고
T를 시작으로 U, V, W순으로 필요한 만큼 사용
물론 타입 별칭, 클래스, 인터페이스에서도 제네릭 타입을 사용할 수 있음
그렇다면 타입스크립트로 라이브러리를 만들 때 고려해야하는 사항?
1. 로직 2. 사용자가 어떤 값을 넣을 건지 3. 어떤 값을 출력 받고 싶은지 4. 에러처리
T를 개별 시그니처가 아닌 모든 시그니처로 한정한 전체 호출 시그니처, T를 Filter 타입의 일부로 선언했으므로 타입스크립트는 Filter 타입의 함술르 선언할 때 T를 한정
T는 string으로 U는 boolean우로 추론
하지만 당연히 명시적으로도 지정가능, 하지만 제대로 두 개의 모든 제네릭 타입을 명시하거나 반대로 아무것도 명시해서는 안 됨
하지만 함수로 전달한 인수의 정보를 이용해 제네릭의 구체 타입을 추론하므로 다음과 같은 상황이 벌어질 수도 있음
Promise의 타이핑은 Promise<T>로 되어있는 상황, 이럴 경우엔 당연히 제네릭 타입을 명시를 해줘야 하지만, 정의가 안 된 경우 타입이 Promise<Unknown>이 되어버림
결국 그렇기에 promise가 성공하고 실행되는 (비동기) result 부분에서 result가 unKnown이 되는 것 -> 우리는 type guard등을 통해 타입을 좁혀 써야함
해결법?
- as 키워드
- 제네릭에 명시
또한, 제네릭은 type alias에서도 사용할 수 있음
extends 키워드를 통해 제네릭에 들어올 타입을 한정할 수 있음
우리가 하는 것은 타입스크립트를 통해 소스코드의 모든 값에 타입을 부여하고자 하는 것 - 매개변수, 리턴 값, 변수 ... 등등
때문에 우리는 코드를 작성할 때 any타입을 없애는 것을 목표로 해야함
타입스크립트는 생각보다 똑똑하므로 추론을 잘 활용해야함
타입은 항상 구체적일수록 좋음