TypeScript - Learning TypeScript Chp 3. Union & Literal

이소라·2023년 3월 11일
0

TypeScript

목록 보기
17/28

3. Union & Literal

  • TypeScript에서 값을 바탕으로 타입을 추론하는 2 가지 핵심 개념
    • Union
      • 값에 허용된 타입을 두 개 이상의 가능한 타입으로 확장하는 것
    • narrowing
      • 값에 허용된 타입을 특정한 타입으로 좁히는 것



3.1 Union Type

  • union type
    • 2 개 이상의 타입으로 구성된 타입을 말합니다.
    • 가능한 값 또는 구성 요소 사이에 | 연산자를 사용하여 나타냅니다.
    • 가능한 타입 중 하나의 값을 할당할 수 있습니다.
function printId(id: number | string) {
  console.log("Your ID is:", id);
}

// Ok, 'number' is assignable to parameter of type 'string | number'
printId(101);
// Ok, 'string' is assignable to parameter of type 'string | number'
printId("202");
// Error: Argument of type '{ myID: number}' is not assignable to parameter of type 'string | number'
printId({ myID: 1234});

3.1.1 Declaring Union Type

  • union 타입 선언은 type annotation으로 정의하는 모든 곳에서 사용할 수 있습니다.
    • 변수에 서로 다른 타입의 값들이 할당될 때, union 타입을 사용합니다.
    • 변수에 초기값과 다른 타입의 값이 할당될 수 있을 때, union 타입을 사용합니다.
    • union 타입 선언의 순서는 중요하지 않습니다.
      • 예) boolean | number = number | boolean
let thinker : string | null = null;

if (Math.random() > 0.5) {
  // Ok, 'string' is assignable to type 'string | null'
  thinker = "Susanne Langer";
}

3.1.2 Union Properties

  • 값이 union 타입일 때, TypeScript는 union으로 선언한 모든 가능한 타입에 존재하는 멤버 속성에만 접근할 수 있습니다.
let physicist = Math.random() > 0.5 ? "Marie Curie" : 85;

// Ok, Property 'toString' exist on type 'string | number'
physicist.toString();
// Error: Poperty 'toUpperCase' does not exist on type 'string | number'
// Property 'toUpperCase' does not exist on type 'number' 
physicist.toUpperCase();
// Error: Poperty 'toFixed' does not exist on type 'string | number'
// Property 'toFixed' does not exist on type 'string' 
physicist.toFixed();
  • union 타입으로 정의된 여러 타입 중 하나의 타입으로 된 값의 속성을 사용하기 위해서, 코드에서 union 타입을 narrow해서 TypeScript가 값의 구체적인 타입을 추론할 수 있게 합니다.



3.2 Narrowing

  • narrowing
    • 값이 정의, 선언 혹은 이전에 유추된 것보다 더 구체적인 타입임을 코드에서 유추하는 것
  • type guard
    • 타입을 좁히는 데 사용할 수 있는 논리적 검사

3.2.1 Assignment Narrowing

  • 변수에 값을 직접 할당하면, TypeScript는 변수의 타입을 할당된 값의 타입으로 좁힙니다.
    • union 타입으로 선언된 변수에 union 타입으로 선언된 타입 중 하나의 값을 할당할 때, 변수 타입을 할당된 값의 타입으로 좁힙니다.
let admiral: number | string;
// admiral : string
admiral = 'Grace Hooper';
// Error: Property 'toFixed' does not exist on type 'string'
admiral.toFixed();
  • TypeScript는 선언된 타입에 대해서 할당 가능성(assignability)을 확인하므로, 초기값을 할당하여 변수의 타입이 좁혀졌더라도 union 타입으로 선언한 모든 타입의 값들을 할당할 수 있습니다.
// x : string | number
let x = Math.random() < 0.5 ? 10 : 'hello world';

x = 1;
// x : number
x.toFixed();
x = 'goodbye!';
// x : string
x.toUpperCase();

3.2.2 typeof type guards

typeof operator

  • JavaScript는 typeof 연산자를 사용하여 런타임에서 갖는 값의 타입에 대한 정보를 얻을 수 있습니다.
  • typeof 연산자의 반환값
    • "string"
    • "number"
    • "bigint"
    • "boolean"
    • "symbol"
    • "undefined"
    • "object"
    • "function"
  • TypeScript에서 type guardtypeof 연산자의 반환값을 확인하는 것입니다.
