Skip to content

타입스크립트 프로그래밍 - 보리스 체르니

Typescript, devBooks3 min read

보리스 체르니의 [ 타입스크립트 프로그래밍 ] O'Reilly Media의 원서의 번역본을 기초로, 이해한 내용을 편하게 작성했습니다.

TL;DR

  • 기본 타입(원시형 값의 타입)은 타입스크립트의 타입추론에게 맡기는 것이 좋다. 이때 선언 키워드( let, const )를 const로 한다면, 가장 좁게, 타입 리터럴로 추론한다. 이러한 특성을 활용하여 type assertion이 가능하다. 후에 살펴보자.

  • 참조형 값 - Object, Array - 은 const로 선언해도 타입을 더 좁게 추론하지 않고 let의 경우처럼 일반적인 추론이 적용된다.

  • enum 은 안전하게 사용하기 까다롭다.

  • 타입스크립트의 배열과 튜플은 멋지다.


타입스크립트의 타입.


타입은 값과 그 값으로 할 수 있는 일의 집합을 말한다.
어떤 타입 T에 대해 타입스크립트의 타입체커(typechecker)는 타입을 확인하여, 유효하지 않은 동작이 실행되는 것을 막는다.

예를 들면,

  • Boolean 타입은 모든 불(true or false)와 불 값으로 수행하는 연산(&&, ||, ! 등)의 집합이다.
  • number 타입은 모든 숫자와 숫자에 수행할 수 있는 모든 연산( +, -, *, /, %, ||, &&, ?, ! 등), 숫자에 호출할 수 있는 모든 메서드(.toFixed, .toPrecision, .toString 등) 의 집합이다.
  • string 타입은 모든 문자열과 문자열에 수행할 수 있는 모든 연산( +, - , *, /, % 등)과 문자열에 호출 할 수 있는 모든 메서드(.concat,.toUppercase 등) 의 집합이다.

타입을 명시한다는 것.


아래 예시에서, Type Annotation을 통해 타입스크립트에게 타입을 명시해주는 것을 보여 준다.

1// typescript
2function squareOf(n: number){
3 return n * n;
4}
5squareOf(2) // 4
6squareOf('x') // // Argument of type 'string' is not assignable to parameter of type 'number'.ts(2345)
1// javascript
2function squareOf(n){
3 return n * n
4}
5squareOf(2) // 4
6squareOf('x') // NaN

n의 타입에 경계 로서, number을 명시하여, 다른 타입의 값이 오지 못하게 하는 것이다. 일단 타입을 제한하면, 타입스크립트(컴파일러)는 함수를 호출 하는 시점에서 호환되는 인수로 호출되는 지 확인하여 에러가 있다면, 에러가 발생한다.


타입들.


any

  • 최후의 보루. 가급적 사용하지 않는 것이 좋다. 말 그대로 any , 즉, 모든 값과 모든 작업의 집합이기 때문에, type checker는 작동하지 않는다. typescript의 강력한 기능을 포기하려는 게 아니라면, 사용을 자제하자.

  • any는 명시적으로 선언하지 않았는데, any로 추론되는 경우1라면 typescript가 컴파일 에러를 일으킬 것이다2.

    1any로 추론되는 경우: 매개변수 타입 정의를 하지 않은 경우, 타입을 사용하지 않는 모듈을 임포트 한 경우 등.2any를 명시적으로 선언하여, 개발자의 의도를 밝혀서, 에러를 없앨 수 (는) 있다.

    TSC flag: noImplicitAny

    타입스크립트의 default 설정은 any로 추론되는 값을 허용한다. TSC에 대한 글에서 정리한 바와 같이, 이 플래그는 strict 패밀리에 속하므로, tsconfig.json에서 strict을 활성화하면, 함께 활성화된다.


unknown

이 부분에서 보리스 체르니의 비유가 흥미롭다. - 영화 Point Break 에서 은행강도 갱에 잠입한 FBI 요원으로 비유한다.

  • unknown은 any와 마찬가지로 모든 값을 대표하지만, 타입스크립트가 unknown 값에 대해 타입을 검사하여 정제하기 전 까지는 타입스크립트가 해당 값을 사용할 수 없게 강제한다.

    아래 예시를 통해 unknown의 동작과 사용법을 들여다 보자.

    1let a : unknown = 30 // unknown
    2// 타입스크립트가 어떤 타입을 unknown 타입으로 추론하는 경우는 없다.
    3let b = a === 120 // boolean
    4// unknown 타입과 boolean 타입의 값을 비교할 수 있다.
    5let c = a + 10
    6// unknown 타입의 값을 특정 타입이라고 가정하고 사용할 수 없다.
    7if(typeof a === 'number'){
    8 let d = a + 10
    9}
    10// 문맥을 통해 타입스크립트에게 해당 값의 타입을 증명해야 한다.
    11// 타입스크립트는 컴파일 타임에 typeof를 활용하여 타입을 확인하기 때문에 위와 같은 타입정제가 가능하다.

boolean

아래 예시를 통해 boolean의 동작과 사용법을 들여다 보자.

1let a = true // boolean
2const a = true // true
3// typescript가 a의 타입을 추론하게 한다. - const로 선언한다면, 가장 좁은 타입으로 추론되기 때문에, 타입 리터럴 true 타입으로 추론(강제)된다.

