strictNullChecks
속성으로 많은 오류가 표시되게 함으로써 null 값과 관련된 문제점을 찾아내 반드시 필요하다!
이 장에서 말하는 내용은 한 범위안의 변수가 null
인 경우와 그렇지 않은 경우보다, 모두가 null
이거나 전부 null
이 아닌 경우로 분명히 구분하는것이 쉽다는 것을 말하고 있음.
→ 타입에 null
을 추가하는 방식으로 모델링 할 수 있다.
const extent = (nums: number[]) => {
let min;
let max;
for (const num of nums) {
if (!min) {
min = num;
max = num;
} else {
min = Math.min(min, num);
max = Math.max(max, num); // 이 부분에서 에러
}
}
return [min, max];
};
// ---------------------------
// 아래는 단일 객체 사용 의사 코드
let result: [number, number] | null = null;
for() {
if(!result) ...
else
}
이 코드의 문제점으로
min
값만 체크하기 때문에max
부분에서number | unudefined
형식의 인수가number
에 할당될 수 없다고 나온다. 또한, [0, 1, 2]의 경우 0이falsy
값이므로 값이 덮어씌워지는 경우와nums
배열이 빈 배열이라면, 둘 다undefined
를 반환한다.
→ 문제를 해결하기 위해 단일 객체 사용하고, 함수호출의 결과값을 단언 혹은 if 구문으로 체크하면 된다.
이러한 null
을 섞어서 사용하는 경우가 클래스에서 문제가 될수 있는데, 값이 완전히 준비되지 않은 상태에서 네트워크 요청을 하는 경우 그 사이의 속성들의 값이 null
이냐 아니냐에 따라서 불확실성이 생긴다.
요약
null
여부가 다른 값의 null
여부에 암시적으로 관련되도록 하면 Xnull
이거나 null
이 아니게 만들어야한다.null
이 존재하지 않도록 해야함strcitNullChecks
반드시 필요!코드로 보는게 편하다.
// 이렇게 작성하는 것 보다
interface Layer {
layout: FillLayout | LineLayout | PointLayout;
paint: FillPaint | LinePaint | PointPaint;
}
// 이렇게 작성하자.(각각 타입의 계층을 분리된 인터페이스로 두기)
interface FillLayer {
type: 'fill';
layout: FillLayout;
paint: FillPaint;
}
interface LineLayer {
type: 'line'
layout: LineLayout;
paint: LinePaint;
}
interface PointLayer {
type: 'paint'
layout: PointLayout;
paint: PointPaint;
}
type Layer = FillLayer | LineLayer | PointLayer;
이렇게 하면 잘못된 조합으로 섞이는 경우를 방지할 수 있다. 또한 안에 태그된 유니온이 있으면 태그를 참고하여 Layer의 타입의 범위를 좁힐 수도 있다.
선택적 필드가 여러개 있는 경우 두 개의 속성을 하나의 객체로 모아 그 객체가 존재하는지 체크하는 방식으로 사용하면 편리하다. 또한 API와 같이 타입의 구조를 손 댈 수 없는 상황에서 인터페이스의 유니온을 사용해 관계를 모델링할 수 있다.
→ 이 두 경우 모두 타입 정의를 통해 속성 간의 관계를 더 명확하게 만들 수 있다.
string
타입 → 범위가 넓다
// 이렇게 쓰기 보다
interface Album {
artist: string;
title: string;
releaseDate: string;
recordingType: string;
}
// 이렇게 쓰자
type RecordingType = 'live' | 'studio';
interface Album {
artist: string;
title: string;
releaseDate: Date;
recordingType: RecordingType;
}
위와 같이 바꾸면 3가지 장점
keyof
연산자로 더욱 세밀하게 객체의 속성 체크가 가능해진다.// 이렇게 작성하면 반환되는 타입이 (string | Date)[]가 되는데,
const pluck = <T>(recors: T[], key: keyof T) => {
return records.map(r => r[key]);
}
// 이렇게 작성하면 반환되는 타입이 Date[] 가 된다.
// 또한 호출 부분에 있어서 매개변수 타입이 정밀해진 덕분에 자동완성 기능을 제공해준다.
const pluck = <T, K extends keyof T>(records: T[], key: K) => {
return records.map(r => r[key]);
}
string의 부분 집합을 정의할 수 있는 기능은 자바스크립트 코드에 타입 안전성을 크게 높인다. 따라서 객체의 속성 이름을 함수 매개변수로 받을 때,
string
보다는keyof T
를 사용하자.
타입선언의 정밀도를 높이려다가 더 좋지않은 개발 경험을 할 수 있으므로 이런 일을 할 때 주의해서 해야한다!
→ 오히려 타입이 부정확해지는 경우도 있다.
또한 타입선언의 정밀도를 높이려다가 정밀도를 높이지 않았을 때보다 발생하는 메시지의 오류가 부정확해지는 경우도 있다.
따라서 정확하게 타입을 모델링할 수 없다면 부정확하게 모델링하지 말고,
any
와unknown
을 구별해서 사용하자.
any
: 어떠한 값이든 가능. 타입을 좁혀서 사용하지 않아도 된다.
unknown
: 어떠한 값이 올 수 있는지 모르므로, 타입을 좁혀서 사용함. 다른값에 할당이 불가능하다.(any
타입 제외)
아래는 참고 사이트(any vs unknown)
명세를 참고해 타입을 생성하는 것이 사용자가 실수를 줄일 수 있게 도와준다.
데이터를 사용해 타입을 생성하면 눈앞에 있는 데이터만 참고하기 때문에 예기치 않은 곳에서 오류가 발생할 수 있다.
예시로 Geojson
을 가져왔는데, 여기서 GeometryCollection
타입인 경우 coordinates
속성이 없어 발생하는 문제를 다루며 이를 분기처리하여 에러를 잡음
타입을 직접 선언하였을 때, 이러한 예외 상황이 포함되지 않았음을 통해 명세를 기반으로 작성하는 것이 더 안정성을 높일 수 있다는것을 말함
특히 GraphQL 처럼 자체적인 타입이 정의된 API에서 잘 동작한다. (GraphQL 써보고 싶다...)
쿼리에서 타입을 생성하기 위해 GraphQL 스키마가 필요하고 Apollo를 통해 스키마를 얻는다. 이를 통해 명세로부터 타입을 가져올 수 있고, 타입과 실제 값이 항상 일치하여 좋다!