TypeScript 2장(6~10)

이종서·2022년 10월 28일
1

TypeScript

목록 보기
1/9

Item 06. 편집기를 사용하여 타입 시스템 탐색하기

타입스크립트를 설치하면 다음 두가지를 실행항 수 있습니다.

  • 타입스크립트 컴파일러(tsc)
  • 단독으로 실행할 수 있는 타입스크립트 서버(tsserver)

보통은 타입스크립트 컴파일러를 실행하는 것이 주된 목적이지만, 타입스크립트 서버 또한 '언어 서비스'를 제공한다는 점에서 중요합니다.

  • 언어서비스 : 코드 자동완성, 명세(사양,specification) 검사, 검색, 리팩터링

Item 07. 타입이 값들의 집합이라고 생각하기

런타입에 모든 변수는 자바스크립트 세상의 값으로부터 정해지는 각자의 고유한 값을 가집니다.
그러나 코드가 실행되기 전, 즉 타입스크립트가 오류를 체크하는 순간에는 '타입'을 가지고 있습니다. '할당 가능한 값들의 집합'이 타입이라고 생각하면 됩니다.

  1. 가장 작은 집합은 아무 값도 포함하지 않은 공집합이며, 타입스크립트에서는 never 타입니다.
    never : 일반적으로 함수의 리턴 타입으로 사용됩니다. 함수의 리턴 타입으로 never가 사용될 경우, 항상 오류를 출력하거나 리턴 값을 절대로 내보내지 않음을 의미합니다. 이는 무한 루프(loop)에 빠지는 것과 같습니다.
let never_type:never;
 
// 오류 발생: 숫자 값을 never 타입 변수에 할당할 수 없습니다.
never_type = 99;
  1. 한 가지 값만 포함하는 타입 - 유닛(unit) 타입 / 리터널(literal) 타입
type A = 'A';
type B = 'B';
type Twelve = 12;
  1. 두 개 혹은 세 개로 묶으려면 유니온(union) 타입을 사용합니다. - 값 집합들의 합집합
type AB = 'A' | 'B';
type AB12 = 'A' | 'B' | 12;

집합의 관점에서, 타입 체커의 주요 역할은 하나의 집합이 다른 집합의 부분 집합인지 검사하는 것이라고 볼 수 있습니다.
타입이 집합이라는 관점에서 extends의 의미는 '~에 할당 가능한'과 비슷하게, '~의 부분 집합'이라는 의미로 받아들일 수 있습니다.

요약

📌 타입을 값의 집합으로 생각하면 이해하기 편합니다.(타입의 '범위')
📌 타입스크립트 타입은 엄격한 상속 관계가 아니라 겹쳐지는 집합(벤 다이어그램)으로 표현됩니다. 두 타입은 서로 서브타입이 아니면서도 겹쳐질 수 있습니다.
📌 한 객체의 추가적인 속성이 타입 선언에 언급되지 않더라도 그 타입에 속할 수 있습니다.

Item 08. 타입 공간과 값 공간의 심벌 구분하기

타입스크립트의 심벌(symbol)은 타입 공간이나 값 공간 중의 한 곳에 존재합니다. 심벌은 이름이 같더라도 속하는 공간에 따라 다른 것을 나타낼 수 있기 때문에 혼란스러울 수 있습니다.

한 심벌이 타입인지 값인지는 언뜻 봐서 알 수 없습니다. 어떤 형태로 쓰이는지 문맥을 살펴 알아내야 합니다.

type T1 = 'string literal';
type T2 = 123;
const v1 = 'string literal';
const v2 = 123;

일반적으로 type이나 interface 다음에 나오는 심벌은 타입인 반면, const나 let 선언에 쓰이는 것은 값입니다.
컴파일 관정에서 타입 정보는 제거되기 때문에, 심벌이 사라진다면 그것은 타입에 해당될 것입니다.

타입 선언(:) 또는 단언문(as) 다음에 나오는 심벌은 타입인 반면, = 다음에 나오는 모든 것은 값입니다.

interface Person {
	first: string;
    last: string;
}

const p: Person = { first: 'Jane', last: 'Jacobs' };
//    -           --------------------------------- 값
//       ------                                     타입

function email(p: Person, subject: string, body: string): Response {
	//   ----- -          -------          ----                     값
    //            ------           -------       -------  --------- 타입
    // ...
}

