🔗 어떤 값에 새 이름을 할당하기
Copy const place = {name: 'New York', latLng: [41.6868, -74.2692]};
const loc = place.latLng;
place.latLng
배열에 loc
라는 별칭(alias
)을 만들었다. 별칭의 값을 변경하면 원래 속성값에서도 변경된다.
Copy > loc[0] = 0
> place.latLng
[0, -74.2692]
하지만, 별칭을 남발하면 제어흐름을 분석하기 어렵다. 별칭을 신중하게 사용해야 코드를 잘 이해하고, 오류도 쉽게 찾을 수 있다.
🔗 다각형을 표현하는 자료구조
Copy interface Coordinate {
x: number;
y: number;
}
interface BoundingBox {
x: [number, number];
y: [number, number];
}
interface Polygon {
exterior: Coordinate[]; // 다각형은 exterior과 holes 속성으로 정의됨
holes: Coordinate[][];
bbox?: BoundingBox;
}
bbox
속성(최적화 속성)을 사용하면 어떤 점이 다각형에 포함되는지 빠르게 체크할 수 있다.
Copy function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
if (polygon.bbox) {
if (pt.x < polygon.bbox.x[0] || pt.x > polygon.bbox.x[1] ||
pt.y < polygon.bbox.y[0] || pt.y > polygon.bbox.y[1]) {
return false;
}
}
// ... more complex check
}
이 코드는 잘 동작하고, 타입체크도 통과하지만 반복되는 부분이 존재한다. polygon.bbox
는 5번이나 등작한다. 중복을 줄이기 위해 임시변수를 뽑아보자.
Copy function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox;
if (polygon.bbox) {
if (pt.x < box.x[0] || pt.x > box.x[1] ||
// ~~~ ~~~ 'box' is possibly 'undefined'
pt.y < box.y[0] || pt.y > box.y[1]) {
// ~~~ ~~~ 'box' is possibly 'undefined'
return false;
}
}
// ...
}
편집기에서 오류로 표시되는데, box
별칭이 첫번째 예제에서는 잘 동작했던 제어 흐름 분석을 방해한다.
box
와 polygon.bbox
의 타입을 조사해보자.
Copy function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
polygon.bbox
// ^? (property) Polygon.bbox?: BoundingBox | undefined
const box = polygon.bbox;
// ^? const box: BoundingBox | undefined
if (polygon.bbox) {
console.log(polygon.bbox);
// ^? (property) Polygon.bbox?: BoundingBox
console.log(box);
// ^? const box: BoundingBox | undefined
}
}
속성체크는 polygon.bbox의 타입을 정제했지만 box는 그렇지 않았기 때문에 오류가 발생한다. 이러한 오류는 '별칭은 일관성 있게 사용한다'는 기본 원칙(golden rule)을 지키면 방지할 수 있다.
→ 속성 체크에 box를 사용하도록 코드를 바꿔보자.
Copy function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox;
if (box) {
if (pt.x < box.x[0] || pt.x > box.x[1] ||
pt.y < box.y[0] || pt.y > box.y[1]) { // OK
return false;
}
}
// ...
}
타입 체커의 문제는 해결되었지만, 코드를 읽는 사람에게는 문제가 남아있다. box와 bbox는 같은 값인데 다른 이름을 사용한 것이다. (아이템 36)
객체 비구조화를 이용하면 보다 간결한 문법으로 일관된 이름으로 사용할 수 있다. 배열과 중첩된 구조에서도 사용할 수 있다.
Copy function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const {bbox} = polygon;
if (bbox) {
const {x, y} = bbox;
if (pt.x < x[0] || pt.x > x[1] || pt.y < y[0] || pt.y > y[1]) {
return false;
}
}
// ...
}
객체 비구조화를 이용할 때는 두 가지를 주의해야한다.
전체 bbox 속성이 아니라 x와 y가 선택적 속성일 경우에 속성 체크가 더 필요하다. 따라서 타입의 경계에 null 값을 추가하는 것이 좋다.
bbox에는 선택적 속성이 적합했지만 holes는 그렇지 않다. holes가 선택적이라면, 값이 없거나 빈 배열이었을 것이다. 차이가 없는데 이름을 구별한 것이다. 빈 배열은 'holes 없음'을 나타내는 좋은 방법이다.
🔗 별칭은 타입 체커뿐만 아니라 런타임에도 혼동을 야기할 수 있다.
Copy const {bbox} = polygon;
if (!bbox) {
calculatePolygonBbox(polygon); // Fills in polygon.bbox
// Now polygon.bbox and bbox refer to different values!
}
타입스크립트의 제어 흐름 분석은 지역 변수에는 꽤 잘 동작한다. 그러나 객체 속성에서는 주의해야 한다.
Copy function expandABit(p: Polygon) { /* ... */ }
polygon.bbox
// ^? (property) Polygon.bbox?: BoundingBox | undefined
if (polygon.bbox) {
polygon.bbox
// ^? (property) Polygon.bbox?: BoundingBox
expandABit(polygon);
polygon.bbox
// ^? (property) Polygon.bbox?: BoundingBox
}
fn(polygon)
호출은 polygon.bbox
를 제거할 가능성이 있으므로 타입을 BoundingBox | undefined
로 되돌리는 것이 안전할 것이다. 그러나 함수를 호출할 때마다 속성 체크를 반복해야 하기 때문에 좋지 않다. 그래서 타입스크립트는 함수가 타입 정제를 무효화하지 않는다고 가정한다. 그러나, 실제로는 무효화될 가능성이 있다. polygon.bbox로 사용하는 대신 bbox
지역변수로 뽑아내서 사용하면 bbox
의 타입은 정확히 유지되지만, polygon.bbox
의 값과 같게 유지되지 않을 수 있다.
📍 요약
별칭은 타입스크립트가 타입을 좁히는 것을 방해한다. 따라서 변수에 별칭을 사용할 때는 일관되게 사용해야 한다.
비구조화 문법을 사용해서 일관된 이름을 사용하는 것이 좋다.
함수 호출이 객체 속성의 타입 정제를 무효화할 수 있다는 점을 주의해야 한다. 속성보다 지역 변수를 사용하면 타입 정제를 믿을 수 있다.
Last updated 6 months ago