let researcher = Math.random() > 0.5 ? 'Rosalind Franklin' : 51;

if (typeof researcher === 'string') {
  // researcher : string
  researcher.toUpperCase();
}
  • type guard!를 사용한 논리적 부정과 else 문에서도 잘 작동합니다.
if (!(typeof researcher === 'string')) {
  // researcher : number
  researcher.toFixed();
} else {
  // researcher : string
  researcher.toUpperCase();
}
  • type guard는 삼항 연산자를 사용하여 작성할 수 있습니다.
typeof researcher === 'string' 
  ? researcher.toUpperCase() // researcher : string 
  : researcher.toFixed(); // researcher : number
  • typeof null
    • typeof null === "object"이기 때문에, typeof 연산자로 nullobject를 구분하지 못합니다.
    • TypeScript에서는 타입 검사기가 해당 값의 타입을 유추하여 값이 null이 될 수 있다고 알려줍니다.
function printAll(strs: string | string[] | null) {
  if (typeof str === 'object') {
    // strs : string[] | null
    // Error: 'strs' is possibly 'null'
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === 'string') {
    // strs : string
    console.log(strs);
  } else {
    // ...
  }
}

3.2.3 Truthiness narrowing

truthy & falsy

  • truthy
    • JavaScript에서 boolean 문맥에서 true로 간주되는 값
    • falsy로 정의된 값을 제외한 모든 값
  • falsy
    • JavaScript에서 boolean 문맥에서 false로 간주되는 값
    • 0, NaN, "", 0n, null, undefined
  • TypeScript는 Boolean 문맥(&&, ||, if 조건문)을 사용하여 잠재적인 값 중 truthy로 확인된 일부에 한해서만 변수의 타입을 줄일 수 있습니다.
    • 이는 null이나 undefined와 같은 값을 거르는데 자주 사용됩니다.
let geneticist = Math.random() > 0.5 ? "Barbara McClintock" : undefined;

if (geneticist) {
  // geneticist : string
  geneticist.toUpperCase();
}
// geneticist : string | undefined
// Error: 'geneticist' is possibly 'undefined'
geneticist.toUpperCase();
  • truthy narrowing은 참 여부 확인 외에 다른 기능을 제공하지 않습니다.
    • string | false의 값에 대해 알고 있는 것이 falsy라면, 그것이 빈 문자열인지 false인지 알 수 없습니다.
let biologist = Math.random() > 0.5 && 'Rachel Carson';

if (biologist) {
  // biologist : string
  biologist;
} else {
  // biologist : false | string
  biologist;
}
  • !를 사용하여 falsy 값을 필터링할 수 있습니다.
function multiplyAll(
  value: number[] | undefined,
  factor: number 
): number[] | undefined {
  if (!values) {
    // values : undefined
    return values;
  } else {
    // values : number[]
    return values.map((x) => x * factor);
  }
}

3.2.4 Equality narrowing

equality operator

  • strict equality operator (===, !==)
    • 같은 타입의 두 피연산자가 값이 일치하는지 비교합니다.
    • 타입 변환을 시도하지 않습니다.
  • equality operator (==, !=)
    • 두 피연산자의 타입의 다를 경우, 타입 변환하여 타입을 같게 만든 후 값이 일치하는지 비교합니다.
  • TypeScript는 switch 문이나 ===, !==, ==, !=와 같은 equality checks를 통해 타입을 좁힙니다.
function example(x: string | number, y: string | boolean) {
  // the type of x has to be equal to the type of y
  // common type between x and y is 'string'
  if (x === y) {
    // x : string
    x.toUpperCase();
    // y : string
    y.toUpperCase();
  } else {
    // x : string | number
    console.log(x);
    // y : string | boolean
    console.log(y);
  }
}
  • 특정한 값과 같은지 확인하여 타입을 좁힐 수 있습니다.
function printAll(strs: string | string[] | null) {
  if (strs !== null) {
    // strs : string | string[]
    if (typeof strs === 'object') {
      // strs : string[]
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === 'string') {
      // strs : string
      console.log(strs);
    }
  }
}
  • null == undefined이기 때문에, ==!=를 이용해 값이 null이나 undefined인지 동시에 확인할 수 있습니다.
interface Container {
  value: number | null | undefined;
}

