TypeScript 빙산 #1

TypeScript/src에서 타입 정의 까보기


Table Of Contents


들어가기


이 그림을 본 적이 있나요?

TypeScript Iceberg

나는 이 그림을 본 지 벌써 1년이 넘어 가는 것 같은데... 위쪽에 위치한 키워드들도 아직 정확히 설명할 수 없는 것들이 있다.

그래서 오늘부터 하나씩 차근차근 포스팅해보려 한다.

string / boolean


  • JS에는 원시 값(primitive)이라는 개념이 있다. 원시 값은 객체(Object)가 아니면서 메소드 또는 속성도 가지지 않는 데이터이다. 하지만 마치 메소드가 있는 것처럼 동작하는데, 이는 JS가 래퍼 객체로 원시 값을 감싼 다음에 래퍼 객체의 속성에 접근하기 때문이다.
    • 래퍼 객체에 대한 설명은 다음에 다루겠다🥰
    • 원시 값에는 아래와 같이 총 7가지 종류가 있다.
      • string
      • number
      • bigint
      • boolean
      • undefined
      • symbol
      • null
    • TS에서는 이들 중에서string, number, boolean에 대응하는 타입이 존재한다.

any


  • TS에 존재하는 특별한 타입이다.
  • 특정 값으로 인하여 타입 검사 오류가 발생하는 것을 원하지 않을 때 사용한다. 또는 개발자가 타입을 명시하지 않으면서 TS가 타입을 추측할 수 업을 때, 자동으로 any 타입이 되기도 한다.
  • any타입 값에는 아무 속성으로 접근하거나, 함수처럼 호출하거나, 아무 타입의 값을 대입하거나 할 수 있다.

unknown


  • 역시 TS에 존재하는 특별한 타입이다.
  • any와 비슷하게 아무 값이나 표현할 수 있다.
  • 하지만 any와는 달리 unknown 타입 값에는 어떤 행동도 할 수 없다.

Array<T>


  • 배열의 타입을 나타낼 때, T[] 또는 Array<T>를 사용할 수 있다. 이 둘은 완전히 동일한 타입이다.
    • 예를 들어서, 문자열 배열의 경우에는 string[] 또는 Array<string> 모두 표현 가능하다.
  • Array<T>는 제네릭(generic)을 사용했기 때문에 제네릭을 사용한 다른 타입과 같이 사용하기 좋다.
    • (제네릭은 바로 아래 단계에 있는데 왜 Array<T>가 먼저 나왔는지 모르겠다🤔 제네릭은 다음 글에서 다룰 테니 놀러와주세용)

Class


Class는 JS의 문법으로, 객체를 생성하기 위한 템플릿이다.

Class 선언을 이용하여 Class를 정의하는 과정에서 type을 입히는 예시는 아래와 같다.

class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } }

Error


Error는 JS의 표준 내장 객체 중 하나다. 런타임 에러가 발생하면 JS에서는 Error 객체를 만들어 돌려준다.

TS에서는 이 객체를 다루기 위해 TS에 Error 타입을 정의해서, 우리는 기본적으로 Error 타입을 사용할 수 있다.

TypeScript/src/lib/es5.d.ts 파일 내에서 아래와 같은 기본적인 Error 타입 정의를 찾아볼 수 있다.

interface Error { name: string; message: string; stack?: string; }

추가로, es2022에서는 Error 클래스에 cause 속성이 추가되었다. 이에 맞춰서 TypeScript에서도 TypeScript/src/lib/es2022.error.d.ts 파일 안에 아래와 같은 interface 선언을 추가해 타입을 확장했다.

interface Error { cause?: unknown; }

cf) TypeScript에서는 같은 이름으로 interface를 여러 번 선언하면, 선언 병합(Declaration Merging) 이 일어나 각 선언이 하나의 interface로 합쳐진다. 따라서 이렇게 다른 파일에서 같은 이름으로 각각 인터페이스를 선언해 확장할 수 있다.

Date


  • Date 역시 JS의 표준 내장 객체 중 하나다.
  • TS에서는 Date 객체를 다루기 위해 TypeScript/src/lib/es5.d.ts 파일에 Date 타입을 정의해두었다.
    • toString(), toDateString()같은 메소드들의 타입이 모두 정의되어 있는 걸 확인할 수 있다!
interface Date { /** Returns a string representation of a date. The format of the string depends on the locale. */ toString(): string; /** Returns a date as a string value. */ toDateString(): string; /** Returns a time as a string value. */ toTimeString(): string; ... 후략 }

Promise<string>


Promise 역시 JS의 표준 내장 객체 중 하나로, 비동기 작업이 맞이할 미래의 완료 도는 실패와 그 결과값을 나타낸다.

여기에서도 Generic이 사용되었다.

