티스토리 뷰

이번 글에서는 이펙티브 타입스크립트를 공부하며 알게 된 unknown 타입에 대해 더 자세히 알아보려고 합니다 :)

 

✔️ 함수의 반환값과 관련된 unknown

함수를 하나 작성해 봅시다.

function parseYAML(yaml: string): any {
	// ...
}

함수의 반환 타입으로 any를 사용하는 것은 좋지 않은 설계이므로, 대신 호출한 곳에서 반환값을 원하는 타입으로 할당하는 것이 이상적입니다.

interface Book {
  name: string;
  author: string;
}
const book: Book = parseYAML(`
  name: Wuthering Heights
  author: Emily Brote
`)

위와 같이 Book이라고 타입을 선언해주지 않고 생략을 하게 되면, parseYAML 함수의 반환 타입인 any 때문에 book 변수의 타입은 암시적 any가 되고, 사용되는 곳마다 타입 오류가 발생하게 됩니다.

const book = parseYAML(`
  name: Jane Eyre
  author: Charlotte Bronte
`);
alert(book.title); // 오류 없음. 런타임에 "undefined" 경고
book('read'); // 오류 없음. 런타임에 "TypeError: book은 함수가 아닙니다" 예외 발생

따라서 함수의 반환 타입을 지정하기 어려운 상황이라면, any 보다는 unknown을 사용하는 것이 더 안전한데요.

function safeParseYAML(yaml: string): unknown {
  return parseYAML(yaml);
}
const book = safeParseYAML(`
  name: The Tenant of Wildfell Hall
  author: Anne Bronte
`);
alert(book.title);
   // ~~~~ 개체가 'unknown' 형식입니다.
book("read");
//~~~~~~~~~~ 개체가 'unknown' 형식입니다.

 

✔️ any가 위험한 이유

any가 강력하면서도 위험한 이유는 다음 두 가지 특징으로 비롯됩니다.

 

1. 어떠한 타입이든 any 타입에 할당 가능하다.

2. any 타입은 어떠한 타입으로도 할당 가능하다.

 

✔️ unknown은 any 대신 쓸 수 있는 타입시스템에 부합하다

unknown은 any와 비슷하면서도 정반대의 특징도 가지고 있습니다.

 

1. 어떠한 타입이든 unknown 타입에 할당 가능하다.

2. unknown 타입은 오직 unknown과 any에만 할당 가능하다.

 

✔️ 그래서 unknown 타입인 채로 값을 사용하면 오류가 발생

unknown 상태로 사용하려고 하면 오류가 발생하기 때문에, 적절한 타입으로 변환하도록 강제할 수 있습니다.

const book = safeParseYAML(`
  name: Villette
  author: Chalotte Bronte
`) as Book;
alert(book.title);
        // ~~~~~~ 'Book' 형식에 'title' 속성이 없습니다.
book('read');
// ~~~~~~~~~ 이 식은 호출할 수 없습니다.

 

✔️ 변수 선언과 관련된 unknown

어떠한 값이 있지만 그 타입을 모르는 경우에 unknown을 사용합니다.

interface Feature {
  id?: string | number;
  geometry: Geometry;
  properties: unknown;
}

 

✔️ instanceof를 체크한 후 unknown에서 원하는 타입으로 변환

좀 전에 unknown을 반환하는 함수를 받아낸 변수 자체에 타입 단언을 사용하여 unknown을 없애고 사용했었습니다. 그 방법 외에도 instanceof를 체크한 후 unknown에서 원하는 타입으로 변환할 수 있습니다. 

 

아래 함수의 매개변수에 값이 들어오긴 하지만, 어떠한 값이 들어올지 모르는 상황입니다. 이펙티브 타입스크립트에서 언급되었던 내용 중, 입력 값에 대해서는 느슨하게 출력값에 대해서는 엄격하게 라는 문구가 있습니다.

unknwon으로 들어오더라도 내부에서 원하는 타입으로 변환 후 정확하게 리턴을 해주면 되겠습니다.

function processValue(val: unknown) {
  if (val instanceof Date) {
    val // 타입이 Date
  }
}

 

✔️ 타입 가드로 unknown에서 원하는 타입으로 변환

function isBook(val: unknown): val is Book {
  return (
    typeof(val) === 'object' && val !== null && 'name' in val && 'author' in val
  );
}
function processValue(val: unknown) {
  if (isBook(val)) {
    val // 타입이 Book
  }
}

 

✔️ 입력값에 어떤 타입이 들어올지 모른다면, 제너릭을 쓰면 되는 거 아냐?

가끔 unknown 대신 제너릭 매개변수가 사용되는 경우가 있습니다.

function safeParseYAML<T>(yaml: string): T {
  return parseYAML(yaml);
}

그러나 위 코드는 일반적으로 타입스크립트에서 좋지 않은 스타일입니다.

제너릭을 사용한 스타일은 타입 단언문과 달라 보이지만, 기능적으로는 동일합니다.

 

제너릭보다는 unknown을 반환하고 사용자가 직접 단언문을 사용하거나 원하는 대로 타입을 좁히도록 강제하는 것이 좋다.

 

✔️ 단언문과 관련된 unknown

이중 단언문에서 any와 unknown을 사용할 수도 있습니다.

declare const foo: Foo;
let barAny = foo as any as Foo;
let barUnk = foo as unknown as Foo;

barAny와 barUnk은 기능적으로는 동일하지만, 나중에 두 개의 단언문을 분리하는 리팩토링을 한다면 unknown 형태가 더 안전합니다.

 

✔️ unknown과 유사하지만 조금 다른 타입들 - { } & object

1. { } : null이랑 undefined가 정말 없다면

{ } 타입은 null과 undefined를 제외한 모든 값을 포함한다.

 

2. object : 객체나 배열만 포함된다면

object 타입은 모든 비기본형(non-primative)  타입으로 이루어진다. 여기에는 true 또는 12 또는 "foo"가 포함되지 않지만 객체와 배열은 포함된다. 

728x90
LIST
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함