function multiplyValue(container: Container, factor: number) {
  // Remove both 'null' and 'undefined' from the type
  if (container.value != null) {
    // container.value : number
    console.log(container.value);
  }
  
  container.value *= factor;
}

3.2.5 The in operator narrowing

in operator

  • JavaScript는 in 연산자를 사용하여 객체에 해당 이름의 속성이 존재하는지 확인할 수 있습니다.
  • TypeScript는 in 연산자를 사용하여 잠재적인 타입들을 속성을 가진 타입으로 좁힐 수 있습니다.
    • 예) "value" in union type
      • true 브랜치는 union 타입 중 value 속성을 가진 타입으로 좁혀지고, false 브랜치는 union 타입 중 value 속성이 없는 타입으로 좁혀집니다.
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    // animal : Fish
    return animal.swim();
  }
  // animal : Bird
  return animal.fly();
}
  • in 연산자들을 사용하여 속성을 가진 타입으로 좁힐 때, 선택적 속성(optional property?)를 가진 타입도 포함시킵니다.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void; fly?: () => void }

function move(animal: Fish | Bird | Human) {
  if ("swim" in animal) {
    // animal : Fish | Human
    return animal.swim();
  }
  // animal : Bird | Human
  return animal.fly();
}

3.2.6 instanceof narrowing

instanceof operator

  • JavaScript에서 A instanceof B는 A가 B의 prototype chain에 존재하는지 확인합니다.
  • TypeScript는 instanceof 연산자를 사용하여 타입을 좁힐 수 있습니다.
function logValue(x: Date | string) {
  if (x instanceof Date) {
    // x : Date
    console.log(x.toUTCString());
  } else {
    // x : string
    console.log(x.toUpperCase());
  }
}



3.3 Literal

3.3.1 Literal Type & Primitive Type

  • literal type

    • 특정 원시값(primitive value)으로 알려진 타입
  • literal 타입 선언

    • type annotation을 이용해 변수에 특정한 primitive 값을 타입으로 선언합니다.
    • 변수를 const로 선언하고 직접 literal 값을 할당합니다.
// philosopher : 'Hypatia'
let philosopher : 'Hypatia'
// admiral : 'Grace Hooper'
const admiral = 'Grace Hooper';
  • primitive type

    • 해당 타입의 가능한 모든 literal 값의 집합
  • boolean, null, undefined 타입 외의 primitive 타입(string, number 등)은 무한한 수의 literal 타입이 있습니다.

    • boolean : true | false
    • null : null
    • undefined : undefined
    • number : 0 | 1 | 2 | ... | 0.1 | 0.2 | ...
    • string : '' | 'a' | 'b' | 'c' | ... | 'aa' | 'ab' | 'ac'

3.3.2 Literal Assignability

  • 서로 다른 literal 타입은 서로 할당할 수 없습니다.
let specificallyAda: 'Ada';
// specificallyAda : 'Ada'
specificallyAda = 'Ada';
// Error: type 'Bryon' is not assignable to type 'Ada'
specificallyAda = 'Bryon';
  • literal 타입에 primitive 타입을 할당할 수 없습니다.
let specificallyAda: 'Ada' =  'Ada';
let str: string;
// Error: Type 'string' is not assignable to type '"Ada"'
specificallyAda = str;
  • primitive 타입에 literal 타입을 할당할 수 있습니다.
let specificallyAda: 'Ada' =  'Ada';
let str: string;
// Ok
str = specificallyAda;

3.3.3 Union Type with Literal type

  • literal값들로 구성된 union 타입을 사용할 수 있습니다.
// union type with string literal types
function alignText(alignment: 'left' | 'right' | 'center') {
  // ...
}
// Ok
alignText('left');
// Error: Argument of type 'down' is not assignable to parameter of type 'left' | 'right' | 'center'
alignText('down');
// union type with number literal types
function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b : 1 : -1;
}
  • literal값과 literal이 아닌 값으로 구성된 union타입을 사용할 수 있습니다.
let lifespan: number | 'ongoing' | 'uncertain';

// lifespan : number
lifespan = 89;
// lifespan : 'ongoing'
lifespan = 'ongoing'
// Error: Type 'true' is not assignable to tyep 'number' | 'ongoing' | 'uncertain'
lifespan = true;
interface Options {
  width: number;
}

function configure(x: Options | 'auto') {
  // ...
}

