Typescript as unknown as은 왜 동작할까?

이두팔·2023년 7월 13일

타입스크립트 사용을 하면서 as unknown as를 쓰면 모든(경험상) 타입에러가 해결되는데, 왜 그럴까?

라는 궁금증을 가지고 하나씩 찾아보았다.


unknown Type

any 타입의 자매처럼 작동한다고 합니다.
any는 모호한 반면, unknown는 세부사항이 필요한데요.
그래서 더 안전하다고 합니다.

(이미지: https://www.typescriptlang.org/docs/handbook/type-compatibility.html#any-unknown-object-void-undefined-null-and-never-assignability)

할당 가능성을 요약한 표인데요.
unknownany 외에는 할당할 수 없음을 보여주고 있습니다.

모든 유형을 any에 할당할 수 있는 것처럼 unknown에도 할당할 수 있어요.

코드로 확인하면 쉽습니다!

let value: unknown;

value = true; // 가능
value = 'test'; // 가능
let value: unknwon;

let value1: boolean = value; // 에러
let valuee2: string = value; // 에러

예시는 boolean, string만 들었지만 object, void, undefined, null도 동일해요.
저희는 value에 어떤 타입의 값이 저장되었는지 모르기 때문에 이런게 가능한건데요.

다음은 unknown 값에 대해 작업을 수행할 때 벌어지는 일에 대해 살펴볼게요.

const jsonParserUnknown = (jsonString: string): unknown => JSON.parse(jsonString);

const myOtherAccount = jsonParserUnknown(`{ "name": "Samuel" }`);

myOtherAccount.name;
error 발생! 'myOtherAccount' is of type 'unknown'.

'myOtherAccount' 객체는 타입스크립트에 타입이 선언되기전까지 사용할 수 없어요.
개발자가 올바르게 타입을 정의했는지 알 수 있게 해줍니다.

어느것도 더이상 type이 올바른 것으로 간주하지 않기 때문에,
any -> unknown으로 이동하면서 모든 타입을 허용함 -> 아무것도 허용하지 않음으로 뒤집었습니다.

이 점이 unknown의 주요 가치에요.
알려지지 않은 타입에 대해 임의의 작업을 수행하지 않도록 한다고 해요.



unknown Type 좁히기

typeof, instanceof 연산자 등 다양한 방법으로 구체적인 type으로 좁힐 수 있어요.

function f20(x: unknown) {
  if (typeof x === "string" || typeof x === "number") {
    x; // string | number
  }
  if (x instanceof Error) {
    x; // Error
  }
  if (isFunction(x)) {
    x; // Function
  }
}

외에도 직접 코드 작성을 통해 타입을 좁힐 수도 있어요.



Type assertions과 unknown

const text: unknown = 'abc';
const abc = text as string;

as를 통해 타입 전환을 할 수도 있어요.

Typescript에서 assertion은 실제로 유효한지 확인하기 위해 별도의 검사를 수행하지 않아요.
그래서 신중하게 사용해야합니다.

const text: unknown = 'abc';
const abc = text as number;
abc.split('')
error! Property 'split' does not exist on type 'number'

런타임 시점에 에러가 발생해버릴 수 있거든요



intersection, union, conditional types, keyof

// intersection에서 unknown은 다 흡수해버립니다.
type T00 = unknown & null; // null
type T01 = unknown & undefined; // undefined
type T02 = unknown & null & undefined; // null & undefined (which becomes never)
type T03 = unknown & string; // string
type T04 = unknown & string[]; // string[]
type T05 = unknown & unknown; // unknown
type T06 = unknown & any; // any

// union에서 unknown이 다 흡수해버립니다.
type T10 = unknown | null; // unknown
type T11 = unknown | undefined; // unknown
type T12 = unknown | null | undefined; // unknown
type T13 = unknown | string; // unknown
type T14 = unknown | string[]; // unknown
type T15 = unknown | unknown; // unknown
type T16 = unknown | any; // any

// intersection과 union에서 unknown을 사용했을 때
type T20<T> = T & {}; // T & {}
type T21<T> = T | {}; // T | {}
type T22<T> = T & unknown; // T
type T23<T> = T | unknown; // unknown

// conditional types에서의 unknown
type T30<T> = unknown extends T ? true : false; // 제네릭에 의해 결정
type T31<T> = T extends unknown ? true : false; // 제네릭에 의해 결정
type T32<T> = never extends T ? true : false; // true
type T33<T> = T extends never ? true : false; // 제네릭에 의해 결정

// keyof unknown
type T40 = keyof any; // string | number | symbol
type T41 = keyof unknown; // never


as unknown as가 동작하는 이유

먼길을 온 것 같은 느낌이에요.
왜 동작하는지는 unknown에 대해 알게되면서 자연스레 알게 되었어요.

const def = 'string' as unknown as number;

말도 안되는 타입 선언이지만 이게 가능한데요.

왜냐면 unknown은 모든 유형에 할당이 가능하기 때문에, x as unknown으로 먼저 할당이 가능합니다.
그 다음으로 unknown as yy에 어떤 타입(string, number 등 모두)이든 다시 unknown에 할당 할 수 있게 되구요,

그래서 언제든 as unknown as는 성공하는 것이죠.

남용하면 타입 안정성을 잃게되기 때문에 아주 주의해서 사용해야하고,
가능하면 이 방법 대신 다른 방법을 사용하는게 좋습니다.



참고
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type
https://mariusschulz.com/blog/the-unknown-type-in-typescript

profile
Software Engineer

0개의 댓글