러닝 타입스크립트 3장
포스트
취소

러닝 타입스크립트 3장

해당 포스트는 러닝 타입스크립트 3장을 읽고 정리한 포스트입니다.

🧬 유니언 ( union )

값에 허용된 타입을 두 개 이상의 가능한 타입으로 확장하는 것을 의미합니다.

타입을 확장했기 때문에 더 많은 타입이 들어갈 수 있도록 유연하긴 하지만 사용에 제한이 걸립니다.
유니언 타입 모두가 갖고 있는 공통된 멤버에만 접근할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
// myValue1: string | number
let myValue1: string | number;

// myValue2: string | number
let myValue2 = Math.random() > 0.5 ? "Aatrox" : 2;

// "string"과 "number" 모두 갖고 있는 메서드
myValue2.toString();

// Error: Property 'toFixed' does not exist on type 'string | number'.
myValue2.toFixed(); // "number"만 갖고 있는 메서드

🛤️ 내로잉 ( narrowing )

값에 허용된 타입들(유니언)을 좁히는 것을 의미합니다.
즉, 여러 타입들중에서 더 구체적인 타입인 것을 타입 체커에게 알려주는 것입니다.

0️⃣ 값 할당을 통한 내로잉

유니온 타입의 경우 값 할당을 통해서 타입을 좁힐 수 있습니다.

1
2
3
4
let myValue: number | string = "Aarox";

// Error: Property 'toFixed' does not exist on type 'string'.
myValue.toFixed();

1️⃣ 조건 검사를 통한 내로잉

조건문을 통해서 유니온 타입을 좁힐 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
let myValue = Math.random() > 0.5 ? "Aatrox" : 2;

// 타입 좁히기
if (myValue === 2) {
  // myValue: 2
  myValue.toFixed();
}
// 타입 좁히기
if (myValue === "Aatrox") {
  // myValue: "Aatrox"
  myValue.toUpperCase();
}

2️⃣ typeof 검사를 통한 내로잉

typeof 연산자를 이용하면 조금 더 넓게 타입을 좁힐 수 있습니다.
단순한 조건 검사를 통한 내로잉보다 더 범용적으로 사용하는 방법입니다.

1
2
3
4
5
6
7
8
9
10
11
12
let myValue = Math.random() > 0.5 ? "Aatrox" : 2;

myValue.toString();

if (typeof myValue === "number") {
  // myValue: number
  myValue.toFixed();
}
if (typeof myValue === "string") {
  // myValue: string
  myValue.toUpperCase();
}

3️⃣ 논리 연산자를 통한 내로잉

if, &&, ||, 삼항 연산자 등을 이용해서 falsy한 값을 제외한 내로잉이 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare let myValue: string | undefined;

if (myValue) {
  // myValue: string
  myValue.toUpperCase();
}

// myValue: string
myValue && myValue.toUpperCase();

// myValue: string
!myValue || myValue.toUpperCase();

// myValue: string
myValue ? myValue.toUpperCase() : null;

단 주의해야 할 점은 ""0 같은 falsy한 값이 있기 때문에 타입이 원하는 대로 추론되지 않을 수 있습니다.

1
2
3
4
5
6
7
8
9
declare let myValue: string | undefined;

if (myValue) {
  // myValue: string
  myValue.toUpperCase();
} else {
  // myValue: string | undefined
  myValue; // 빈 문자열("")도 "falsy"한 값이기 때문에 "undefined"로 확신할 수 없음
}

💀 리터럴 타입 ( literal type )

원시 타입중 하나가 아닌 특정 원싯값으로 알려진 타입을 의미합니다.
즉, string이 아닌 "Aatrox", 10 등의 구체적인 타입을 리터럴 타입이라고 합니다.
( string, number 같은 원시 타입은 해당 타입의 모든 리터럴 타입의 집합체입니다. )

1
2
// myValue: "Aatrox"
const myValue = "Aatrox";

0️⃣ 리터럴 타입 할당 가능성

동일한 원시 타입이라고 하더라도 서로 다른 리터럴 타입이라면 할당이 불가능합니다.

1
2
let myValue: "Aatrox" = "Aatrox";
myValue = "Puppy"; // Error: Type '"Puppy"' is not assignable to type '"Aatrox"'

또한 넓은 타입에서 좁은 타입으로 할당이 가능((1))하지만, 반대는 불가능((2))합니다.

1
2
3
4
5
6
7
8
// (1)
let myValue1: string;
myValue1 = "Aatrox"; // "Aatrox" => string

