[TypeScript] 엄격한 타입 strict 옵션 적용
strict 옵션이란?
정의해놓은 코드에서 강하게 타입을 정의할 수 있고 추후에 일어날 수 있는 타입 정의에 대한 오류 대응할 수 있고 안전하게 코딩을 할 수있게 한다.
tsconfig.json에서 strick 옵션은 default가 false이며 true로 고쳐주면 아래의 옵션들도 같이 true가 된다.
{
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
}
내장 타입의 위계 구조 타입 오류
// 타입스크립트 내장 타입의 위계 구조 아래로 갈수록 하위
// const a: Element
// const b: HTMLElement
// const c: HTMLDivElement
// const evt1: Event
// const evt2: UIEvent
// const evt3: MouseEvent
async function handleListClick(event: Event) {
// MouseEvent는 UIEvent를 상속, UIEvent는 Event를 상속
null 타입 오류 / Object is possibly 'null'.ts(2531) (null일 수도 있다)
if (
event.target instanceof HTMLParagraphElement ||
event.target instanceof HTMLSpanElement
) {
selectedId = event.target.parentElement.id;
}
if (
event.target instanceof HTMLParagraphElement ||
event.target instanceof HTMLSpanElement
) {
if (!event.target.parentElemnt) {
return;
}
selectedId = event.target.parentElement.id
}
위와 같이 존재하지 않으면 return으로 종료하여 해결할 수도 있고 아래와 같이 삼항연산자를 이용해도 된다.
if (
event.target instanceof HTMLParagraphElement ||
event.target instanceof HTMLSpanElement
) {
selectedId = event.target.parentElement // 있으면 id 찾기
? event.target.parentElement.id
: undefined; // null 타입 오류 해결
}
Type 'undefined' is not assignable to type 'string'. ts(2345)
fetchCountryInfo에 countryName을 string으로 받을 수있게 했기 때문에 아래와 같이 오류가 생긴다.
string에 undefined까지 추가해준다.
타입단언
querySelector는 Element 또는 null이 단언 타입이다.
! type assertion로 null이 아니라고 단언을 하면 eslint에서 Forbidden non-null assertion이라는 에러가 뜬다.
interface Person {
name: string;
skill: string;
}
const ian: Person = {
name: 'ianlee',
skill: 'react',
};
// type annotation ":"
// 정의만하고 속성, 객체 정의하지 않으면 타입오류가 남(장점)
const ian: Person = {};
위와 같이 Person이라고 인터페이스를 주었을 때 name, skill 스펙을 가지고 타입 정의를 할 수 있고,
정의만하고 name, skill을 정의하지 않으면 오류가 나서 타입스크립트의 장점을 얻을 수 있다.
// as 타입단언(assertion) 객체에 Person라고 하는 인터페이스를 둘거다
// 타입 오류가 나지 않으므로 초기값을 정하지 않은 누락 등 에러 발생 가능
const ian = {} as Person;
// ian.name = 'ianlee';
// ian.skill = 'react';
타입단언 as를 쓰게되면 Person 인터페이스를 줘서 ian의 name, skill 타입을 정의하지 않아도 오류가 발생하지 않아 초기값 누락에 대한 에러가 발생할 수 있기 때문에 주의해야 한다.
// non-null type assertion ! 값이있다고 확신
const a: string | null;
a!
마찬가지로 non-null assertion " ! "을 이용해 값이 있다고 해버리면 보이지 않는 사각지대에서 에러가 일어날 수 있다.
그러므로 타입에 대한 확신이 있을때 이용하는 것을 권장한다.
옵셔널 체이닝 연산자 ?
Type 'null' is not assignable to type 'string'. ts(2322)는 null대신 빈문자열 ''을 넣어주면 해결된다.
Object is possibly 'null'.ts(2531) 는 해결하는 방법이 여러가지가 있다.
// 1. Object가 null일수도 있으니 조건문을 이용해서 해결
function clearDeathList() {
if (!deathsList) {
return;
}
deathsList.innerHTML = '';
}
// 2. 옵셔널 체이닝 연산자 ? 사용
recoveredList?.appendChild(li);
// 위와 밑의 조건문은 같은 의미다
if (recoveredList === null || recoveredList === undefined) {
return;
} else {
recoveredList.appendChild(li);
}
// 3. Element 타입 정하기
const rankList = $('.rank-list') as HTMLOListElement;
const deathsList = $('.deaths-list') as HTMLOListElement;
const recoveredList = $('.recovered-list') as HTMLOListElement;
// index.html에서 <ol>에 있으므로 HTMLOListElement로 단언한다
우선순위는 !보다는 ?를 쓰는 것이 좋다.
DOM 유틸 함수
DOM 접근 유틸함수 접근하기 위해 $를 붙여주고 타입단언을 이용하였다.
반환값이 Element 또는 null이므로 null이 아니라고 선언해줘야 했고 타입단언을 지정해줘야했다.
// utils
// 아래 DOM에서 document.querySelector('.confirmed-total') 대신 $('.confirmed-total')로 줄여쓸 수 있다
function $(selector: string) {
return document.querySelector(selector);
}
// DOM
// let a: Element | HTMLElement | HTMLParagraphElement;
const confirmedTotal = $('.confirmed-total') as HTMLSpanElement; // 타입단언
const deathsTotal = $('.deaths') as HTMLParagraphElement;
제네릭을 통해 as를 없애고 구체적인 DOM함수 클래스 타입을 넘길 수 있게 한다.
HTMLElement라는 제약조건을 통해 호환이 될 수 있는 하위 타입들만 들어갈 수 있게한다.
// utils
// 제네릭타입 default값을 HTMLDivElement으로 기본값설정 Div면 타입넘길 필요 없어짐
function $<T extends HTMLElement = HTMLDivElement>(selector: string) {
const element = document.querySelector(selector);
return element as T;
}
// DOM
const confirmedTotal = $<HTMLSpanElement>('.confirmed-total');
const deathsTotal = $<HTMLParagraphElement>('.deaths');