const axis1: string = 'x'; // 타입은 string
const axis2 = 'y'; // 타입은 "y"
interface Product{
id: string;
name: string;
price: number;
}
// 비구조화 할당문
function logProduct(product: Product){
const {id, name, price} = product;
console.log(id, name, price);
}
타입스크립트에서 변수의 타입은 처음 등장 할 때 결정된다.
함수 매개변수에 타입 구문을 생략하는 경우
1. 기본 값이 있는 경우
// base = 10으로 초기화 -> 타입은 number로 추론
function paraseNumber(str: string, base = 10){...}
2. 타입 정보가 있는 라이브러리에서, 콜백 함수의 매개변수 타입은 자동으로 추론 됨
// express HTTP 서버 라이브러리를 사용하는 request와 response의 타입 선언은 불필요
// bad
app.get('/health', (request: express.Request, response: express.Response) => {
response.send('ok');
})
// good
app.get('/health', (request, response) => {
response.send('ok');
})
1. 객체 리터럴을 정의할 때
엄격한 객체 리터럴 체크(잉여속성체크)가 동작함
-> 선택적 속성이 있는 타입의 오타를 잘 잡는다.
-> 할당하는 시점(실제로 실수가 발생한 부분)에 오류를 표시
2. 함수의 반환에 타입을 명시하자
- 오류의 위치를 정확히 표시해준다.
- 반환 타입을 명시하면 함수에 대해 더욱 명확하게 알 수 있다.
- 명명된 타입을 사용하기 위해서다.
// add의 매개변수처럼 함수의 반환에도 타입을 입력해주자
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;}로 추론했다.
// add함수의 매개변수는 명명 타입을 가지지만, 추론된 반환타입은 그렇지 않다.
no-inferrable-types
: 작성된 모든 타입 구문에 정말로 필요한지 확인할 수 있다.🎯 요약
타입스크립트의 타입 구문을 필요시에 적절히 잘 사용해야한다.
변수의 값은 바뀔 수 있지만 그 타입은 보통 바뀌지 않는다.
다른 타입에는 유니온 대신 별도의 변수를 사용하자
- 서로 관련이 없는 두 개의 값을 분리한다.
- 변수명을 더 구체적으로 지을 수 있다.
- 타입 추론을 향상시키며, 타입 구문이 불필요해진다.
- 타입이 좀 더 간결해진다.
(string|number) -> string 또는 number
let
대신const
로 변수를 선언하게 된다.
변수의 유효범위를 주의하자(가려지는 변수)
const id = '123'
fetchProduct(id);
{
const id = 123; // 정상
fetchProduct(id); // 정상
}
🎯 요약
타입이 다른 값을 사용할 때는 변수를 재사용하지 말자
타입스크립트가 코드를 체크하는 시점(정적 분석 시점)에 변수는 '가능한' 값들의 집합인 타입을 가진다. (= 넓히기)
-> 타입 넓히기가 진행되면, 주어진 값으로 추론 가능한 타입이 여러 개 이기 때문에 모호하다.
넓히기 제어하기
1. 객체 와 배열을 제외하고, const 사용
interface Vector3{x: number; y: number; z: number;}
function getComponent(vector: Vector3d, axis: 'x' | 'y' | 'z'){
return vector[axis];
}
// bad
let x = 'x';
let vec = {x: 10, y: 20, z: 30};
getComponent(vec,x); // 오류
// -> 'string' 형식의 인수는 'x' | 'y' | 'z' 형식의 매개변수에 할당될 수 없다.
// good
// x는 재할당 불가
const x = 'x';
let vec = {x: 10, y: 20, z: 30};
getComponent(vec,x); // 정상 , x: 'x'
2. 객체의 const
2-1 객체의 const에서 타입스크립트의 넓히기 알고리즘은 각 요소를 let으로 할당된 것처럼 다룬다.
const v = {
x: 1,
}; // v의 타입은 {x: number}
v.x = 3; // 정상
v.x = '3'; // '"3"' 형식은 'number' 형식에 할당할 수 없습니다.
v.y = 4; // '{x: number;}' 형식에 'y' 속성이 없습니다.
v.name = 'lee'; // '{x: numer;}' 형식에 'name' 속성이 없습니다.
2-2 타입스크립트의 기본 동작을 재정의 하여 타입 추론의 강도를 직접 제어하자.
a. 명시적 타입 구문을 제공
const v: {x: 1|3|5} = {
x: 1,
}; // 타입이 {x: 1|3|5;}
b. 타입 체커에 추가적인 문맥을 제공
예) 함수의 매개변수로 값을 전달
c. const 단언문 사용
- 변수 const와 const 단언문(as const)은 다르다.
->as const
는 최대한 좁은 타입으로 추론
// 1. 변수 const
const v1 = {
x:1,
y:2,
}; // 타입은 {x: number; y: number;}
// 2. 속성 값 뒤에 const 단언
const v2 = {
x: 1 as const,
y: 2,
} // 타입은 {x: 1; y: number;}
// 3. 객체 const 단언
const v3 = {
x:1,
y:2,
} as const; // 타입은 {readonly x: 1; readonly y: 2;}
- 배열을 튜폴로 추론할 수 있다.
const arrayA = [1,2,3]; // 타입이 number[]
const tuplaeA = [1,2,3] as const; // 타입이 readonly [1,2,3]
🎯 요약
타입 넓히기를 이해하고, 제어하는 방법을 알자
조건문, instanceof, 속성체크, Array.isArray, 명시적 태그 붙이기(태그된 유니온, 구별된 유니온, (고유 타입)), 타입 가드(is)...
// 1. 조건문에서의 null 체크
// -> 단, typeof null 체크는 "object"이므로 제대로 좁혀지지 않을 수 있다.
// good
const el = document.getElementById('foo'); // 타입이 HTMLElement | null
if(el){
el // 타입이 HTMLElement
el.innerHTML = 'party time'.blink(); //.blink(): 텍스트를 깜빡이게 만드는 HTML요소메서드
}else{
el // 타입이 null
alert('No element #foo');
}
// bad
const el = document.getElementById('foo'); // 타입이 HTMLElement | null
if(typeof el === 'object'){
el // 타입이 HTMLElement | null
}
// 2. instanceof
function contains(text: string, serach: string|RegExp){
if(search instanceof RegExp){
search // 타입이 RegExp
return !!search.exec(text);
// search.exec(text)의 반환값이 null이 아니라면,
// 즉 일치하는 부분이 있을 경우에는 true로 변환
// 일치하는 부분이 없으면(null을 반환하면) false로 변환
}else{
search // 타입이 string
return text.includes(search);
}
}
// 3. 속성체크
interface A {a: number}
interface B {b: number}
function pickAB(ab: A|B){
if('a' in ab){
ab // 타입이 AB
}else{
ab // 타입이 B
}
ab // 타입이 A|B
}
// 4. Array.isArray
function contains(text: string, terms: string|string[]){
const termList = Array.isArray(terms) ? terms : [terms];
termList // 타입이 string[]
// ...
}
// 5. 명시적 태그(태그된 유니온, 구별된 유니온)
interface UploadEvent{
type: 'upload';
filename: string;
contents: string;
}
interface DownloadEvent{
type: 'download';
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent){
switch(e.type){
case 'download':
e // 타입이 DownloadEvent
break;
case 'upload':
e // 타입이 UploadEvent
break;
}
}
// 6. 타입 가드(is)
// bad -> undefined가 걸러지지 않았다.
const jackson5 = ['Jackie', 'Tito', 'Jermaine', 'Marion', 'Michael'];
const members = ['Janet', 'Michael']
.map(who => jackson5.find(n => n === who))
.filter(who => who !== undefined); // 타입이 (string|undefined)[]
//good
function isDefined<T>(x: T | undefined): x is T{
return x !== undefined;
}
const members = ['Janet', 'Michael']
.map(who => jackson5.find(n => n === who))
.filter(isDefined); // 타입이 string[]
🎯 요약
타입을 좁힐 수 있는 방법들을 알아두자
객체를 생성할 때는, 여러 속성을 한꺼번에 생성해야 타입 추론에 유리하다.
(타입 단언문(as)
로 각각 만들 수 있지만 별로다.)
객체 전개 연산자(...)
// 1. 작은 객체들(pt, id)에서 큰 객체 만들기(namedPoint)
const pt = {x: 3, y: 4};
const id = {name: 'lee'};
const namedPoint = {...pt, ...id};
namedPoint.name; // 정상, 타입이 string
// 2. 타입 걱정 없이 필드 단위로 객체 생성
// 모든 업데이트마다 새 변수를 사용하여 각각 새로운 타입을 얻도록 해야 함
interface Point { x: number, y: number};
const pt0 = {};
const pt1 = {...pt0, x: 3}; // x: number
const pt: Point = {...pt1, y: 4}; // Point
// 3. 안전한 방식으로 조건부 속성 추가 -> null 또는 {} 사용
declare let hasMiddle: boolean;
const firstLast = {first: 'lee', last: 'yoon'};
const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
/*
const president: {
middle?: string | undefined;
first: string;
last: string;
}*/
// 4. 한꺼번에 여러 속성 추가
declare let hasDates: boolean;
const nameTitle = {name: 'lee', title: 'hi'};
const pharaoh = {
...nameTitle,
...(hasDates ? {start: -2589, end: -2566} : {})
};
pharaoh.start
// 오류 -> '{name: string; title: string;}' 형식에 'start' 속성이 없습니다.
// pharaoh.start를 사용하고 싶다면 헬퍼 함수 사용
function addOptional<T extends object, U extends object>(
a: T, b: U | null): T & Partial<U>{
return {...a, ...b};
}
const pharaoh = addOptional(
nameTitle,
hasDates ? {start: -2589, end: -2566} : null);
pharaoh.start // 정상, 타입이 number | undefined;
🎯 요약
객체를 한꺼번에 만들자, 안전한 타입으로 속성을 추가하려면 객체 전개 연산자를 사용하자(...)
const borough = {name: 'Brooklyn', location: [40.688, -73.979]};
const loc = borough.location; // borough.location에 loc 별칭 사용
loc[0] = 0;
borough.location; // [0, -73.979] 원래 속성도 0으로 변경 됨
1. 별칭은 일관성 있게 사용하자.
2. 객체 비구조화(구조 분해)를 이용하자.
// 별칭 일관되게 사용, 객체 비구조화(구조 분해) 사용
function isPointInPolygon(polygon: Polygon, pt: Coordinate){
const {bbox} = polygon;
if(bbox){
const {x, y} = bbox;
if(pt.x < x[0] || pt.x > x[1] ||
pt.y < y[0] || pt.y > y[1]){
return false;
}
}
//...
}
구조 분해 사용시 주의점
2-1. 선택적 속싱일 경우, 타입의 경계(?)에 null 값을 추가하는 것이 좋다.
2-2. (?) 빈 배열은 'holes 없음'을 나타내는 좋은 방법이다.
3. 속성보다 지역변수를 사용하자.
-> 함수 호출이 객체 속성의 타입 정제를 무효화 할 수 있다.
function fn(p: Polygon){//...}
polygon.bbox; // 타입이 BoundingBox | undefined
if(polygon.bbox){
polyhon.bbox; // 타입이 BoundingBox
fn(polygon);
polyhon.bbox; // 타입이 BoundingBox
}
🎯 요약
타입 별칭을 사용할 때는, 신중하게
비동기 동작 모델링
ES2015 이전: 콜백 사용
ES2015: 프로미스(Promise) 사용
ES2017: async & await 사용
콜백보다 프로미스나 async & await를 사용하자.
🎯 요약
비동기 함수에서는 async & await를 사용하자
타입스크립트는 타입을 추론할 때 값과 값이 존재하는 곳의 문맥(코드의 위치)까지 살핀다.
문맥과 값을 분리했을 때 변수, 튜플, 객체, 콜백에서 생기는 문제점들
1. 변수 사용 시
타입 선언 또는 const 키워드 사용
type Language = 'JavaScript' | 'TypeScript' | 'Python';
function setLanguage(language: Language){ }
// 1. 인라인 형태
// ts가 -> 함수 선언을 통해 매개변수가 Language 타입이어야 한다는 것을 알음
setLanguage('JavaScript'); // 정상
// 2. 참조 형태
// ts가 -> 할당 시점에 타입을 추론함 -> string으로 추론
let language = 'JavaScript';
setLanguage(language);
// 'string' 형식의 인수는 'Language' 형식의 매개변수에 할당될 수 없습니다.
// 해결법1. 타입선언에서 language의 가능한 값을 제한하는 것
let language: Language = 'JavaScript';
setLanguage(language); // 정상
// 해결법2. language를 const를 사용해 상수로 만든다.
const language = 'JavaScript';
setLanguage(language); // 정상
2. 튜플 사용 시
function panTo(where: [number, number]){}
// 1. 인라인 형태
panTo([10,20]); // 정상
// 2. 참조 형태
const loc = [10,20];
panTo(loc);
// 'number[]' 형식의 인수는 '[number, number]' 형식의 매개변수에 할당 할 수 없다.
// 해결책 -> panTo 함수에 readonly 구문을 추가하고, as const 사용
function panTo(where: readonly [number, number]){}
const loc = [10,20] as const;
panTo(loc); // 정상
// 다만, 타입 정의에 실수가 있으면(ex. 튜플에 값 추가)
// 근본적인 오류가 어디서 났는지 알기 힘들다.
3. 객체 사용 시
타입선언(const a: b = ...)을 추가하거나 상수 단언(as const)를 사용한다.
4. 콜백 사용 시
매개변수에 타입 구문을 추가 하거나 전체 함수 표현식에 타입 선언을 적용
🎯 요약
타입 추론에 문맥이 어떻게 사용되는지 이해하여 대처하자
타입 흐름을 개선하고, 가독성을 톺이고, 명시적인 타입 구문의 필요성을 줄이자.
이는 직접 구현하기보다는 내장된 함수형 기법과 로대시 같은 유틸리티 라이브러리를 사용하자.
Array.prototype.map(내장 함수)보다 _.map(로대시)를 사용하려는 이유는?
콜백을 전달하는 대신 속성의 이름을 전달할 수 있다.
interface BasketballPlayer{
name: string;
team: string;
salary: number;
}
declare const rosters: {[team: string]: BasketballPlayer[]};
const allPlayers = Object.values(rosters).flat();
const namesA = allPlayers.map(player => player.name); // 타입이 string[]
const namesB = _.map(allPlayers, player => player.name); // 타입이 string[]
const namesC = _.map(allPlayers, 'name'); // 타입이 string[]
const salaries = _.map(allPlayers, 'salary'); // 타입이 number[]
const teams = _.map(allPlayers, 'team'); // 타입이 string[]
const mix = _.map(allPlayers, Math.random() < 0.5 ? 'name' : 'salary');
// 타입이 (string|number)[]
🎯 요약
직접 구현보다는 내장된 함수형 기법과 로대시 같은 유틸리티 라이브러리를 사용하자