[any 42] 값의 타입을 모를땐 any대신 unknown

2022. 7. 29. 09:39책/이펙티브 타입스크립트

1. any, unknown, never

할당 가능성의 관점에서 any, unknown, never타입을 다시 생각해보면 아래 특징을 갖는다.

구분 특징
any 어떠한 타입이든 any 타입에 할당 가능
any 타입은 어떠한 타입에도 할당 가능(never제외)
unknown 어떠한 타입이든 unknown에 할당 가능
unknown은 오직 unknown과 any에만 할당 가능
never 어떠한 타입도 never에 할당 불가
never는 어떠한 타입에도 할당 가능

타입을 할당 가능한 값의 집합으로 볼 때, 한 집합이 다른 집합의 부분집합이면서 동시에 상위 집합이 될 수 없다. 때문에 any가 강력하면서 동시에 문제를 일으키는 원인이 되는 것이다.

 

반면 unknown은 any대신 쓸 수 있는 안전한 타입이다. 오직 unknown과 any에만 할당 가능하므로 unknown 타입인 채로 값을 사용하면 오류가 발생하기 때문에, 적절한 타입으로 변환하도록 강제할 수 있기 때문이다.

 

2. unknwon 형태

함수의 반환값, 변수 선언, 단언문과 관련된 형태가 있다.

 

가. 함수의 반환값

함수의 반환값이 unknown이면 그 값을 그대로 쓸 수 없기 때문에 타입 단언문을 강제할 수 있으며, 의도 자체가 이를 위함이기 때문에 단언문을 쓴다한들 문제가 되지 않는다

function parseYAML(yaml :string) :unknown{
  return;
}

interface Book { name :string; author :string; }

// unknown 타입은 Book타입에 할당할 수 없는데요?
// any 타입을 반환하면 할당 가능하긴 함ㅋ
const book: Book = parseYAML(`
  name: ABC
  author: Choco
`)

// 애초에 반환값이 Book을 기대하며 함수를 호출한거니
// as Book으로 타입 단언문 써도 문제 없다
const book2 = parseYAML(`
  name: ABC
  author: Choco
`) as Book

 

나. 변수 선언

어떠한 값이 있는데 그 타입을 모를 경우 unknown을 사용한다. 이렇게 만들어두면 이후 사용자가 단언문이나 instanceof, 타입 가드를 도입해 원하는 방식으로 타입을 좁히도록 강제할 수 있다.

1) instanceof
function processValue(val: unknown) {
  // unknown이니까 val 어차피 그대로 못씀
  // 그니까 사용자 의도에 맞게 타입 좁혀서 쓰셈
  if (val instanceof Date) {
    ...
  }
}

2) 타입 가드
function isBook(val: unknown): val is Book {
  return (
    typeof(val) === 'object' &&
    val !== null &&
    'name' in val && 'author' in val
  )
}

 

다. 이중 단언문

이중 단언문에서 기능 자체는 동일하게 돌아가지만 이후 리팩터링에서 unknown형태가 더 안전하다. 예를 들어 as Different부분을 리팩터링 과정에서 때냈을 때 as any만 남은 부분은 온갖 곳에 악영향을 미치겠지만, as unknown 부분은 타입 체커에게 오류를 띄우게 함으로써 이를 방지해준다

interface Something { }
interface Different { }
declare const something: Something;

// 나중에 as Different 떼냈을때
// as any만 남은 부분은 어디가 잘못되고있는지 모를걸?
let Any = something as any as Different; 
let Unk = something as unknown as Different;

 

 

3. { }, object

  • { } 타입은 null과 undefined를 제외한 모든 값을 포함한다
  • object 타입은 모든 비기본형(non-primitive) 타입으로 이루어진다

unknown 타입이 도입되기 전에는 이를 대체하여 쓰이던 타입이지만 요새는 드물다. 만약 값에 null, undefined가 할당될 일이 죽어도 없다고 판단되는 경우엔 써도 되기는 하다.