Effective Typescript vol.5 (any 다루기)
개요
이펙티브 타입스크립트라는 책을 읽고 장별(각각의 장안에 아이템별)로 학습한 내용 정리하기
이 책의 목표
독자에게 타입스크립트나 자바스크립트를 가르치는 것이 아니라, 초급자나 중급자가 전문가로 발전할 수 있게 돕는 것
프로그래밍에서 어떤 방법을 가르치는게 아니라 어떤 방법을 사용할 때 왜 그래야 하는지를 알려준다.
이 장을 통해 알수 있는 것
전통적인 프로그래밍 언어들의 타입 시스템은 완전히 정적이거나 완전히 동적으로 확실히 구분 되어 있다. 하지만 타입스크립트의 타입 시스템은 선택적이고 점진적이기 때문에 정적, 동적 특성을 동시에 가진다. 이러한 특성 덕분에 일부분에만 타입스크립트를 적용할 수 있으며, 점진적인 마이그레이션도 가능하다. 그리고 마이그레이션을 할 때 코드의 일부분에 타입 체크를 비활성화시켜 주는 any 타입의 역할이 아주 중요하다. any 타입은 너무나 강력한 힘을 가지고 있기 때문에 현명하게 활용해야 하는데 이번장에서는 any의 현명한 활용방법에 대해 다룬다.
Item38. any 타입은 가능한 한 좁은 범위에서만 사용하기
- 의도치 않은 타입 안정성의 손실을 피하기 위해서 any의 사용 범위를 최소한 좁혀야 한다.
- 함수의 반환 타입이 any인 경우 타입 안정성이 나빠지므로 any 타입을 반환하면 절대 안 된다.
- 강제로 타입 오류를 제거하려면 any 대신 @ts-ignore를 사용하는게 좋다.
type Foo = {
foo: string;
};
type Bar = {
bar: string;
};
const expressionReturningFoo = (): Foo => {
return {
foo: "foo",
};
};
const processBar = (x: Bar): void => {
console.log(x);
};
function f1() {
const x: any = expressionReturningFoo(); // 이렇게 해서 x를 다른곳에서 쓰거나 return하게 되면 any가 퍼져버리는 상황이 올 수 있다.
processBar(x);
}
function f2() {
const x = expressionReturningFoo();
processBar(x as any); // 이게 더 낫다
}
Item39. any를 구체적으로 변형해서 사용하기
- any를 사용할 때는 정말로 모든 값이 허용되어야만 하는지 면밀히 검토해야 한다.
- any보다 더 정확하게 모델링 할 수 있도록 any[] 또는 {[id: string]: any} 또는 () => any처럼 구체적인 형태를 사용해야한다.
const numArgsBad = (...args: any) => args.length; // return 타입이 any
const numArgsGood = (...args: any[]) => args.length; // return 타입이 number
Item40. 함수 안으로 타입 단언문 감추기
- 타입 선언문은 일반적으로 타입을 위험하게 만들지만 상황에 따라 필요하기도 하고 현실적인 해결책이 되기도 한다. 불가피하게 사용해야 한다면, 정확한 정의를 가지는 함수 안으로 숨기도록 한다.
function shallowObjectEqual<T extends object>(a: T, b: T): boolean {
for (const [k, aVal] of Object.entries(a)) {
if (!(k in b) || aVal !== b[k]) { // 원래는 여기서 에러가? 났었다고 하는데 지금은 안나는걸 보니 타입 시스템이 좀더 구문을 잘 이해하게 된거 같다.
return false;
}
}
return Object.keys(a).length === Object.keys(b).length;
}
Item41. any의 진화를 이해하기
- 일반적인 타입들은 정제(타입좁히기와 같은 동작)되기만 하는 반면, 암시적 any와 any[] 타입은 진화할 수 있다. 이러한 동작이 발생하는 코드를 인지하고 이해할 수 있어야 한다.
- any를 진화시키는 방식보다 명시적 타입 구문을 사용하는 것이 안전한 타입을 유지하는 방법이다.
// "noImplicitAny": true
const 이건anyArr = []; // any[]로 되어있지만,
이건anyArr.push(1);
console.log(이건anyArr) // number[]로 진화
이건anyArr.push("ㅇ");
console.log(이건anyArr) // (string | number)[]로 진화
Item42. 모르는 타입의 값에는 any 대신 unknown을 사용하기
- unknown은 any 대신 사용할 수 있는 안전한 타입이다. 어떠한 값이 있지만 그 타입을 알지 못하는 경우라면 unknown을 사용함으로써 타입 단언문이나 타입 체크를 사용하도록 강제할 수 있게 된다. (any일때는 타입체크가 안걸릴것이다.)
- {} 타입은 null과 undefinedfmf 제외한 모든 값을 포함한다.
- unknown은 모든 타입의 상위이고 반대로 never는 모든 타입의 하위 타입이다. 이 말인 즉슨 unknown 타입은 모든 타입이 될 수 있으며 모든 타입은 unknown이 될 수 없다. 반대로 never 타입은 모든 타입이 될 수 없고 모든 타입은 never가 될 수 있다.
any 대신에 unknown을 사용함으로써 얻을 수 있는 타입의 안정성
const parseYamL = (text: string): any => ({});
const book = parseYamL(`name: Jane
author: Char`);
// book이 any가 되므로 이하의 두줄 코드는 어떠한 타입에러도 나지 않고 런타임에서 에러가 날것이다.
console.log(book.title);
book();
const safetyParseYamL = (text: string): unknown => ({});
const book2 = safetyParseYamL(`name: Jane
author: Char`);
// Object is of type 'unknown'
console.log(book2.title);
// Object is of type 'unknown'
book2();
const book3 = safetyParseYamL(`name: Jane
author: Char`) as Book;
// Property 'title' does not exist on type 'Book'
console.log(book3.title);
// This expression is not callable.Type 'Book' has no call signatures
book3();
unknown과 never
let mk: unknown = "string";
mk = 1;
mk = { key: "asdads" };
// Type 'unknown' is not assignable to type 'number'
let num: number = mk;
// Type 'string' is not assignable to type 'never'
let nv: never = "string";
// Type 'number' is not assignable to type 'never'
let nv2: never = 2;
Item43. 몽키 패치보다는 안전한 타입을 사용하기
- 몽키패치란, 프로그램이 시스템 소프트웨어를 개별적으로 확장 또는 수정하는 방법을 의미하는데 자바스크립트에 있어서는 프로토 타입에 특정 메소드 추가 한다거나 document 객체에 전역 변수를 삽입하는 등이 있다.
- 전역 변수나 DOM에 데이터를 저장하는 것보다 데이터를 분리하여 사용해야 한다.
- 어쩔수 없이 document 안에 데이터를 저장해야 하는 경우, 안전한 타입 접근법 중 하나(보강이나 사용자 정의 인터페이스로 단언)를 사용해야 한다.
test.d.ts로 Document 타입을 선언병합
declare interface Document {
monkey: string;
}
다른 모듈에서도 monkey타입이 제대로 반영된다.
이것좀 추가로 읽자
TypeScript에서 전역 개체 타입은 어떻게 정의하나요?
팀 구성원이 짝을 지어 기술 리서치를 진행하면서 여러 건의 일을 병렬로 진행하고 있다. 리서치 결과를 건별로 공유하고 논의하는 자리가 만들어지다보니 자연스레 회의가 많아졌다. 프로토콜
huns.me
Item44. 타입 커버리지를 추적하여 타입 안정성 유지하기
- noImplicitAny로 암묵적인 any타입 사용을 금지 시켜도, 명식적 any 또는 서드파티 타입 선언(@types)을 통해 any 타입은 코드 내에 여전히 존재할 수 있다는 점을 주의하자.
- npx type-coverage 을 이용하면 타입 심벌중 any가 아닌 타입을 가지고 있는 심벌의 퍼센트가 나오는데 이를 통해 타입 커버리지를 추적할 수 있으니 활용해 보자.