클래스가 타입으로 쓰일 때는 형태(속성과 메서드)가 사용되는 반면, 값으로 쓰일 때는 생성자가 사용됩니다.
한편, 연산자 중에서도 타입에서 쓰일 때와 값에서 쓰일 때 다른 기능을 하는 것들이 있습니다. 그 예 중 하나로 typeof를 들 수 있습니다.

type T1 = typeof p;       // 타입은 Person
type T2 = typeof email;   // 타입은 (p: Person, subject: string, body: string) => Response

const v1 = typeof p;     // 값은 "object"
const v2 = typeof email; // 값은 "function"

값의 관점에서 typeof는 자바스크립트 런타임의 typeof 연산자가 됩니다.
값 공간의 typeof는 대상 심벌의 런타임 타입을 가리키는 문자열을 반환하며, 타입스크립트 타입과는 다릅니다.
(타입스크립트 타입의 종류가 무수히 많은 반면, 자바스크립트는 단 6개의 런타임 타입만 존재합니다. - string, number, boolean, undefined, object, function)

  • 값으로 쓰이는 this는 자바스크립트의 this 키워드입니다. 타입으로 쓰이는 this는, 일명 '다형성(polymorphic) this'라고 불리는 this의 타입스크립트 타입입니다. 서브 클래스의 메서드 체인을 구현할 때 유용합니다.
  • 값에서 &와 |는 AND와 OR 비트연산입니다. 타입에서는 인터섹션과 유니온입니다.
  • const는 새 변수를 선언하지만, as const는 리터널 또는 리터널 표현식의 추론된 타입을 바꿉니다.
  • extends는 서브클래스(class A extends B) 또는 서브타입(interface A extends B) 또는 제너릭 타입의 한정자(Generic)를 정의 할 수 있습니다.
  • in은 루프(for (key in object)) 또는 매핑된(mapped) 타입에 등장합니다.

요약

📌 모든 값은 타입을 가지지만, 타입은 값을 가지지 않습니다. type과 interface 같은 키워드는 타입 공간에만 존재합니다.
📌 class나 enum 같은 키워드는 타입과 값 두 가지로 사용될 수 있습니다.
📌 typeof, this 그리고 많은 다른 연산자들과 키워드들은 타입 공간과 값 공간에서 다른 목적으로 사용될 수 있습니다.

Item 09. 타입 단언보다는 타입 선언을 사용하기

타입스크립트에서 변수에 값을 할당하고 타입을 부여하는 방법은 두 가지입니다.

interface Person { name: string };
const alice: Person = { name: 'Alice' }; // 타입은 Person
const bob = { name: 'Bob' } as Person;   // 타입은 Person
  1. alice: Person은 변수에 '타입 선언'을 붙여서 그 값이 선언된 타입임을 명시합니다.
  2. as Person은 '타입 단언'을 수행합니다. 그러면 타입스크립트가 추론한 타입이 있더라도 Person 타입으로 간주합니다.

❗️ 타입 단언 보다는 타입 선언을 사용하는게 낫습니다.

const alice: Person = {};
   // ~~~~~ 'Person' 유형에 필요한 'name' 속성이 '{}' 유형에 없습니다.
const bob = {} as Person; // 오류 없음

타입 선언은 할당되는 값이 해당 인터페이스를 만족하는지 검사합니다.
(타입 단언은 강제로 타입을 지정했으니 타입 체커에게 오류를 무시하라고 하는 것입니다.)

⭐️ 타입 단언이 꼭 필요한 경우가 아니라면, 안전성 체크도 되는 타입 선언을 사용하는 것이 좋습니다.

❗️ const bob = {} 는 단언문의 원래 문법이며 {} as Person과 동일합니다. 그러나 const bob = {} 같은 코드는 이 .tsx 파일(타입스크립트 + 리액트)에서 컴포넌트 태그로 인식되기 때문에 현재는 잘 쓰이지 않습니다.

단언문을 쓰지 않고, 화살표 함수 안에서 타입과 함께 변수를 선언하는 것이 가장 직관적입니다.
다음 코드는 최종적으로 원하는 타입을 직접 명시하고, 타입스크립트가 할당문의 유효성을 검사하게 합니다.

const people: Person[] = ['alice', 'bob', 'jan'].map(
  (name): Person => ({name})
);

그러나 함수 호출 체이닝이 연속되는 곳에서는 체이닝 시작에서부터 명명된 타입을 가져야 합니다. 그래야 정확한 곳에 오류가 표시됩니다.

📍 타입 단언이 꼭 필요한 경우 (타입 단언은 타입 체커가 추론한 타입보다 여러분이 판단하는 타입이 더 정확할 때 의미가 있습니다.)

  • DOM 엘리먼트에 대해서는 타입스크립트보다 여러분이 더 정확히 알고 있을 겁니다.
