ts는 코드를 분석하여 변수와 함수의 타입을 추론한다. 이 때, 문맥(context)이라는 정보를 사용하여 타입 추론을 더욱 정확하게 수행한다.
문맥은 일반적으로 변수나 함수가 사용되는 위치를 의미하는데 예를 들어, 변수가 선언된 위치, 함수의 파라미터나 반환값이 사용되는 위치 등이 모두 문맥이 될 수 있다.
그러나 값을 변수로 분리한 경우
type Language = 'JS' | 'TS' | 'Python';
const setLanguage = (language: Language) => {}
let language = 'JS';
setLanguage(language);
이렇게 되면 할당시점에 타입을 추론하므로 string 으로 추론하게 된다.
이를 해결하기 위해 아래처럼 해주면 되는데
하지만 이 과정에서 문맥에서 값을 분리하였다. 문맥과 값을 분리하면 추후에 근본적인 문제를 발생시킬 수 있다. 문맥 소실로 인해 발생하는 오류와 해결하는 방법을 살펴보자.
문자열 리터럴 타입과 마찬가지로 튜플 타입에서도 문제가 발생한다.
type Language = 'JavaScript' | 'TypeScript' | 'Python'
function setLanguage(language: Language) {
/* ... */
}
// Parameter is a (latitude, longitude) pair.
function panTo(where: [number, number]) {
/* ... */
}
panTo([10, 20]) // OK
const loc = [10, 20] // number[]
panTo(loc) // Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
위 코드처럼 오류가 뜨는데 타입 선언 제공해서
const loc: [number, number] = [10, 20]
panTo(loc) // OK
명시적으로 loc가 어떤 타입이라고 타입 체커에게 알려주면 오류가 발생하지 않는다.
또 다른 방법으론 as const를 사용해 상수 문맥을 제공하자. as const는 그 값이 내부까지 상수라는 사실을 타입스크립트에게 알린다. 하지만 as const는 너무 과하게 정확해서 readonly 타입이라 panTo의 매개변수로 전달할 수 없다.
const loc = [10, 20] as const // readonly [10, 20]
panTo(loc)
// ~~~ Type 'readonly [10, 20]' is 'readonly'
// and cannot be assigned to the mutable type '[number, number]'
따라서 panTo 함수에 readonly 구문을 추가하면 오류를 고칠 수 있다.
function panTo(where: readonly [number, number]) {
/* ... */
}
const loc = [10, 20] as const
panTo(loc) // OK
as const는 문맥 손실과 관련한 문제를 해결할 수 있지만, 단점을 가지고 있다.
만약 타입 정의에 실수가 있다면 오류는 타입 정의가 아니라 호출되는 곳에서 발생한다는 것이다.
문맥에서 값을 분리하는 문제는 문자열 리터럴이나 튜플을 포함하는 큰 객체에서 상수를 뽑아낼 때도 발생한다.
type Language = 'JavaScript' | 'TypeScript' | 'Python'
interface GovernedLanguage {
language: Language
organization: string
}
function complain(language: GovernedLanguage) {
/* ... */
}
complain({ language: 'TypeScript', organization: 'Microsoft' }) // OK
const ts = {
language: 'TypeScript',
organization: 'Microsoft',
}
complain(ts)
// ~~ Argument of type '{ language: string; organization: string; }'
// is not assignable to parameter of type 'GovernedLanguage'
// Types of property 'language' are incompatible
// Type 'string' is not assignable to type 'Language'
이를 해결할려면
const ts: GovernedLanguage = {
// ...
}
콜백을 다른 함수로 전달할 때, 타입스크립트는 콜백의 매개변수 타입을 추론하기 위해 문맥을 사용한다.
function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
fn(Math.random(), Math.random())
}
callWithRandomNumbers((a, b) => {
a // Type is number
b // Type is number
console.log(a + b)
})
a와 b의 타입이 number로 추론되는데 콜백을 상수로 뽑아내면 문맥이 소실되고 noImplicitAny 오류가 발생한다.
function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
fn(Math.random(), Math.random())
}
const fn = (a, b) => {
// Parameter 'a' implicitly has an 'any' type.
// Parameter 'b' implicitly has an 'any' type.
console.log(a + b)
}
callWithRandomNumbers(fn)
이는 매개변수에 타입 구문을 추가해서 해결할 수 있다.
const fn = (a: number, b: number) => {
console.log(a + b)
}
callWithRandomNumbers(fn)
또는 전체 함수 표현식에 타입 선언하자
type callbackType = (n1: number, n2: number) => void
function callWithRandomNumbers(fn: callbackType) {
fn(Math.random(), Math.random())
}
const fn: callbackType = (a, b) => {
console.log(a + b)
}
callWithRandomNumbers(fn)
ts에서 어떤 작업을 할때는 절차형, 함수형, 라이브러리 사용 중에서는 라이브러리를 사용하는 것이 가장 좋은 방법입니다.
ts는 js의 정적 타입 시스템을 가지고 있어 코드를 작성할 때 타입 오류를 미리 방지할 수 있다. 이러한 특징으로 인해 함수형 프로그래밍 기법과 함께 사용하면 더욱 강력한 타입 시스템을 구축할 수 있다.
함수형 프로그래밍은 부작용을 최소화하고 함수의 입력과 출력만으로 프로그램의 상태를 관리하는 프로그래밍 패러다임이다. ts에서 함수형 프로그래밍을 적용하는 방법은 다음과 같다.
function add(a: number, b: number): number {
return a + b;
}
interface Person {
readonly name: string;
readonly age: number;
}
function birthday(person: Readonly<Person>): Person {
return { ...person, age: person.age + 1 };
}
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
const doubled = map([1, 2, 3], x => x * 2); // [2, 4, 6]
ts에서는 함수형 라이브러리도 다양하게 제공됩니다. 예를 들어, Lodash 와 같은 라이브러리는 함수형 프로그래밍을 쉽게 적용할 수 있도록 다양한 함수를 제공한다. 이러한 라이브러리를 사용하면 타입스크립트의 강력한 타입 시스템을 쉽게 활용할 수 있다.
타입 흐름을 개선하고, 가독성을 높이고, 명시적인 타입 구문의 필요성을 줄이기 위해 직접 구현하기보다는 내장된 함수형 기법과 Lodash 같은 유틸리티 라이브러리를 사용하자.