// (2)
let myValue2: "Aatrox";
// Error: Type 'string' is not assignable to type '"Aatrox"'.
myValue2 = myValue1; // string => "Aatrox"

⛑️ 엄격한 null 검사

모든 타입에 nullundefined를 허용할지 여부를 결정하는 것입니다.

0️⃣ strictNullChecks

strictNullChecks 옵션을 이용해서 설정할 수 있습니다.
( 엄격하게 null을 검사하는 것이 더 모범적이라고 합니다. )

strictNullChecks: false로 설정하면 모든 타입에 | null | undefined가 추가된 것처럼 동작합니다.

  • tsconfig.json
1
2
3
4
5
6
{
  "compilerOption": {
    "strictNullChecks": true, /* When type checking, take into account null and undefined */
    // ... 생략
  }
}

아래 예시는 null 체크 여부에 null을 넣었을 때의 결과입니다.

1
2
3
4
5
6
7
8
9
let myValue: string;

// strictNullChecks: true
myValue = null; // Error: Type 'null' is not assignable to type 'string'.
myValue = undefined; // Error: Type 'undefined' is not assignable to type 'string'.

// strictNullChecks: false
myValue = null; // 문제 없음
myValue = undefined; // 문제 없음

1️⃣ 초깃값이 없는 변수

초깃값을 넣어주지 않은 변수라면 타입이 정해져 있더라도 타입 체커가 인지하고 타입 에러를 띄워줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
let myValue1: string;

// Error: Variable 'myValue1' is used before being assigned.
myValue1.length;
// Error: Variable 'myValue1' is used before being assigned.
myValue1?.length;

let myValue2: string | undefined;

// Error: Variable 'myValue2' is used before being assigned.
myValue2.length;
// 정상 동작
myValue2?.length;

⛳ 타입 별칭

type 키워드를 이용해서 타입에 대한 변수인 타입 별칭을 생성할 수 있습니다.

1
2
3
type MyValue = string | number;

let myValue: MyValue = "Aatrox";

0️⃣ 타입 별칭은 TypeScript만의 문법

타입 별칭은 JavaScript에 존재하지 않습니다.
즉, 컴파일하고 나면 사라집니다. ( 런타임에 존재하지 않음 )

바로 위의 코드를 컴파일하면 아래와 같은 결과가 나옵니다.
( tsconfig.json의 옵션에 따라 조금씩 달라질 수 있지만, 핵심은 type이 없어진다는 것입니다. )

1
2
"use strict";
let myValue = "Aatrox";

타입 별칭은 유니온 / 인터섹션 등으로 결합될 수 있습니다.
여기서 중요한 점은 MyValueWithNumberMyValue를 참조하기 때문에 MyValue가 변하면 MyValueWithNumber도 동기화 된다는 점입니다.

1
2
3
4
type MyValue = string | undefined;

// 즉, "type MyValueWithNumber = number | string | undefined"
type MyValueWithNumber = MyValue | number;

❓ 의문

0️⃣ 교재 72p ( strictNullCheck )

strictNullCheck를 비활성화면 코드의 모든 타입에 | null | undefined를 추가해야 모든 변수가 null 또는 undefined를 할당할 수 있습니다.”라고 적혀있는데 “비활성화”가 아니라 “활성화하면”이라고 적어야 맞지 않나라고 생각해서 기록합니다.

활성화해야 null 체크를 엄격하게 하기 때문에 null을 따로 추가해줘야 변수에 할당할 수 있는 것이 맞지 않나요…?

1️⃣ 타입 내로잉

타입 내로잉에서는 왜 falsy한 값을 제대로 추론하지 않을까요…?

string에는 falsy한 값인 ""가 들어있기 때문에 else에서 "" | undefined라서 string | undefined로 추론하는 것 같네요 🥲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let myValue = Math.random() > 0.5 ? "Aatrox" : undefined;

if (myValue) {
  // myValue: string
  myValue.toUpperCase();
} else {
  // myValue: string | undefined
  myValue; // Q: 왜 "" | undefined로 추론하지 않을까??
}

// 아래 코드는 제대로 추론함
if (typeof myValue === "string") {
  // myValue: string
  myValue.toUpperCase();
} else {
  // myValue: undefined
  myValue;
}

📮 레퍼런스

  1. « 러닝 타입스크립트 3장 » ( 조시 골드버그 지음, 고승원 옮김, 한빛미디어, 2023 )
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

자바스크립트 완벽 가이드 13장 정리 ( Asynchronous )

러닝 타입스크립트 4장