개념
(1) any와 unknown의 차이:
any는 어떤 타입 시스템에 혼란을 준다. 어떤 타입이든 any에 할당 가능하면서 any로 어떤 타입이든 할당할 수 있기 때문이다.
'a' 라는 타입은 string 타입보다 작기 때문에 'a'는 string 타입에 할당 가능하다. 그런데 'a' as any라고 하면 number라는 더 큰 타입에도 할당가능하며, boolean 을 받을 수도 있게 된다. 다시 말해 타입 체커가 무용지물이 된다.
반면 unknown은 타입을 unknown 하나의 타입으로 줄이는 기능만 한다. unknown은 오직 unknown과 any에만 할당 가능하다. unknwon 타입으로 나오는 결과가 있다면 말그래도 "무슨 타입인 지 알 수 없음" 상태이고 어떤 타입인지 확인하지 않는 이상 사용이 어렵다. 하지만 이러한 특성 때문에 타입 체커 관점에서 any에 비해 안전한 타입으로 사용할 수 있다.
*참고로 never는 unknown 과 반대로 어떤 타입도 never에 할당할 수 없는 공집합의 개념이다.
(https://ui.toast.com/posts/ko_20220323)
unknown 타입으로 할당 받은 값은 다음과 같이 세 가지 방법으로 타입을 체크해 사용할 수 있다.
- 이중단언: as unknwon as <타입>의 형식으로 unknown 타입으로 타입을 줄인 뒤 의도된 타입으로 단언하는 방법이다. 책에서는 이 방법을 권장한다. "어떤 타입을 의도"하는 것이라면 단언을 사용하는 것이 적절하기 때문이다.
- instanceOf 를 사용할 수 있다. 참고로 instanceOf는 값공간을 체크한다.
- 타입가드를 만들 수 있다. 타입을 체크하기 위해서는 예시에 적힌 것처럼 객체확인, null 체크, 각각의 값들을 다 확인해야한다.
// 예시 3 : 이중 단언 - 특정 타입을 기준으로 타입 체크
const book3 = safeParseYAML(`name: 'The Hitchhiker's Guide to the Galaxy', author: 'Douglas Adams'`) as Book
alert(book2.title) // 오류: Book 형식에 title 속성이 없습니다.
book2('read') // 이 식은 호출할 수 없습니다.
// 예시 4: instance of
function processValue(val: unknown) {
if(val instanceof Date) {
return val;
}
}
// 예시 5 : 타입 가드
function isBook(book: unknown): book is Book {
return (typeof book === 'object' && book !== null && 'name' in book && 'author' in book)
}
이중 단언을 사용할 때 as any를 사용하는 것과 비교해볼 수 있다. any를 이중 단언문에 사용하면 나중에 이 이중 단언문을 분리하는 리팩터링 시에 어쨋든 any로 단언한 값이 나오게 되고 이로 인한 영향력이 퍼지게 된다.(타입 에러를 잡지 못함) 반면 unknwon은 분리되는 그 위치에서 에러를 발생시키므로 조금 더 안전하다.
// as any as Book 과 차이
type Foo = {
bar: string;
}
type Bar = {
bar: string;
}
declare const foo: Foo;
let barAny = foo as any as Bar;
let barUnk = foo as unknown as Bar;
사용법
아래와 같이 선언된 requestDto를 테스트 하는 상황에서 unknwon의 이중 단언을 사용할 수 있다.
아래의 클래스에서 date 값은 timestamp 형식의 string을 받아 js-joda의 LocalDateTime 타입으로 변환하고 있다고 할 때,
이 Dto를 테스트 하기 위해서는 date 값으로 string을 넣어주되 이 타입은 LocalDateTime 타입이어야하는 모순적인 상황이 발생한다.
export class Request {
@ApiProperty()
@IsInt()
id: number;
@ApiProperty()
@IsNotEmpty()
@ToLocalDateTime()
date: LocalDateTime;
}
이를 테스트 할 때 아래와 같이 string 값을 대입하되 타입을 LocalDateTime으로 단언해줌으로써 위의 모순적인 조건을 만족시키며 테스트를 할 수 있다. 기존 회사 코드를 작성할 때는 테스트는 어쩔 수 없는 부분이 있지 하면서 as any를 사용했는데 이펙티브 타입 스크립트 42장을 읽으며 보다 안전한 코드에 대해 배우고 적용하면서 즐거웠다!
describe('POST /test', () => {
it('요청이 성공하면 statusCode는 OK를 반환한다', async () => {
// given
const dto = new Request();
dto.id = 0;
dto.date =
'2021-01-01 00:00:00' as unknown as LocalDateTime;
// when
const response = await request(app.getHttpServer())
.post('/api/test')
.send(dto);
// then
expect(response.body.statusCode).toBe(ResponseStatus.OK);
});
});
'백엔드 개발' 카테고리의 다른 글
#031. 쿼리개선: N*M -> N+M 개선하기 (0) | 2023.04.09 |
---|---|
#030. CORS 에러 원인과 해결 (feat. 서버에서 CORS 테스트 하기) (0) | 2023.04.08 |
#028. TS 타입 좁히기(2) Brand 사용해 nominal typing 하기 (0) | 2023.03.22 |
#027. TS 타입 좁히기(1) Tagged Union (0) | 2023.03.08 |
#026. TS 의 타입 넓히기 (0) | 2023.02.07 |