살짝 설명하자면... Promise는 원래 비동기 작업의 결과를 나타낸다고 위에서 말했는데, 이 결과값의 타입을 <string> 부분을 이용해서 선언해주고 있는 것이다.

  • Promise<string>string 타입의 값을 결과값으로 가진다는 뜻이다.
  • Promise<number>처럼 쓰면 결과값이 number타입이라고 선언하는 것이다.

그래서 TypeScript/src/lib/es5.d.ts에 정의된 Promise의 타입 선언을 보면, Generic을 사용해 Promise<T>처럼 사용하고 있다. 즉, Promise가 처리할 값의 타입을 T로 둔다는 뜻이다.

  • 아래 then 메서드를 보면, 인자로
    1. Promise가 성공적으로 처리되었을 때 호출되는 콜백 함수 onfulfilled
    2. Promise가 실패했을 때 호출되는 콜백 함수 onrejected를 받고 있다.
  • 그리고 이 콜백 함수들이 반환하는 값을 제네릭 타입 매개변수 <TResult1 = T, TResult2 = never>처럼 정의해주고 있다.
    • onfulfilled의 반환값TResult1 타입이다. 이 타입은 TResult1 = T에서 볼 수 있읏이, 기본값으로 T 타입과 동일하게 설정된다.
    • 즉, Promise<T>에서 onfulfilled 함수는 T 타입의 값을 반환하는데, 이 타입을 TResult1이라는 이름으로 선언하고 있다. 만약 다른 타입은 반환하고 싶은 경우, TResult1에 다른 타입을 지정할 수도 있다.
    • onrejected의 반환값TResult2 타입이다. 기본값은 never이고, onrejected 콜백이 실행되지 않거나, 반환되는 값이 없음을 나타낸다.
    • 마찬가지로 오류 처리 결과를 다른 타입으로 반환하고 싶다면, TResult2에 다른 타입을 지할 수 있다.
  • 마지막으로, then 메서드는 이 TResult1 혹은 TResult2인 결과값을 Promise로 감싸 반환하는 것을 알 수 있다. (: Promise<TResult1 | TResult2>)
interface Promise<T> { /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ then<TResult1 = T, TResult2 = never>( onfulfilled?: | ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: | ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null ): Promise<TResult1 | TResult2>; /** * Attaches a callback for only the rejection of the Promise. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of the callback. */ catch<TResult = never>( onrejected?: | ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null ): Promise<T | TResult>; }

BigInt


위에서 JS의 원시 값(primitive) 중 bigint가 있다고 했다. 따라서 TS에서도 bigint(소문자다!) 타입이 존재한다.

그런데 여기에서는 BigInt를 언급했으므로, JS의 표준 내장 객체 중 하나인 BigInt(대문자에 주의)를 가리키는 것 같다.

BigIntNumber가 나타낼 수 있는 최대치인 25312^{53} - 12531보다 큰 정수를 표현할 수 있는 내장 객체이다.

따라서 TS에서는 BigInt를 다루기 위해 타입을 따로 선언해 두었는데, BigInt는 es2020부터 도입되었으므로 TypeScript/src/lib/es2020.bigint.d.ts에서 타입 선언을 찾아볼 수 있다.

interface BigInt { /** * Returns a string representation of an object. * @param radix Specifies a radix for converting numeric values to strings. */ toString(radix?: number): string; /** Returns a string representation appropriate to the host environment's current locale. */ toLocaleString( locales?: Intl.LocalesArgument, options?: BigIntToLocaleStringOptions ): string; /** Returns the primitive value of the specified object. */ valueOf(): bigint; readonly [Symbol.toStringTag]: "BigInt"; }

(string | boolean)[]


  • TS에서는 |을 이용해서 유니온 타입(Union Types) 을 정의할 수 있다.
  • 유니온 타입을 이용하면 2개 이상의 타입들을 결합해 새로운 타입들을 만들 수 있다.
    • 이 때, 결합된 각각의 타입들은 유니온의 member라고 부른다.
    • const pet: Dog | Cat = new Dog() 처럼 사용하면, pet에는 Dog 또는 Cat 타입 객체를 할당할 수 있다. 따라서 new Dog()으로 Dog 타입 객체를 대입하는 것은 올바른 문법이다.
  • (string | boolean)[]string 혹은 boolean 타입의 값을 모두 포함할 수 있는 배열을 의미한다.
  • string[] | boolean[] 타입과는 헷갈리지 말아야 한다.
    • (string | boolean)[] 타입은 [1, false, true, 5, false, 2]처럼 number 또는 boolean이 각 자리에 섞여 있어도 된다는 뜻이다.
    • string[] | boolean[] 타입은 string[] 또는 boolean[] 타입이므로, ['a', 'b', 'c']처럼 string만의 배열이거나, [true, true, false, true]처럼 boolean만의 배열이어야 한다.

참고