이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리
📍요약
✅타입 추론이 가능하다면, 타입 구문을 명시하지 않는게 좋다
✅가능한 함수 시그니처에는 타입을 명시하고, 실제 함수의 지역 변수에는 타입 구문을 명시하지 않는다
✅타입 추론이 가능한 경우에도 객체 리터럴과 함수 반환값에는 타입 명시를 고려할 필요가 있다
(내부 구현의 오류가 사용자 코드 위치에 나타나는 것을 방지)
📍타입 추론의 장점
수동으로 명시해야 하는 타입 구문의 수를 줄여, 코드의 전체적인 안정성 향상
- 따라서 필요한 곳에만 타이핑해야 한다
📍타입 추론을 사용해야 하는 이유
복잡한 객체도 타입 추론 가능
- 타입을 생략해도 된다
const person = {
name: 'Sojourner Truth',
born: {
where: 'Swartekill, NY',
when: 'c.1797',
},
died: {
where: 'Battle Creek, MI',
when: 'Nov. 26, 1883'
}
};
타입스크립트가 더 정확할 수도 있다
const axis1: string = 'x'; // Type is string
const axis2 = 'y'; // Type is "y"
let axis2 = 'y'; // Type is string
- axis2의 타입은 "y"
(string이 아님)
⭐그런데 const 대신 let을 쓰면 문자열로 타입 추론한다
📍구조분해할당을 써야 하는 이유
- 구조분해할당은 모든 지역 변수의 타입이 추론되도록 함
📍기본값이 있는 매개변수도 타입이 추론된다
📍타입 정보가 있는 라이브러리에서 콜백 함수의 매개변수 타입도 자동으로 추론됨
- express 라이브러리 예시
// HIDE
namespace express {
export interface Request {}
export interface Response {
send(text: string): void;
}
}
interface App {
get(path: string, cb: (request: express.Request, response: express.Response) => void): void;
}
const app: App = null!;
// END
// Don't do this:
app.get('/health', (request: express.Request, response: express.Response) => {
response.send('OK');
});
// Do this:
app.get('/health', (request, response) => {
response.send('OK');
});
- request, response 객체의 타입을 할당할 필요 없음
📍타입 추론될 수 있지만, 타입 명시가 필요한 경우1
- 객체 리터럴의 타입을 명시할 때
interface Product {
id: string;
name: string;
price: number;
}
function logProduct(product: Product) {
const id: number = product.id;
// ~~ Type 'string' is not assignable to type 'number'
const name: string = product.name;
const price: number = product.price;
console.log(id, name, price);
}
// 타입 명시 -> 실제로 에러 발생하는 부분을 감지. Good
const furby: Product = {
name: 'Furby',
id: 630509430963,
// ~~ Type 'number' is not assignable to type 'string'
price: 35,
};
logProduct(furby);
// 타입 추론 -> 객체가 쓰이는 곳 (매개변수) 에서 에러 발생. Bad
const furby = {
name: 'Furby',
id: 630509430963,
price: 35,
};
logProduct(furby);
// ~~~~~ Argument .. is not assignable to parameter of type 'Product'
// Types of property 'id' are incompatible
// Type 'number' is not assignable to type 'string'
📍타입 추론될 수 있지만, 타입 명시가 필요한 경우2
- HTTP 요청을 보내고 응답값을 타이핑할 때
- 예시) 주식 가격 API 에 요청보내는 함수
// 받아온 응답이 있으면 캐싱
const cache: {[ticker: string]: number} = {};
// 주식 가격 조회 비동기 함수
function getQuote(ticker: string) {
// 캐싱된 결과를 리턴
if (ticker in cache) {
return cache[ticker]; // 반환1
}
// 없으면 HTTP 요청
return fetch(`https://quotes.example.com/?q=${ticker}`)
.then(response => response.json())
.then(quote => {
cache[ticker] = quote;
return quote; // 반환2
});
}
그런데 이 코드에 오류가 있다
- fetch 함수의 반환값은 무조건 Promise이므로, 반환2의 결과는 Promise이다
- 따라서 캐시된 반환1도 Promise이어야 한
그런데 반환 타입을 정하지 않으면, 실제 에러의 원인이 아닌 곳에서 에러가 발생한다
function considerBuying(x: any) {}
// 엄한 곳에서 에러 발생
getQuote('MSFT').then(considerBuying);
// ~~~~ Property 'then' does not exist on type
// 'number | Promise<any>'
// Property 'then' does not exist on type 'number'
⭐반환 타입을 명시해주면 정확한 위치에 에러가 표시된다
- 반환 타입을 Promise<number> 로 명시
const cache: {[ticker: string]: number} = {};
function getQuote(ticker: string): Promise<number> {
if (ticker in cache) {
return cache[ticker];
// ~~~~~~~~~~~~~ Type 'number' is not assignable to 'Promise<number>'
}
// COMPRESS
return Promise.resolve(0);
// END
}
📍타입 추론될 수 있지만, 타입 명시가 필요한 경우3
- 명명된 타입을 사용하기 위해
- 예시)
interface Vector2D { x: number; y: number; }
function add(a: Vector2D, b: Vector2D) {
return { x: a.x + b.x, y: a.y + b.y };
}
/* 함수 시그니처
add(a: Vector2D, b: Vector2D): {
x: number;
y: number;
} -> 단순히 number 객체로 추론됨
명명된 타입이 반환 타입으로 지정되면 좋겠어!
*/
// 반환 타입에 명명된 타입으로 표시됨
function add(a: Vector2D, b: Vector2D): Vector2D {
return { x: a.x + b.x, y: a.y + b.y };
}