이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리
📍요약
✅unknown은 any 대신 사용할 수 있는 안전한 타입이다
- 어떤 값이 있을 때, 그 타입을 알지 못하는 경우라면 unknown을 사용하면 된다
✅사용자가 타입 단언문이나 타입 체크를 사용하도록 강제하려면 unknown을 사용하면 된다
✅{}, object, unknown의 차이점을 이해해야 한다
📍함수의 반환값과 관련된 unknown
✅함수의 반환값으로 any 대신 unknown을 사용해야 한다
any를 사용하는 경우
function parseYAML(yaml: string): any {
// ...
}
interface Book {
name: string;
author: string;
}
const book = parseYAML(`
name: Jane Eyre
author: Charlotte Brontë
`);
// book : 암시적 any 타입으로 사용하는 곳에서 에러 유발
alert(book.title); // No error, alerts "undefined" at runtime
book('read'); // No error, throws "TypeError: book is not a
// function" at runtime
- 타입에러로 스크린 불가하므로 바람직하지 않다
✅반환값을 unknown을 사용하는 경우
function parseYAML(yaml: string): any {
// ...
}
interface Book {
name: string;
author: string;
}
// unknown 타입을 반환하는 함수로 래핑
function safeParseYAML(yaml: string): unknown {
return parseYAML(yaml);
}
const book = safeParseYAML(`
name: The Tenant of Wildfell Hall
author: Anne Brontë
`);
alert(book.title);
// ~~~~ Object is of type 'unknown'
book("read");
// ~~~~~~~~~~ Object is of type 'unknown'
타입에러를 유발할 수 있다
📍할당 가능성
- 어떠한 타입이든 any 타입에 할당 가능
- any 타입은 어떠한 타입으로도 할당 가능 (never 제외)
declare function temp1(): never;
declare function temp2(): any;
// 어떠한 타입이든 any 타입에 할당 가능하다
const a: any = temp1(); // never 타입을 any 타입에 할당
// any 타입은 어떠한 타입으로도 할당 가능하다 (never 제외)
const b: never = temp2(); // 타입 에러 : any를 never에 할당 불가
const c: string = temp2(); // OK
⭐한 집합은 다른 모든 집합의 부분 집합이면서 동시에 상위 집합이 될 수 없다
- any는 이 법칙을 무시하기 때문에 강력하면서 에러를 일으킨다
✅반면 unknown 타입은
- 어떠한 타입이든 unknown 타입에 할당 가능
- unknown 타입은 어떠한 타입으로도 할당 가능하진 않다 (unknown 과 any 에만 할당 가능)
any의 1번은 같지만, 2번은 다름
✅반면 never 타입은 unknown과 정반대
- 어떠한 타입도 never에는 할당 불가
- never 타입은 어떠한 타입으로도 할당 가능
- 예제
declare function fnNever(): never;
declare function fnAny(): any;
declare function fnUnknown(): unknown;
declare function fnString(): string;
const a: any = fnNever(); // never를 any에 할당 (가능)
const b: never = fnAny(); // any를 never에 할당 (불가)
const c: string = fnAny(); // any를 string(모든 타입)에 할당 (가능)
const d: number = fnUnknown(); // unknown을 number에 할당 (불가)
const e: unknown = fnString(); // string(모든 타입)을 unknown에 할당 (가능)
⭐그래서 맨 처음의 예제에서 함수의 반환 타입이 unknown 이어도 Book 타입 단언 가능
(unknown 상태로 그대로 사용 불가)
function parseYAML(yaml: string): any {
// ...
}
interface Book {
name: string;
author: string;
}
function safeParseYAML(yaml: string): unknown {
return parseYAML(yaml);
}
const book = safeParseYAML(`
name: Villette
author: Charlotte Brontë
`) as Book; // unknown 타입에 어떤 타입이든(Book) 할당 가능
alert(book.title);
// ~~~~~ Property 'title' does not exist on type 'Book'
book('read');
// ~~~~~~~~~ this expression is not callable
📍변수 선언과 관련된 unknown
✅어떠한 값이 있지만 그 타입을 모를 때 unknown을 사용
- 그래서 일단 함수의 매개변수에 unknown을 부여하고 타입체크 테크닉을 이용..
interface Book {
name: string;
author: string;
}
interface Geometry {}
interface Feature {
id?: string | number;
geometry: Geometry;
properties: unknown;
}
function processValue1(val: unknown) {
// instanceof 연산자로 Date 타입 걸러내기
if (val instanceof Date) {
val // Type is Date
}
}
// 타입 가드
function isBook(val: unknown): val is Book {
return (
typeof(val) === 'object' && val !== null &&
'name' in val && 'author' in val
);
}
function processValue2(val: unknown) {
if (isBook(val)) {
val; // Type is Book
}
}
📍단언문과 관련된 unknown
✅이중 단언문
- 아래의 이중 단언문은 최종적으로 Bar 타입으로 결정되는 것에서 기능적 차이는 없지만,
중간 과정에서 any 보다는 unknown이 예기치 못한 악영향을 막을 수 있다
interface Foo { foo: string }
interface Bar { bar: string }
declare const foo: Foo;
// 타입을 any -> Bar 변환 : any 로 바꾸면 다른 곳에서 에러 발생
let barAny = foo as any as Bar; // 최종 타입 bar
// unknown을 거치는 것이 바람직함
let barUnk = foo as unknown as Bar; // 최종 타입 Bar
✅ {} 타입
null과 undefined를 제외한 모든 값 포함
let temp1: {};
temp1 = 23;
let temp2: {};
temp2 = null; // 타입에러 : null 을 {} 에 할당 불가
let temp3: {};
temp3 = undefined; // 타입에러 : undefined 를 {} 에 할당 불가
✅object 타입
모든 비원시값 (객체나 배열)
let a: object;
// 가능
a = [];
a = {};
a = () => {};
// 불가
a = 23;
a = "abc";
c = false;
⭐unknown 타입이 도입되면서 {} 대신 unknown을 더 많이 쓰는 추세
null / undefined 가 불가능한 것이 확실하면 {}를 사용하면 됨