
타입스크립트는 타입 추론을 적극적으로 수행한다.
이 타입 추론은 수동으로 명시해야 하는 타입 구문의 수를 매우 줄여 주기 떄문에, 코드의 전체적인 안정성이 향상된다.
타입스크립트는 결국 타입을 위한 언어이기 때문에, 변수를 선언할 때마다 타입을 명시해야 한다고 생각할 수 있지만, 사실 많은 타입 구문은 불필요하다.
//example 1
let x:number = 1;
let x = 1; // 이렇게만 써도 x의 타입을 number로 추론한다.
//example 2
const person: {
name: string;
born: {
where :string;
when: string;
}
died: {
where: string;
when: string;
}
} = {
name: 'Sodd',
born: {
where: 'NY',
when: 'c.111'
},
died: {
where : 'NT',
when: 'Nbv. 120'
}
}
const person1 = {
name: 'Sodd',
born: {
where: 'NY',
when: 'c.111'
},
died: {
where : 'NT',
when: 'Nbv. 120'
}
} // 이렇게만 써도 충분하다.
➡️ 타입 추론은 우리보다 더 정확하게 작동할 때도 있다.
const axis1 : string = 'x'; // 타입은 string
const axis2 = 'y' // 타입은 y
// axis2의 타입이 string으로 생각할 수 있으나 'y'가 더 정확한 타입이다.
➡️ 타입이 추론되면 리팩터링 역시 용이해진다.
interface Product {
id : number;
nmae: string;
price: number;
}
function logProduct (product: Product) {
const id : number = product.id;
const name: string= product.nmae;
const price: number = product.price;
console.log(id, name, price)
}
위 코드에서 id에 문자도 들어 있을 수 있음을 나중에 알게 되어 Product 내의 id 타입을 변경하면 함수안의 id의 타입과 맞지 않아 오류가 발생한다.
이는 함수안에 명시적 타입 구문이 있어 발생한 오류로 해당 부분을 없애면 통과하지만, 이 경우 비구조화 할당문을 이용하는 게 좋다.
interface Product {
id : number;
name: string;
price: number;
}
function logProduct (product: Product) {
const {id, name,price} = product;
console.log(id, name, price);
}
정보가 부족해 타입이 추론하지 못하는 경우엔 명시적 타입 구문이 필요하지만, 이 경우 product에서 해당 상황을 해결하였으니 함수 안에 추가적으로 명시적 타입 구문을 넣는 것은 불필요하다.
➡️ 이상적인 타입스크립트 코드는 함수/메서드 시그니처에 타입 구문을 포함하지만, 함수 내에서 생성된 지역 변수에는 타입 구문을 넣지 않는다.
타입 구문을 생략하는 경우도 있는데
// 아래와 같이 쓸 필요가 없다.
app.get('/health',(request: express.Request, response: express.Response) => {
response.send('OK')
})
//이렇게 쓰자.
app.get('/health',(request,response) => {
response.send('OK')
})
✔️ 타입이 추론될 수 있음에도 여전히 타입을 명시하고 싶은 몇 가지 상황이 있다.
// 이렇게 쓰면 잉여 속성 체크가 동작하지 않아 변수가 사용되는 순간에 오류가 발생한다.
const elmo = {
name: 'Tick',
id: '123213',
price: 292.11,
}
logProduct(elmo) // 여기 elmo서 오류
// 하지만 명시적으로 타입을 지정해주면 잉여 속성 체크가 동작해 할당되는 순간에 오류가 발생한다.
const furby: Product = {
name: 'Trinck',
id: 11111, // 여기서 타입 오류
price: 123,
}
logProduct(furby);
// 조회 종목을 다시 요청하지 않도록 캐시한 함수
const cache: {[ticker: string] : number} = {};
function getQuote(ticker:string) {
if(ticker in cache) {
return cache[ticker]
}
return fetch('https://quotes.example.com/?q=${ticker}')
.then(res => res.json())
.then(quote => {
cache[ticker] = quote;
return quote;
})
}
getQuote는 항상 Promise를 반환해야 하므로 if구문에는 cache[ticker] 가 아니라 Promise.resolve(cache[ticker]) 를 반환해야 한다.
위 오류는 getQuote 내부가 아닌 호출한 코드에서 발생한다.
따라서 의도된 반환 타입을 명시하여 정확한 위치에 오류가 발생하게 만들 수 있다.
❗구현상의 오류가 사용자 코드의 오류로 표시되는 경우를 방지한다.
function getQuote(ticker:string) : Promise<number> {/.../}
➡️ 위와 같이 오류 위치를 제대로 표시해 주는 이점 외에도, 반환 타입 명시해야 하는 이유가 있다.
interface Vector2D {x: number, y: number;}
function add(a: Vector2D, b:Vector2D) {
return {x: a.x + b.x, y: a.y + b.y}
}
위에서 add 함수의 반환 타입은 {x: number; y:number;} 로 추론되어 Vector2D 와 호환되지만, Vector2D로 나오지 않아 사용자 입장에서 혼란을 야기한다.
따라서 추론된 반환 타입이 복잡해질수록 명명된 타입을 제공하는 이점이 커진다.
☑️ TS가 타입을 추론할 수 있다면 타입 구문을 작성하지 않는 것이 좋다.
☑️ 이상적인 경우 함수/메서드 시그니처에는 타입 구문이 있지만, 함수 내 지역 변수에는 없다.
☑️ 추론될 수 있는 경우라도 객체 리터럴과 함수 반환에는 타입 명시를 고려할 것.
JS에서는 한 변수를 다른 목적을 가진 다른 타입으로 재사용해도 된다.
let id = 'adf'
fetchProduct(id) // id의 type은 string
id = 123;
fetchProductBySerialNum(id) // id의 type은 number
하지만 TS에서는 두 가지 오류가 발생한다.
let id = 'adf'
fetchProduct(id) // id의 type은 string
id = 123; // error1 > string형식에 할당할 수 없음
fetchProductBySerialNum(id) // error2 > string형식의 인수는 number 형식의 매개변수에 할당 불가
여기서 '변수의 값은 바뀔 수 있지만 그 타입은 보통 바뀌지 않는다' 는 관점을 알 수 있다.
위 에러를 해결하려면 string 과 number를 모두 포함할 수 있도록 타입을 확장하면 된다.
(유니온 타입)
let id: string | number = '12';
하지만, 위와 같이 유니온 타입을 사용하면 id를 사용할 때마다 매번 값이 어떤 타입인지 확인해야 하기 때문에 차라리 별도의 변수를 도입하는 것이 낫다.
const id = '12' ;
const serial = 123123;
아무 관련 없는 값을 같은 변수에 무분별하게 재사용하면 혼란을 야기한다.
➡️별도의 변수를 사용하는 것이 바람직한 이유는 다음과 같다.
☑️ 변수의 값은 바뀔 수 있지만 타입은 일반적으로 바뀌지 않는다.
☑️ 혼란을 막기 위해 타입이 다른 값을 다룰 때에는 변수를 재사용하지 않도록 한다.