아이템19. 추론 가능한 타입을 사용해 장황한 코드 방지하기
생각한 것보다 타입추론이 더 정확한 경우가 있습니다.
const x: string = 'x'; // string 타입 const y = 'y'; // "y" 타입
타입 추론이 충분히 가능한 경우에는 추론을 이용하는 것이 좋습니다.
- 객체 리터럴을 정의할 때는 반드시 타입을 정의합니다. => 잉여 속성 체크가 동작하여 명확해 집니다.
함수/메서드 시그니처에는 타입 구문을 포함하지만, 함수 내에서 생성된 지역변수에는 타입구문을 최대한 생략하는 것이 이상적입니다.
// getQuote의 리턴 타입 구문을 미리 작성한 후에 내용을 작성한다면 실수나 예기치 않은 오류를 방지 할 수 있습니다. const cache: {[ticker: string]: number } = {}; function getQuote(ticker: string) { // Promise<any> // 리턴 타입을 명시하는 것이 좋습니다. if(ticker in cache) return cache[ticker]; // Promise.resolve(cache[ticker]); // 로 변경되어야 합니다. return fetch(`https://quotes.example.com/?q=${ticker}`) .then(response => response.json()) .then(quote => { cache[ticker] = quote; // quote는 any 타입이기 때문에 타입 검사 후 할당해야 합니다. return quote; }) }
아이템20. 타른 타입에는 다른 변수 사용하기
자바스크립트에서는 한 변수를 다른 타입으로 재사용 가능합니다.
let id = "12-34-56";
fetchProduct(id); // string 으로 사용
id = 123456;
fetchProductBySerialNumber(id); // number로 사용
타입스크립트에서는 타입 변경이 불가합니다.(범위를 좁히는 변경은 가능. 유니온 타입) 그리고 타입이 다른 변수는 아래의 이유와 같이 별도로 사용하는 것이 바람직합니다.
- 서로 관련이 없는 두 개의 값을 분리합니다.
- 변수명을 더 구체적으로 지을 수 있습니다.
- 타입 추론을 향상시키며, 타입 구문이 불필요해집니다.
- 타입이 좀 더 간결해집니다.
let
대신const
로 변수를 선언하게 됩니다.const
를 사용하면 코드가 간결해지고 타입 체커가 타입을 추론하기에 좋습니다.
아이템21. 타입 넓히기
타입스크립트는 타입을 추론할 때 타입 넓히기를 사용합니다. 명확성과 유연성 사이의 균형을 위해서 타입을 추론하여 결정합니다. 하지만 의도하지 않은 타입이 추론될 수 있기 때문에 적절히 제어하는 방법에 익숙해져야 합니다.
let
보다는const
를 사용합니다.const
는 변수의 값을 변경할 수 없도록 제한하므로 타입을 좁게 추론하도록 도와줍니다.const a = 'a'; // "a" let b = 'b'; // string
- 명시적 타입 구문을 사용합니다.
const v: {x: 1|3|5} = { // { x: 1|3|5 } x: 1, };
readonly
,const 단언문(as const)
을 사용합니다. 불변한 상수를 선언하므로 가장 좁은 타입으로 추론합니다.
const v3 = {
x: 1,
y: 2
} as const; // { readonly x:number; readonly y:number }
const a1 = [1, 2, 3]; // 배열
const a2 = [1, 2, 3] as const; // 튜플
아이템22. 타입 좁히기
코드 내에서 타입이 명확할 수록 모호함이 줄어 들고 예기치 않은 오류를 줄일 수 있으므로, 최대한 타입이 좁게 추론되고 사용되도록 하는 것이 좋습니다.
- 적절히 분기분을 이용해서 타입을 좁혀야 합니다.
null
이나undefined
를 체크하면 타입을 좁힐 수 있습니다.instanceof
,typeof
도 적절히 사용하면 타입을 좁힐 수 있습니다. - 사용자 정의 타입 가드를 이용하여 타입을 구별하는 일종의 helper 함수를 만들 수 있습니다.
const jackson5 = ['Jackie', 'Tito', 'Jermaine', 'Marlon', 'Michael'];
// return 값이 true라면 지정한 타입임을 추론할 수 있도록 해줍니다.
function isDefined<T>(val: T | undefined): val is T {
return val !== undefined;
}
const members = ['Janet', 'Michael']
.map( who => jackson5.find(n => n === who))
// .filter( who => who !== undefined); // 여전히 (string | undefined)[] 타입
.filter(isDefined); // string[] 타입
console.log(members); // ["Michael"]
아이템23. 한꺼번에 객체 생성하기
객체를 생성할 때 타입 추론에 유리하도록 하기 위해서는 속성을 포함해서 한번에 생성해야 합니다.
const pt= {};
pt.x = 3; // 오류: 타입 {}에 할당 불가
pt.y = 4; // 오류: 타입 {}에 할당 불가
spread 연산자를 통해서 한번에 객체를 선언할 수 있습니다.
interface Point { x: number; y: number; }
const pt0 = {};
const pt1 = {...pt0, x: 3 };
const pt: Point = {...pt1, y: 4 }; // 정상
삼항연산자를 사용하여 optional 속성이 추론되도록 할 수 있습니다.
declare let hasMiddle: boolean;
const firstLast = { first: 'Harry', last: 'Truman' };
const president = { ...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
// 추론된 타입
// const president: {
// middle?: string | undefined;
// first: string;
// last: string;
// }
아이템24. 일관성 있는 별칭 사용하기
- 별칭을 사용할 때 참조값이 사용된다는 점을 항상 고려해야합니다.
const borough = { name: 'Brooklyn', location: [40.688, -73.979]};
const loc = borough.location;
loc[0] = 1;
console.log(loc, borough);
// [1, -73.979],
// {
// "name": "Brooklyn",
// "location": [
// 1,
// -73.979
// ]
// }
- 객체 비구조화를 이용해서 별칭과 원본의 이름을 동일하게 사용하는 것이 좋습니다.
- 함수의 호출이 객체 속성의 타입 정제(타입 좁히기 등)을 무효화할 수 있다는 점을 주의해야합니다. 문제를 최소화 하기 위해서 객체나 속성을 사용하기 보다는 지역변수(별칭)을 사용하는 것이 좋습니다.
아이템25. 비동기 코드에는 콜백 대신 async 함수 사용하기
너무 명백하게 async/await
가 가독성, 직관적인 코드 작성에 도움이 되기 때문에 별 다르게 설명할 내용이 없습니다.
- 콜백보다 프로미스를 사용하는게 코드 작성과 타입 추론면에서 유리합니다.
async/await
를 사용하면 간결하고 직관적인 코드를 작성할 수 있고 모든 종류의 오류를 제거할 수 있습니다.- 어떤 함수가 프로미스를 반환한다면 async로 선언하는 것이 좋습니다.
아이템26. 타입 추론에 문맥이 어떻게 사용되는지 이해하기
- 인라인 형태로 값을 할당할 때와 변수로 값을 할당할 때 타입 추론의 결과가 달라질 수 있다는 것을 이해해야 합니다.
type Language = 'JavaScript' | 'Typescript' | 'Python' ;
function setLanguage(lang: Language){} ;
setLanguage('JavaScript'); // 정상: 인라인으로 할당 할때 문맥을 고려하여 적절히 타입 추론함
let lang = 'JavaScript';
setLanguage(lang); // 오류: lang이 string으로 추론
const lang1 = 'JavaScript';
setLanguage(lang1); // 정상: lang을 "JavaScript"으로 추론
let lang2: Language = 'JavaScript';
setLanguage(lang2); // 정상: lang에 타입 명시
- 튜플 사용시에도 위 문자열의 경우와 동일한 문제가 발생합니다. 변수에 할당할 때는 배열로 타입을 추론하는 것에 주의해야합니다.
function panTo(where: [number, number]) {}
panTo([10, 20]); // 정상
const loc = [10, 20];
panTo(loc); // 오류: loc이 배열로 추론되었음
const loc1: [number, number] = [10, 20];
panTo(loc1); // 정상
const loc2 = [10, 20] as const;
panTo(loc2); // 오류: 튜플로 추론하긴했지만 readonly [number, number] 로 추론함
function panTo2(where: readonly [number, number]) {}
const loc3 = [10, 20] as const;
panTo2(loc3); // 정상: 함수 시그니처를 변경함. 함수 시그니처 변경이 불가능할 경우에는 타입을 명시하는 방식 사용
- 객체와 콜백을 사용할 때에도 리터럴로 바로 할당하는 경우라면 적절하게 타입을 추론하지만, 변수로 할당하게 되는 경우라면 필요로 하는 타입을 추론하지 못합니다.
아이템27. 함수형 기법과 라이브러리로 타입 흐름 유지하기
- 타입 흐름을 개선하고, 가독성을 높이고, 명시적인 타입 구문의 필요성을 줄이기 위해 직접 구현하기보다는 내장된 함수형 기법과 로대시 같은 유틸리티 라이브러리를 사용하는 것이 좋습니다.