TS가 작성된 코드를 체크하는 정적 분석 시점에, 변수는
가능한
값들의 집합인 타입을 가진다.
만약 상수를 사용하여 변수를 초기화할 때 타입을 명시하지 않으면 타입 체커는 지정된 단일 값으로 할당 가능한 값들의 집합을 유추한다.
이렇게 할당 가능한 값들의 집합을 유추하는 과정을 타입 넓히기라고 한다.
// Type 'x' const x = 'x'; // Type string let x = 'x';
const
const
키워드는 재할당을 허용하지 않기 때문에 더욱 좁은 타입으로 추론할 수 있다.
하지만 객체나 배열의 경우에는 프로퍼티가 재할당 가능하기 때문에 const
만으로는 프로퍼티의 타입 넓히기를 제어할 수 없다.
const v: { x: 1 | 3 | 5 } = {
x: 1,
};
아이템 26에서 상세하게 다룰 예정
const
단언문 사용 const v1 = {
x: 1,
y: 2
} // { x: number; y: number; }
const v2 = {
x: 1 as const,
y: 2
} // { x: 1; y: number; }
const v3 = {
x: 1,
y: 2
} as const; // { readonly x: 1; readonly y: 2; }
const a1 = [1, 2, 3]; // number[]
const a2 = [1, 2, 3]; as const // readonly [1, 2, 3]
null
체크const el = document.getElementById('foo'); // 타입이 HTMLElement | null
if (el) {
el; // HTMLElement
} else {
// null
}
instanceof
Array.isArray
같은 내장 함수typeof
tagged union
)사용자 정의 타입 가드
)function isInputElement(el: HTMLElement): el is HTMLInputElement {
return 'value' in el;
}
function getElementContent(el: HTMLElement) {
if (isInputElement(el)) {
el; // HTMLInputElement
return el.value;
}
el; // HTMLElement
return el.textContent;
}
declare let hasDates: boolean;
const nameTitle = { name: 'Khufu', title: 'Pharaoh' };
// 아래의 방식은 유니온으로 추론되기 때문에 다루기 까다롭다.
// const pharaoh: {
// start?: number | undefined;
// end?: number | undefined;
// name: string;
// title: string;
// }
const pharaoh = {
...nameTitle,
...(hasDates ? { start: -2589, end: -2566 } : {}),
};
function addOptional<T extends object, U extends object>(
a: T,
b: U | null
): T & Partial<U> {
return { ...a, ...b };
}
// 이렇게 사용하는 것이 훨씬 효율적이다.
// const pharaoh2: {
// name: string;
// title: string;
// } & Partial<{
// start: number;
// end: number;
// }>
const pharaoh2 = addOptional(
nameTitle,
hasDates ? { start: -2589, end: -2566 } : null
);
해당 장에서 말하는 부분은 쉽게 표현하면 아래와 같다.
// 이렇게 별칭을 사용하기 보다
const box = polygon.box;
// 이렇게 비구조화 문법을 사용하라
const { box } = polygon;
async
함수 사용하기콜백보다는 프로미스, 프로미스보다는
async/await
을 사용하는 것이타입 추론
,간결하고 직관적인 코드 작성
,모든 종류의 오류 제거
측면에서 유리하다.
async/await
을 사용해야하는 이유async/await
을 사용해야하는 이유async
함수는 항상 프로미스를 반환하도록 강제된다.async function fetchPages() {
const [res1, res2, res3] = await Promise.all([fetch(url1), fetch(url2), fetch(url3)])
...
}
비교가 안될정도로 차이가 심하다!
function fetchPagesCB() {
let numDone = 0;
const reses: string[] = [];
const done = () => {
const [res1, res2, res3] = reses;
...
}
urls.forEach((url, i) => {
fetchURL(url, r => {
reses[i] = url
numDone++
if (numDone === urls.length) done();
})
}
}
as const
)을 사용하면 된다.유효한 상태만 표현한다는 것은 특정 상태를 관리할 때 사용되는 속성만 갖도록 표현하라는 것이다.
만약 응답에 대한 로딩상태, 에러, 성공을 관리하는 상태가 있다고 가정한다면 아래와 같은 형태일 것이다.
interface state {
isLoading: boolean;
error: boolean;
data: string;
}
위와 같은 형태도 어색하지는 않다 말 그대로 상태를 관리하는 것이니 로딩 성공 여부와 에러 발생 여부 성공 데이터가 담겨있는 형태이기 때문에 추상화는 잘못되었다고 표현할 수 없다.
하지만 이렇게 되면 케이스 분기 처리가 복잡해질 수 있다.
때문에 아래와 같이 작성한다면 상태를 정의하는 코드는 길어지겠지만 분기 처리가 용이하다.
interface RequestPending {
state: 'pending';
}
interface RequestError {
state: 'error';
error: string;
}
interface RequestSuccess {
state: 'ok';
data: string;
}
type RequestState = RequestPending | RequestError | RequestSuccess;
interface State {
currentPage: string;
requests: { [page: string]: RequestState };
}
function renderPage(state: State) {
const { currentPage } = state;
const requestState = state.requests[currentPage];
switch (requestState.state) {
case 'pending':
return `Loading ${currentPage}...`;
case 'error':
return `Error! Unable to load ${currentPage}: ${requestState.error}`;
case 'ok':
return `<h1>${currentPage}</h1>\n${requestState.data}`;
}
}
반환 타입
)와 느슨한 형태(매개변수 타입
)를 도입하는 것이 좋다.