configure({width: 100});
configure('auto');
// Error: Argument of type 'automatic' is not assignable to parameter of type Options | 'auto'
configure('automatic');



3.4 Strict Null Checking

십억 달러의 실수(The Billion-Dollar Mistake)

  • 다른 타입이 필요한 위치에서 null 값을 사용하도록 허용하는 타입 시스템
    • 1965년, null 참조의 발명으로 수많은 오류, 취약성 및 시스템 충돌이 발생했으며, 지난 40년간 10억 달러의 피해를 입었을 것입니다(Tonny Hoare, 2009).
  • TypeScript는 '십억 달러의 실수(The Billion-Dollar Mistake'를 바로잡기 위해 Strict null checking를 사용합니다.
  • TypeScript는 StrictNullChecks 옵션의 활성화 여부에 따라nullundefined가 다르게 처리합니다.
  • StrictNullChecks가 false일 때, nullundefined가 효과적으로 무시됩니다.
    • null이나 undefined일 수 있는 값에 정상적으로 접근 가능합니다.
    • 특정 타입을 갖고 있는 변수에 null이나 undefined을 할당할 수 있습니다.
      • 이것은 런타임에서 예상하지 못한 오류를 발생시킵니다.
// strictNullChecks : false
declare const loggedInUsername: string;

const users = [
  { name: 'Oby', age; 12 },
  { name: 'Heera', age; 32 },
]

const loggedInUser = users.find((u) => u.name === loggedInUsername);
console.log(loggedInUser.age);

let str: string;
str = null;
  • StrictNullChecks가 true일 때, nullundefined는 그 자신의 구별된 타입을 가지고 확인됩니다.
    • null이나 undefined일 수 있는 값은 접근하기 전에 테스트를 해야합니다.
      • narrwoing하여 null이나 undefined일 수 있는 값을 확인합니다.
    • 특정 타입을 갖고 있는 변수에 null이나 undefined을 할당할 수 없습니다.
// strictNullChecks : true
declare const loggedInUsername: string;

const users = [
  { name: 'Oby', age; 12 },
  { name: 'Heera', age; 32 },
]

const loggedInUser = users.find((u) => u.name === loggedInUsername);
// Error: loggedInUser is possibly 'undefined'
console.log(loggedInUser.age);

let str: string;
// Error: Type 'null' is not assignable to type 'string'
str = null;



3.5 Type Alias

  • Type Alias
    • TypeScript가 재사용하는 타입에 할당하는 더 쉬운 이름을 말합니다.
    • type 새로운 이름 = 타입 형태를 가집니다.
    • 파스칼 케이스(PascalCase)로 이름을 지정합니다.
// before
let rawDataFirst : boolean | number | string | null | undefined ;
let rawDataSecond : boolean | number | string | null | undefined ;
let rawDataThird : boolean | number | string | null | undefined ;


// after
type RawData = boolean | number | string | null | undefined ;

let rawDataFirst: RawData;
let rawDataSecond: RawData;
let rawDataThird: RawData;
  • TypeScript가 type alias를 발견하면, 해당 type alias가 참조하는 실제 타입을 입력한 것처럼 작동합니다.

3.5.1 Type Aliases are Not JavaScript

  • type alias는 TypeScript의 Type System에만 존재합니다.
// data.ts
type RawData = boolean | number | string | null | undefined ;

let rawDataFirst: RawData;
let rawDataSecond: RawData;
let rawDataThird: RawData;

// data.js(complied)
let rawDataFirst;
let rawDataSecond;
let rawDataThird;
  • type alias는 Type System에만 존재하므로 런타임 코드에서 참조할 수 없습니다.
    • type alias는 개발 시에서만 존재합니다.
type SomeType = string | undefined;
// Error: 'SomeType' only refer to a type, but is being used as a value here.
console.log(SomeType);

3.5.2 Combining Type Aliases

  • type alias는 다른 type alias를 참조할 수 있습니다.
type Id = number | string;
// IdMabe : number | string | undefined | null
type IdMaybe = Id | undefined | null;
  • 사용 순서대로 type alias를 선언할 필요는 없습니다.
    • 파일 내에서 type alias를 먼저 선언하고, 참조할 type alias를 나중에 선언해도 됩니다.
// Ok
type IdMaybe = Id | undefined | null;
type Id = number | string;



참고

0개의 댓글