-
이펙티브 타입스크립트- 3장 타입추론 (2)TypeScript 2022. 4. 10. 12:35
아이템 20. 다른 타입에는 다른 변수 사용하기
한 변수를 다른 타입으로 재사용해도 되는 자바스크립트와 달리 타입스크립트는 에러가 난다.
let id = '12-34-56'; fetchProduct(id); // string으로 사용 id = 123456; // '123456' 형식은 'string'형식에 할당할 수 없습니다. fetchProductBySerialNumber(id); // number로 사용 //'string' 형식의 인수는 'number' 형식의 매개변수에 할당될 수 없습니다.
이때 에러를 고치기 위해서는 id가 string과 number를 모두 포함할 수 있도록 string | number로 유니온 id 타입을 사용해 확장할 수 있다.
let id: string|number = '12-34-56'; fetchProduct(id); id = 123456; // 정상 fetchProductBySerialNumber(id); // 정상
하지만 이는 더 많은 문제를 야기할 수 있다.
id를 사용할 때마다 값이 어떤 타입인지 확인해야 하기 때문에 유니온 타입은 string이나 number 같은 간단한 타입에 비해 다루기 더 어렵다.
차라리 별도의 변수를 도입하는 것이 낫다.
const id = '12-34-56'; fetchProduct(id); const serial = 123456; fetchProductBySerialNumber(serial);
위 예처럼 다른 타입에는 별도의 변수를 사용하는게 바람직한 이유는 아래와 같다.
- 변수명을 더 구체적으로 지을 수 있다.
- 타입 추론을 향상시키며 타입 구문이 불필요해진다.
- 타입이 좀 더 간결해진다.(string | number 대신 string과 number를 사용)
- let 대신 const 변수를 선언하게 된다. (const로 변수 선언 시 코드 타입이 바뀌는 변수는 피해야 하며 목적이 다른 곳에는 별도의 함수명을 사용해야 한다)
그런데 위의 재사용되는 변수를 아래의 예제처럼 '가려지는(shadowed)'변수를 혼동해서는 안된다.
let id: string = '12-34-56'; fetchProduct(id); { id = 123456; // 정상 fetchProductBySerialNumber(id); // 정상 }
여기서 id는 이름이 같지만 실제로 아무런 관련이 없어 각기 다른 타입으로 사용돼도 무방하다.
하지만 이는 동작이 될지라도 사람들에게 혼란을 줄 수 있으므로 (목적이 다른 곳에는 꼭 별도의 변수명을 쓰자) 많은 개발팀에서 린터 규칙을 통해 '가려지는' 변수를 사용하지 못하게 한다.
요약
- 변수의 값은 바뀔 수 있지만 타입을 바뀌지 않는다.
- 혼란을 막기 위해 타입이 다른 값을 다룰 때에는 변수를 재사용하지 않도록 한다.
아이템 21. 타입 넓히기
상수를 사용해서 변수를 초기화할 때 타입을 명시하지 않으면 타입 체커는 타입을 결정해야 한다.
즉 지정된 단일 타입을 가지고 할당 가능한 값들의 집합을 유추해야 한다는 뜻인데, 이러한 과정을 '넓히기(widening)'이라고 부른다.
다음 예제는 실행은 잘되지만 편집기에서는 오류가 발생한다.
interface Vector3 { x: number; y: number; z: number; } function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') { return vector[axis]; } let x = 'x'; let vec = { x: 10, y: 20, z: 30 }; getComponent(vec, x); // 'string' 형식의 인수는 'x' | 'y' | 'z' 형식의 매개변수에 할당될 수 없습니다.
getComponent 함수는 두 번째 매개변수에 'x' | 'y' | 'z' 타입을 기대했지만, x의 타입은 할당 시점에 넓히기가 동작해서 string으로 추론되어 오류가 난 것이다. (string 타입은 'x' | 'y' | 'z' 타입에 할당 불가)
타입 넓히기가 진행될 때, 주어진 값으로 추론 가능한 타입이 여러개이기 때문에 상당히 모호해진다.
다음 예제를 보자.
const mixed = ['x', 1];
mixed의 타입이 될 수 있는 후보들은 상당히 많다. ('x' | 1)[] / ['x', 1] / [string, number] / (string|number)[] 등등.
(위 경우에는 타입스크립트는 mixed의 타입을 (string|number)[]로 추론한다.)
타입스크립트는 넓히기의 과정을 제어할 수 있도록 몇 가지 방법을 제공한다.
첫 번째 방법은 const이다.
let대신 const로 변수를 선언하면 더 좁은 타입이 된다.
위의 getComponent 예시에서 const를 사용하면 이제 오류가 나지 않는다.
interface Vector3 { x: number; y: number; z: number; } function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') { return vector[axis]; } const x = 'x'; let vec = { x: 10, y: 20, z: 30 }; getComponent(vec, x); // 정상
이제 x는 재할당될 수 없으므로 타입스크립트는 의심의 여지 없이 더 좁은 타입('x')로 추론할 수 있다.
(또한 문자 리터럴 타입 'x'는 'x' | 'y' | 'z' 에 할당 가능하므로 타입체커를 통과한다)
그러나 객체나 배열의 경우 const 사용 시 문제가 있다.
const v = { x: 1, }; v.x = 3; v.x = '3'; v.y = 4; v.name = 'Pythagoras';
v의 타입 역시 구체적인 정도에 따라 다양하게 추론될 수 있다.
가장 구체적인 경우는 {readonly x: 1} 일 것이고, 가장 추상적인 경우는 {[key: string]: number} 혹은 object일 것이다.
하지만 타입스크립트는 객체의 경우 각 요소를 let으로 할당된 것처럼 다룬다.
그래서 v의 타입은 {x:number}가 된다.
덕분에 v.x에 다른 숫자를 재할당할 순 있지만 다른 타입을 할당할 순 없고,
객체 v에 다른 프로퍼티를 추가하는 것도 불가능하다.
const v = { x: 1, }; v.x = 3; v.x = '3'; // '3' 형식은 'number' 형식에 할당할 수 없습니다. v.y = 4; // '{ x: number }' 형식에 'y' 속성이 없습니다. v.name = 'Pythagoras'; // '{ x: number }' 형식에 'name' 속성이 없습니다.
타입스크립트는 잘못된 추론을 하지 않기 위해 적당한 강도의 타입 넓히기를 수행하는데,
이 강도를 제어하기 위해 세 가지 방법이 존재한다.
- 첫 번째, 명시적 타입 구문을 제공하기
- 타입 체커에 추가적인 문맥 전달(함수의 매개변수로 값을 전달하는 등의 방식)
- const 단언문(as const) 사용하기
// 명시적 타입 구문 제공하기 const v: { x: 1|3|5 } = { x: 1, }; // 타입이 { x: 1|3|5; } // 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; }
배열을 튜플 타입으로 추론할 때도 마찬가지로 as const를 사용할 수 있다.
const a1 = [1, 2, 3]; // 타입이 number[] const a2 = [1, 2, 3] as const; // 타입이 readonly [1, 2, 3]
이렇듯, as const를 사용하면 타입스크립트가 최대한 좁게 타입을 추론한다는 것을 알 수 있다.
요약
- 타입스크립트 넓히기를 통해 상수의 타입을 추론하는 법을 이해해야 한다.
- 동작에 영향을 줄 수 있는 방법인 const, 타입구문, 문맥, as const에 익숙해져야 한다.
'TypeScript' 카테고리의 다른 글
이펙티브 타입스크립트- 3장 타입추론 (4) (0) 2022.04.18 이펙티브 타입스크립트- 3장 타입추론 (3) (0) 2022.04.17 이펙티브 타입스크립트- 3장 타입추론 (1) (0) 2022.04.03 이펙티브 타입스크립트- 2장 타입시스템 (4) (0) 2022.04.02 이펙티브 타입스크립트- 2장 타입시스템 (3) (0) 2022.04.01