치춘짱베리굿나이스

any, unknown, never 본문

Javascript + Typescript/이론과 문법

any, unknown, never

치춘 2023. 8. 21. 16:29

any, unknown, never

anyunknown 둘이 상당히 비슷해 보이는데 살짝 다른, 특별한 타입 키워드이다

never는 갑자기 생각나서 추가했다

any

let a: any = 1;

any 쓰면 애니추천

모든 타입이 할당될 수 있는 타입이다

메타몽 같은 타입이라고 생각하면 된다… any는 무엇이든 될 수 있다

any를 쓴다는 것은 사실상 “타입 체크를 하지 마시오” 라고 말하는 것과 같다

특징

let a: any = 1;
a = [1, 2, 3];
a = "hello";
a = { name: "abc" }

모든 타입이 할당될 수 있다는 것은, 위처럼 모든 타입의 값들을 할당받을 수 있다는 뜻이다

any는 무엇이든 될 수 있기 때문에! 위처럼 어떠한 값을 대입하든 오류가 발생하지 않는다

 

let a: any = "hello";
let b: number = a;

any는 어떠한 타입이든 될 수 있기 때문에! 어떠한 타입의 변수에도 할당 가능하다

위와 같은 경우에도 정상적으로 할당이 가능하다

여담으로 위의 케이스는 당연히 컴파일도 안 되거나 NaN으로 처리될 줄 알았는데 bnumber가 아닌 string으로서 동작을 하더라…

 

let a: any = 1;

console.log(a + 1);
console.log(a.toString());
console.log(a.map((v: number) => v + 1));

다른 타입 객체에 존재하는 메서드 등을 마구잡이로 호출해도 정적 타입 검사에선 걸리지 않는다 (빨간 줄이 없는 것을 볼 수 있음)

any는 무엇이든~ 될 수 있기 때문에 모든 타입 검사를 만족한다

 

물론 실행시키면 해당 메서드가 실제로 존재하지 않기 때문에 터진다

 

console.log(a.unavailableWhateverMethod());

어느 타입에도 존재하지 않는 메서드를 아무렇게나 호출해도 정적 타입 검사에서 걸리지 않는다

세상에 타입이 얼마나 많은데 하나 쯤은 위와 같은 메서드를 가지고 있을 수도 있는 것 아닐까?

any는 어떠한 타입이든 될 수 있기 때문에 위와 같은 메서드도 가질 수 있는 타입으로 처리되는 것이다

 

let c;

선언만 하고 초기화가 없는 변수는 기본적으로 암묵적 any 처리된다

이게 싫다면 (아예 타입 검사 단계에서 오류로 처리하고 싶다면) tsconfig 에서 noImplicitAny 속성을 true로 바꿔두면 된다

사용처

타입스크립트의 타입 시스템을 일시적으로 우회하는 데에 많이 사용한다

자바스크립트로 작성된 코드를 타입스크립트로 순차적으로 마이그레이션 할 때, 우선 any를 사용해서 코드가 돌아가게끔 수정해둔 뒤 나중에 제대로 된 타입을 추가하는 식이다

단점

any는 모든 타입이 될 수 있기 때문에 위처럼 타입 검사에 걸리지 않으며, 이는 타입스크립트를 쓰는 가장 큰 이유를… 회피하는 것과 같다

any의 “모든 타입이 될 수 있다” 라는 부분은 의도치 않은 형변환과 위처럼 number 타입 변수에 string이 대입되는 등의 부작용을 일으킬 수 있으므로 프로그램의 안정성이 떨어지게 된다

또한 any는 한번 쓰면 다른 코드에서도 any를 써야 하는 경우가 점점 많아지는 등 코드 내부에서 퍼져나가기 때문에 더더욱 조심해야 한다

unknown

let a: unknown = 1;

any와 비슷하게 모든 타입이 될 수 있는 타입이다

any와 비슷하지만, 타입 추론을 아예 꺼 버리는 any와 다르게 좀 더 명시적으로 “나 여기 들어가야 할 타입이 뭔지 모르겠다” 라는 뜻을 가진다

unknown을 발견한다면 “여기 들어가야 하는 타입의 종류가 뭔지 모르겠으니 누가 좀 도와줘" 라고 생각하자

타입스크립트 문서에서의 unknown

TypeScript 3.0 introduces a new top type unknown. unknown is the type-safe counterpart of any. Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.

