똑같은 삽질은 2번 하지 말자
Effective Typescript vol.3 (Item 19 ~ 27) 본문
개요
이펙티브 타입스크립트라는 책을 읽고 장별(각각의 장안에 아이템별)로 학습한 내용 정리하기
이 책의 목표
독자에게 타입스크립트나 자바스크립트를 가르치는 것이 아니라, 초급자나 중급자가 전문가로 발전할 수 있게 돕는 것
프로그래밍에서 어떤 방법을 가르치는게 아니라 어떤 방법을 사용할 때 왜 그래야 하는지를 알려준다.
이 장을 통해 알수 있는 것
타입스크립트가 어떻게 타입을 추론하는지
언제 타입 선언을 작성해야 하는지
타입 추론이 가능하더라도 명시적으로 타입 선언을 작성하는 것이 필요한 상황은 언제인지
3장 타입추론
Item19. 추론 가능한 타입을 사용해 장황한 코드 방지하기
- 타입스크립트가 타입을 추론할 수 있다면 굳이 타입 구문을 작성하지 않는게 좋다.
- 이상적인 경우 함수의 시그니처에는 타입 구문이 있지만, 함수 내의 지역 변수에는 타입 구문이 없다. 함수의 시그니처란 함수의 타입 즉 parameter의 타입과 반환값의 타입이다. 이 이외에 지역변수에 타입 구문이 없으면 좋은점은 읽는사람으로 하여금 함수의 로직에만 집중할 수 있게 할 수 있다.
- 추론될 수 있는 경우라도 객체 리터럴과 함수 반환에는 타입 명시를 고려해 보는게 좋다. 이는 내부 구현의 오류가 사용자 코드 위치에 나타나는 것을 방지해 주기 때문이다.
마지막에 거론한 "오류가 사용자 코드 위치에 나타나는 것을 방지해 준다" 라는 말은
이하의 quote를 가져오는데 이미 값이 있을시에는 캐싱을 하는 코드가 있다.
하지만 이 코드를 보면 return 타입이 캐싱일때와 아닐때가 다르다는걸 알 수 있다.
(캐싱일 때는 Promise<number> 가 아닌 그냥 number)
이렇게 되면 실제로 함수를 작성한 부분에서 오류가 나는게 아니라 함수를 쓰는곳에서 오류가 난다.
그래서 반환타입을 명시적으로 타입을 선언해주면
밑과 같이 실제로 함수를 작성한 부분에서 오류가 발생해 잘못된 함수 작성을 방지할 수 있다.
Item20. 다른 타입에는 다른 변수 사용하기
- 변수의 값은 바뀔 수 있지만, 타입은 일반적으로 바뀌지 않는다
- 혼란을 막기 위해 타입이 다른 값을 다룰 때에는 변수를 재사용하지 않도록 하자. 별도의 변수로 두는게 좀 더 가독성이 높고 오류를 방지할 수 있다. (밑과 같은 경우에 두개의 id 변수를 두는게 좋다라는 말)
Item21. 타입 넓히기
타입스크립트는 작성된 변수에 타입을 명시하지 않으면 타입을 체크하는 정적 분석 시점에 이 변수의 가능한 타입들을 추측한다. 타입들이기 때문에 이를 다른 말로 타입 넓히기 라고 부르는 것 같다.
- 타입스크립트가 넓히기를 통해 상수의 타입을 추론할 때 let으로 할때와 const로 할 때 다른 타입으로 추측 되는것과 같이 상수의 타입을 추론하는 법을 이해해야 한다.
- 동작에 영향을 줄 수 있는 방법인 const, 타입 구문, 문맥 as const에 익숙해져보자.
const와 let
const text = 'good' // 이 변수의 타입은 'good'
let text2 = 'good' // 이 변수의 타입은 string
const, 타입 구문, 문맥 as const의 예시
const v1: { x: 1 | 3 | 5 } = {
x: 1,
};
// const v1: {
// x: 1 | 3 | 5;
// };
const v2: {
x: 1;
y: 2;
};
// const v2: {
// x: 1;
// y: 2;
// };
const v3 = {
x: 1 as const,
y: 2,
};
// const v3: {
// x: 1;
// y: number;
// };
const v4 = {
x: 1,
y: 2,
} as const;
// const v4: {
// readonly x: 1;
// readonly y: 2;
// };
Item22. 타입 좁히기
타입 넓히기의 반대인 타입 좁히기...보통 조건문을 통한 타입 가드로 타입을 좁혀 나가곤 한다.
- 분기문 외에도 여러 종류의 제어 흐름을 살펴보면 타입스크립트가 타입을 좁혀 나가는 과정이 보인다.
- 태그 or 구별된 유니온과 사용자 정의 타입 가드를 사용하여 타입 좁히기 과정을 원할하게 만들 수 있다.
Item23. 한꺼번에 객체 생성하기
- 속성을 제각각 추가하지 말고 한번에 객체에 추가(객체 리터럴)해주는게 좋은데, 그렇게 하면 타입 추론 부분에서 객체를 선언한 순간 타입 추론이 이루어지고 그 객체의 타입의 정해지므로 제각각 추가하는 경우에는 강제로 타입을 씌우거나 type코드를 추가해줘야한다. 그리고 여러가지 객체를 합칠 때는 객체 전개 {...a, ...b}를 활용하면 손쉽게 할 수 있다.
- 객체에 조건부로 속성을 추가하는 방법을 알아두자 예를 들면 밑과 같은
밑의 코드는 start 가 있으면 end는 반드시 존재해야하는 조건으로 묶음으로써 좀더 정확히 타입을 표현할 수 있다.
Item24. 일관성 있는 별칭 사용하기
- 별칭(반복 되는 코드를 줄이는 어떤 변수를 지칭)은 타입스크립트가 타입을 좁히는 것을 방해할 수 있다. 따라서 변수에 별칭을 사용할 때는 일관되게 사용해야 한다. 즉, 한번 별칭을 만들었으면 그 친구만 쓰라는 애기다.
- 비구조화 (distructuring) 문법을 사용해서 일관된 이름을 사용하는 것이 좋다.
Item25. 비동기 코드에는 콜백 대신 async 함수 사용하기
- 콜백보다는 프로미스를 사용하는 게 코드 작성과 타입 추론 면에서 유리하다.
- async가 기본 Promise를 리턴하지만 Nested되서 리턴되지 않기 때문에 그 부분은 신경 안써도 된다.
- 어떤 함수를 프로미스를 반환한다면 프로미스를 생성하기보다는 async와 await를 사용하는게 간결하고 직관적인 코드를 작성할 수 있고 모든 종류의 오류를 제거할 수 있어서 좋다. 여기서 오류란 어떤 함수에서 분기에 따라 한 부분은 동기이면서 또 다른 한 부분은 비동기로 작성된다면 Promise를 생성해서 리턴하는 곳만 비동기로 되어버려서 이 함수를 예측하기가 아주 어려워진다.
예측하기 어려운 코드
const _cache: { [url: string]: string } = {};
const fetchURL = (url: string, callback: (text: string) => void) => {
// 비동기 작업후 callback호출
};
function fetchWithCache(url: string, callback: (text: string) => void) {
if (url in _cache) {
callback(_cache[url]);
} else {
fetchURL(url, (text) => {
_cache[url] = text;
callback(text);
});
}
}
let requestStatus: "loading" | "success" | "error";
function getUser(userId: string) {
fetchWithCache(`/user/${userId}`, (profile) => {
requestStatus = "success";
});
requestStatus = "loading";
}
getUser에서 만약에 캐시가 없을 경우 정상적으로 status가 loading되고 캐시가 저장 success로 다시 status가 바뀌겠지만,
이미 캐시가 존재할 경우, 'success'가 되고 나서 바로 'loading'으로 돌아가 버릴 것이다.
이것들을 async로 해버리면 일관적인 동작을 강제할 수 있게 된다.
Item26. 타입 추론에 문맥이 어떻게 사용되는지 이해하기
- 타입 추론에서 문맥이 어떻게 쓰이는지 주의해서 살펴보자. 타입스크립트는 타입을 추론할 때 단순히 값만 고려하지 않고 값이 존재하는 곳의 문맥까지도 고려한다. 보통 여기서 말하는 문맥이란 예를 들면 어떤 변수가 변경가능한 변수이면 명시되어있는 타입과 맞지 않을 가능성이 발생할 수 있으므로 타입스크립트가 에러를 발생시키는 부분들이 있는데, 이때 타입스크립트가 문맥을 이용해 타입을 추론해 나간다는 걸 알 수 있다. (밑의 예시 참조)
- 이와 비슷하게 mutable한 array object등등에서 발생할 수 있다.
- 변수를 뽑아서 별도 선언했을 때 오류가 발생한다면 타입 선언을 추가해줘야 한다.
- 변수가 정말로 상수라면 상수 단언(as const)을 사용해야 하지만, 상수 단언을 사용하면 정의한 곳이 아니라 사용한 곳에서 오류가 발생하므로 주의해야 한다.
Item27. 함수형 기법과 라이브러리로 타입 흐름 유지하기
타입 흐름을 개선하고, 가독성을 높이고, 명시적인 타입 구문의 필요성을 줄이기 위해 직접 구현하기보다는 내장된 함수형 기업과 lodash 같은 유틸리티 라이브러리를 사용하는 것이 좋다. 라고 적혀있지만 이건 어디까지나 타입스크립트의 관점에 좋은것이고 만약 라이브러리의 크기가 커서 번들 사이즈가 커지거나 요즘 이슈화 되고있는 OSS의 보안문제도 있으므로 마냥 라이브러리를 쓰는게 좋은건 아닐꺼 같다.