document.querySelector('#myButton').addEventListener('click', e => {
  e.currentTarget // 타입은 EventTarget
  const button = e.currentTarget as HTMLButtonElement;
  button         // 타입은 HTMLButtonElement
})
  • 자주 쓰이는 특별한 문법(!)을 사용해서 null이 아님을 단언하는 경우도 있습니다.
const elNull = document.getElementById('foo'); // 타입은 HTMLElement | null
const el = document.getElementById('foo')!;    // 타입은 HTMLElement

변수의 접두사로 쓰인 !는 boolean의 부정문입니다. 그러나 접미사로 쓰인 !는 그 값이 null이 아니라는 단언문으로 해석됩니다.

❗️ 모든 타입은 unknown의 서브타입이기 때문에 unknown이 포함된 단언문은 항상 동작합니다. (unknown 단언은 임의의 타입 간에 변환을 가능케 하지만, unknown을 사용한 이상 적어도 무언가 위험한 동작을 하고 있다는 걸 알 수 있습니다.)

요약

📌 타입 단언(as Type)보다 타입 선언(: Type)을 사용해야 합니다.
📌 화살표 함수의 반환 타입을 명시하는 방법을 터득해야 합니다.
📌 타입스크립트보다 타입 정보를 더 잘 알고 있는 상황에서는 타입 단언문과 null 아님 단언문을 사용하면 됩니다.

Item 10. 객체 래퍼 타입 피하기

자바스크립트에는 객체 이외에도 기본형 값들에 대한 일곱 가지 타입(string, number, boolean, null, undefined, symbol, bigint)이 있습니다.

기본형인 string의 경우 메서드를 가지고 있는 것처럼 보입니다.

> 'tomato'.charAt(3)
   "a"

string '기본형'에는 메서드가 없지만, 자바스크립트에는 메서드를 가지는 String '객체' 타입이 정의되어 있습니다.
자바스크립트는 기본형을 String 객체롤 래핑(wrap)하고, 메서드를 호출하고, 마지막에 래핑한 객체를 버립니다.

⭐️ 자바스크립트는 기본형과 객체 타입을 서로 자유롭게 변환합니다.

  1. String 객체는 오직 자기 자신하고만 동일합니다.
> "hello" === new String("hello")
   false
  
> new String("hello") === new String("hello")
  false 
  
> const st = new String("hello")
> st === st
  true
  1. 객체 래퍼 타입의 자동 변환은 종종 당황스러운 동작을 보일 때가 있습니다. (어떤 속성을 기본형에 할당한다면 그 속성이 사라집니다.)
> x = "hello"
> x.language = 'English'
'English'
> x.language
undefined

실제로는 x가 String 객체로 변환된 후 language 속성이 추가되었고, language 속성이 추가된 객체는 버려진 것입니다.

타입 스크립트는 기본형과 객체 래퍼 타입을 별도로 모델링합니다.

  • string과 String
  • number와 Number
  • boolean과 Boolean
  • symbol과 Symbol
  • bigint와 BigInt

❗️ string은 String에 할당할 수 있지만 String은 string에 할당할 수 없습니다.
대부분 라이브러리와 마찬가지로 타입스크립트가 제공하는 타입 선언은 전부 기본형 타입으로 되어있습니다.

기본형 타입은 객체 래퍼에 할당할 수 있기 때문에 타입스크립트는 기본형 타입을 객체 래퍼에 할당하는 선언을 허용합니다. 그러나 그냥 기본형 타입을 사용하는 것이 낫습니다.
그런데 new 없이 BigInt와 Symbol을 호출하는 경우는 기본형을 생성하기 때문에 사용해도 좋습니다.

> typeof BigInt(1234)
"bigint"
> typeof Symbol('sym')
"symbol"

이들은 BigInt와 Symbol '값'이지만, 타입스크립트 타입은 아닙니다. 앞 예제의 결과는 bigint와 symbol 타입의 값이 됩니다.

요약

📌 기본형 값에 메서드를 제공하기 위해 객체 래퍼 타입이 어떻게 쓰이는지 이해해야 합니다. 직접 사용하거나 인스턴스를 생성하는 것은 피해야 합니다.
📌 타입스크립트 객체 래퍼 타입은 지양하고, 대신 기본형 타입을 사용해야 합니다. String 대신 string, Number 대신 number ... 등을 사용해야 합니다.

profile
프론트엔드 개발자

0개의 댓글