Front-end/TypeScript

any와 unknown

이안92 2024. 2. 11. 17:55
반응형

🤪 anyscript

 

타입스크립트 프로젝트의 tsconfig에서 strict를 true로 설정하거나, noImplicitAny를 true로 설정하면, 각종 변수에 타입을 지정하지 않았을 때 에러가 발생합니다. 타입스크립트에서 기본적으로 타입 추론이라는 것을 하는데, 추론한 근거가 없으면 암시적으로 any로 할당됩니다. 이러한 것을 못하게 하는 옵션이 noImplicitAny(말 그대로, no implicit any – 암시적인 any는 no)를 true로 설정하는 것이고, strict를 true로 설정하면 noImplicitAny도 true로 설정되죠. 타입스크립트 프로젝트에서 strict 모드를 설정하는 것은, 프로젝트에 엄격한 룰을 강제해서 추후에 코드를 수정하기 쉽게 만들어주는데, 이러한 프로젝트에서 막상 타입 지정하기가 귀찮아서 any로 일단 선언해두고 사용하는 경우가 있습니다. 이런 게 반복되고 도배되기 시작하면, 이건 타입스크립트가 아니라 anyscript라고 부를 수도 있을 만한 상황이 될 수도 있겠죠. 즉, 귀찮아서 잠시 사용할 수 있을지는 몰라도, any는 어떤 타입이든 모두 올 수 있음을 말하기 때문에 자바스크립트를 쓰는 것과 다름이 없어지는 상황이 되어서 권장하지 않습니다.

 

😶‍🌫️ unknown?

 

비슷한 것으로 unknown도 있는데, any와의 차이는 뭘까요? any는 아무거나 다 올 수 있고, 그 이후 어떤 동작을 해도 되는 것에 비해, unknown도 아무거나 다 올 수 있지만, 그 이후는 어떤 동작도 허용하지 않습니다. 말이 어려운데 코드를 통해서 살펴보겠습니다.

const sayNumber = (num: number) => {
  console.log('num: ', num)
}

const sum = (a: any, b: any): any => a + b
const three = sum(1, 2)
sayNumber(three)

const minus = (a: any, b: any): unknown => a - b
const five = minus(7, 2)
sayNumber(five) // 'unknown' 형식의 인수는 'number' 형식의 매개 변수에 할당될 수 없습니다.

 

sayNumber라는 함수가 있습니다. 이 함수는 number 형식의 num이라는 값을 입력받아서, 콘솔에 출력해주는 함수입니다. 다음으로 sum이라는 함수는 a, b를 입력 받아서 a+b를 리턴해주는 함수입니다. 이 함수의 return type을 보면 any로 명시하였죠. 변수 three는 sum 함수에 1, 2를 전달해서 3이라는 return 값을 받았지만 이 변수의 type은 any입니다. minus라는 함수는 a, b를 입력 받아서 a-b를 리턴해주는 함수입니다. 이 함수의 return type은 unknown으로 명시하였습니다. 변수 five는 minus 함수에 7, 2를 전달해서 5라는 return 값을 받았지만 이 변수의 type은 unknown입니다.

 

⇒ sayNumber함수에 any type의 three를 전달하면, 아무런 에러 없이 "num: 3"이 출력됩니다. 하지만 sayNumber함수에 unknown type의 five를 전달하면 ‘unknown’ 형식의 인수는 ‘number’ 형식의 매개 변수에 할당될 수 없습니다. 라는 에러 메시지가 나오게 됩니다. 타입을 unknown으로 지정은 할 수 있지만, 해당 타입으로 지정된 값을 사용할 때가 되면 그 타입이 어떤 타입인지 명확히 알아야 사용할 수 있게 막아주는 역할을 한다고 보면 됩니다.

위 코드의 경우 minus 함수의 return type을 명시적으로 지정해주거나 five 변수의 type을 number라고 타입 단언을 해주면 사용할 수 있게 됩니다.

const minus = (a: any, b: any): unknown => a - b
const five = minus(7, 2) as number
sayNumber(five)

 

물론 위의 예제처럼 타입 단언을 이용하기보다는 minus 함수의 타입을 명확하게 지정해주거나 generic을 사용하는 게 일반적이긴 하겠지만, unknown을 설명하기 위해 만든 예제입니다.

다시 정리해보면 any, unknown 둘 다 ‘아무거나’에 들어갈 수 있지만 unknown은 그것을 사용하는 쪽에서 특별한 처리를 강제하게 해서, 안전하게 사용할 수 있게 해줍니다.

 

🔏 unknown을 사용해야 할 상황

 

위에서 unknown에 대해서 알아보았는데, any와 같이 사용하지 말아야만 할까요? 적은 상황이지만, unknown은 아래의 예제와 같이 사용할 케이스가 있습니다.

const isNil = (param: unknown): boolean => param === null || param === undefined

isNil이라는 함수는 param을 입력받아서, 해당 param이 null이나 undefined인지를 체크해서 true/false를 리턴해줍니다. 이 경우, param은 단순히 null, undefined인지에 대한 체크만 하기 때문에, 어떠한 타입이 들어올지 몰라도 됩니다. 이때 unknown을 사용할 수 있습니다. 물론 이때 any를 사용할 수도 있지요. 프로젝트의 설정에 따라서 any를 아예 사용하지 못하게 하는 경우도 있는데, isNil의 param 같은 경우 unknown 타입이 적절하다고 생각합니다.

하나의 예시를 더 들어보겠습니다.

function prettyPrint(x: unknown): string {
  if (Array.isArray(x)) {
    return "[" + x.map(prettyPrint).join(", ") + "]"
  }
  if (typeof x === "string") {
    return `"${x}"`}
  if (typeof x === "number") {
    return String(x)
  }
  return "etc."
}

위 prettyPrint 함수는 x라는 param을 입력받는데, 그 param이 array, string, number일 때와 그 밖의 상황에 대해서 처리를 하고 있는데, 3번째 줄에 있는 map(), join()은 array에서 사용할 수 있는 javascript 내장함수입니다. x가 any일 때는, x가 어떤 것이라고 하더라도 map(), join()을 사용할 수 있는데, x가 unknown일 때는, x가 array일 때만 map(), join()을 사용할 수 있게 강제해줍니다. (그냥 사용한다면, ‘x’ is of type ‘unknown’. 이라는 에러 메시지를 보여줍니다) 따라서, any 대신 unknown을 사용해서 더 안전한 코드를 작성할 수 있게 됩니다.

 

📌 any, unknown 결론

 

  • any: 아무때나 쓸 수 있지만, typescript를 쓰는 의미가 사라짐
  • unknown: 어쩔 수 없이 써야하는 상황이 있을 때, 방어처리를 통해 안전하게 사용가능함
반응형