​ * 타입 리터럴은 타입 안정성Type Safety을 높여주며 타입스크립트의 차별되는 특성이다. 자바 같은 언어를 사용하는 친구에게 자랑할 만 하다 (!!!? 🤩)


number


bigint

  • bigint는 자바스크립트와 타입스크립트에 새로 추가된! 타입이다. 라운딩 관련 에러 걱정없이 큰 정수를 처리할 수 있다.
  • 모든 BigInt의 집합이다.

string


symbol

  • 유니크한 값을 만들 때 사용한다. symbol을 이용하면 사람들이 잘 알려진 키 (well-known key)만 사용할 수 있게 강제하기 때문에, 키를 잘못 설정하는 실수를 방지한다. 객체의 기본 반복자(Symbol.iterator)를 설정하거나 객체가 어떤 인스턴스인지(Symbol.hasInstance) 런타임에 오버라이딩하는 것과 비슷한 기능을 제공한다.

object

  • 객체 타입은 객체의 형태를 정의한다. 객체 타입만으로는, 객체 리터럴({})과 new키워드를 통해 만든 객체를 구분할 수 없다. 이는 자바스크립트가 구조 기반 타입 (structural type)을 갖게 설계되었기 때문이다. 따라서 타입스크립트도 이름 기반의 타입(nominal type) 스타일보다는 자바스크립트 스타일을 선호한다.

    구조기반 타입화 Structural Typing

    구조 기반의 타입화 방식에서는 객체의 이름에 상관없이 객체가 어떤 프로퍼티를 갖는 지를 따진다 - 이름 기반 타입화에서는 이름에 따른다. 일부 언어에서는 이를 덕 타이핑이라 한다. 간단히 말해 구조만 같다면 호환되는 특성을 말한다.

  • 객체를 타입과 함께 서술해보자!

    1let a : object = {
    2 b: 'x'
    3} // Property 'b' does not exist on type 'object'.ts(2339)

    예상과는 다른 결과다! objectany보다 약간 더 좁은 타입으로, 서술하는 값에 대해 정보를 알려주지 않으며, 값 자체가 자바스크립 객체라고 ( null 이 아니라고 ) 말해줄 뿐이다.

    => 타입스크립트의 추론에 맡겨보자!

    1let a1 = {
    2 b: 'x'
    3};
    4a1.b // string
    5let a2 = {
    6 c: {
    7 b: 'x'
    8 }
    9}
    10a2.c // (property) c: {
    11 b: string;
    12}

    객체 리터럴 문법에서는 선언된 객체의 구조를 그대로 추론한다! 식별자 a2의 경우 처럼, 객체 안의 객체 구조도 제대로 추론한다.

    타입스크립트에서 객체를 const를 선언할 때 타입 추론은 기본 타입의 경우와 다르게 여전히 let키워드로 선언할 때와 같은 추론결과를 내놓는다.

  • 객체 리터럴은 프로퍼티를 명시적으로 구조를 제한(선언)하는 것과 같다

    1let c= {
    2 firstName: "john",
    3 lastName: "McCoy",
    4};
    5
    6class Person {
    7 constructor(
    8 public firstName: string = "richard",
    9 public lastName: string = "Lim"
    10 ) {}
    11}
    12
    13c = new Person("Kim"); // works!

    위 예시에서 c 의 타입은 Person 클래스 인스턴스와 호환되므로 - 만족하므로 타입스크립트는 Person 인스턴스를 c에 할당하는 동작을 허용한다.

    기본적으로 타입스크립트는 객체 프로퍼티에 엄격한 편이다. 사전에 정의된 타입(구조)를 벗어나면 에러를 발생한다.

  • 선택형 프로퍼티 선언과 인덱스 시그니쳐

    1// optional property
    2let a : {
    3 b: number,
    4 c?: string,
    5 [key: number]: boolean
    6}
    7// index signature
    8let cinemaSeatingAssignment: {
    9 [seat: string]: string
    10} = {
    11 '34D': 'Danny DW Kim',
    12 '34E': 'Dan Abramov'
    13}

    인덱스 시그니쳐

    [key: T]: U 같은 문법을 인덱스 시그니쳐라 한다. 즉 '이 객체에서 모든 T타입의 키는 U타입의 값을 갖는다'라고 읽을 수 있다. 자바스크립트 객체의 프로퍼티 키는 문자열 또는 (배열 객체의 경우) 숫자를 키의 타입으로 사용하기 때문에, T타입은 number또는 string타입에 할당할 수 있는 타입이어야 한다.

  • readonly 한정자를 활용한 읽기전용 필드 정의

    1let user : {
    2 readonly firstName: string
    3} = firstName: 'anne'
    4user.firstName = 'anne with e' // Cannot assign to 'firstName' because it is a read-only property.ts(2540)
  • 저자 보리스 체르니의 객체 요약

    • 객체 리터럴 또는 형태라 불리는 표기법 {a: string}은 객체가 어떤 필드를 포함할지 알거나 객체의 모든 값이 같은 팁을 가질 때 사용할 수 있다.

    • 빈 객체 리터럴 표기법 {}. 이건 추천하지 않는다 - 빈 객체 타입에는 null과 undefined를 제외한 모든 타입을 할당 할 수 있다.

      1let danger: {};
      2danger = 3; // 여기서 에러없이 동작한다..! Type Safety를 생각한다면 쓰지말자
    • object 타입. 필드에 관심없고, 그저 객체가 필요할 때나 사용한다.

    • Object 타입. 사용하지 않는 것이 좋다.