unknown은 타입스크립트 3.0에 추가된 최상위 타입으로, any의 타입-안전한 (type-safe) 대응책이라고 한다

어떠한 것이든 unknown에 할당할 수 있지만, unknown는 타입 단언 없이는 unknownany를 제외한 다른 타입에 할당할 수 없다

마찬가지로 첫 타입 단언 또는 다른 구체적인 타입으로 좁혀주지 않으면 unknown 변수에 다른 어떠한 연산도 불가능하다

특징

let a: unknown = 1;
a = [1, 2, 3];
a = "hello";
a = { name: "abc" }

unknownany와 마찬가지로 어떠한 타입이든 할당받을 수 있다

unknown 또한 위처럼 어떠한 값이 들어오더라도 오류가 발생하지 않는다

 

let a: unknown = "hello";
let b: number = a;

다만 여기서 any와의 차이점이 드러난다

unknown은 어떠한 타입이든 될 수는 있지만, 타입이 정해진 다른 변수에 할당이 불가능하다

 

let a: unknown = "hello";
let b: number = a as number;

unknown 타입은 명시적으로 타입을 변환해주지 않는 이상 다른 타입에 할당할 수 없다

반대로 말하면, 위처럼 명시적으로 다른 타입으로 변환해줬다면 정상적으로 할당이 가능하다

 

let a: unknown = "hello";

console.log(a + 1);
console.log(a.toUpperCase());
console.log(a[0]);

unknownany와 다르게 첫 타입 단언 없이는 어떠한 조작도 할 수 없다

모든 메서드들이 정적 타입 검사에 걸리지 않던 any와 달리, unknown은 어떠한 메서드나 연산을 실행시키든 타입 검사 시에 막혀버린다

unknown은 모든 타입이 가지고 있는 공통적인 연산밖에 수행할 수 없는데, 이게 문제는 사칙연산도 공통 연산에 포함이 안 된다는 것…

혹시 “모든 타입이 수행할 수 있는 공통적인 연산” 의 예시를 아시는 분은 제보 부탁드립니다,,

 

let a: unknown = "hello";

console.log((a as number) + 1);
console.log((a as string).toString());
console.log((a as Array<number>)[0]);

따라서 unknown 타입은 다른 타입으로 좁혀서 사용해줄 의무가 있다

위처럼 아무렇게나 타입을 명시해 주면 그에 해당하는 메서드 또는 연산을 사용할 수 있는 것을 볼 수 있다

장점

unknown의 “타입을 좁혀주지 않으면 연산이 불가한” 특성 때문에 어떠한 타입의 메서드든 마구 실행시켜 버리는 (그러고 오류가 나 버리는) any보다 조금 더 안전하다

any일 때 타입 체크를 그냥 스킵해버리는 이슈를 보완한 것과 마찬가지 효과를 내며, 타입 단언문이나 타입 체크를 필수적으로 수행함으로써 더 안전한 프로그램을 작성할 수 있다

never

function thisFunctionOnlyThrowsError(): never {
    throw new Error("Error!");
}

사실 never는 위의 두 타입과는 관계가 딱히 없다

일반적으로 함수의 반환값 타입으로 사용되며, “이 함수는 값을 반환할 리가 없다” 라는 의미를 내포한다

무조건 오류를 던지는 함수이거나, 무한 루프 함수의 경우 never 타입을 사용한다 (또는 그렇게 추론된다)

never에 대한 더욱 자세한 내용은 이 글을 읽어보자

특징

let a: never = 1;

never 타입은 어떠한 타입도 될 수 없다

 

let b: string & number = 1;

타입 집합이 잘못되었을 경우 (호환되지 않는 타입간의 교차 타입 등) 에도 never로 대체된다

never는 그 자체로 “불가능함” 을 뜻한다고 볼 수 있다

 

function foo(): never {
  throw new Error("");
}

let a = foo();
let b: string = a;
let c: number = a;
let d: boolean = a;
let e: any = a;

never 타입은 모든 타입의 하위 타입이기 때문에, unknown과 반대로

  • 어떠한 타입도 never 변수에 할당할 수 없지만
  • 모든 타입에 할당할 수 있다

참고 자료

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html

https://www.dgmunit1.com/blog/typescript/item38_44

https://stackoverflow.com/questions/51439843/unknown-vs-any

https://jbee.io/typescript/TS-9-unknown/

https://yamoo9.gitbook.io/typescript/types/never

https://ui.toast.com/posts/ko_20220323

Comments