TypeScript 3장 - 러닝 타입스크립트(2)
포스트
취소

TypeScript 3장 - 러닝 타입스크립트(2)

TypeScript

image

Part.1

chapter.3

  • 유니언 : 값에 허용된 타입을 두 개 이상의 가능한 타입으로 확장하는 것
  • 내로잉 : 값에 허용된 타입이 하나 이상의 가능한 타입이 되지 않도록 좁히는 것

3.1 유니언 타입

1
let mathmatician = Math.random() > 0.5 ? undefined : "Mark Goldberg";
  • mathmaticianundefined이거나 string일 수 있다.
  • 이처럼 값이 정확히 어떤 타입인지는 모르지만 두 개 이상의 옵션 중 하나라는 것을 알고 있을 때, 이를 유니언이라고 한다.
  • 이렇게 작성된 mathmaticianstring|undefined로 표현된다.
  • 변수 위에 마우스를 가져가면 타입을 표시한다.
3.1.1 유니언 타입 선언
  • 변수의 초깃값이 있더라도 변수에 대한 명시적 타입 애너테이션을 제공하는 것이 유용할 때 유니언 타입을 사용한다.
  • 초깃값은 null이지만 잠재적으로 string 될 수 있음을 알린다.

    1
    2
    3
    4
    5
    
    let thinker: string | null = null;
    
    if (Math.randon() > 0.5) {
      thinker = "Susanne"; // OK
    }
    
  • 유니언 타입 선언의 순서는 중요하지 않으며 boolean|numbernumber|boolean은 같다.
3.1.2 유니언 속성
  • 값이 유니언 타입일 때, 타입스크립트는 유니언으로 선언하여 접근이 가능한 메서드에만 접근할 수 있다.
  • 유니언 외의 타입에 접근하려고 하면 타입 검사 오류가 발생한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    let physicist = Math.random() > 0.5 ? "Marie" : 84;
    
    physicist.toString(); // OK
    physicist.toUpperCase();
    // Error : Property 'toUpperCase' does not exist on type 'string | number '
    // Property  'toUpperCase' does not exist on type 'number'
    physicist.toFiexd();
    // Error : Property 'toFiexd' does not exist on type 'string | number'
    // Property 'toFiexd' does not exist on type 'string'
    
    • 두 타입 모두 사용 가능한 메서드여야 한다.
    • 이러한 여러 개의 타입 중 하나의 타입으로 된 값의 속성을 사용하려면, 값이 보다 구체적인 타입 중 하나라는 것을 타입스크립트에 알려야 한다.
    • 이러한 과정을 내로잉이라고 부른다.

3.2 내로잉

  • 값이 정의, 선언 혹은 이전에 유추된 것보다 더 구체적인 타입임을 코드에서 유추하는 것이다.
  • 타입스크립트가 값의 타입이 이전에 알려진 것보다 더 좁혀졌다는 것을 알게 되면 값을 더 구체적인 타입으로 취급한다.
  • 타입을 좁히는 데 사용하는 논리적 검사를 타입 가드라고 한다.
3.2.1 값 할당을 통한 내로잉
  • 변수에 값을 할당하면 변수의 타입을 할당된 값의 타입으로 좁힌다.

    1
    2
    3
    4
    5
    6
    
    let admiral: number | string;
    admiral = "Grace";
    
    admiral.toUpperCase(); // OK : string
    admiral.toFixed();
    // Error : Property 'toFixed' does not exist on type 'string'
    
    • 변수에 유니언 타입 애너테이션이 명시되고 초깃값이 주어질 때 값 할당 내로잉이 작동한다.
    • 타입스크립트는 변수가 나중에 유니언 타입으로 선언된 타입 중 하나의 값을 받을 수 있지만, 처음에는 초기에 할당된 값의 타입으로 시작한다.
    1
    2
    3
    4
    5
    
    let inventor: number | string = "Hedy";
    
    inventor.toUpperCase(); // OK string
    inventor.toFixed();
    // Error : Property 'toFixed' does not exist on type 'string'
    
3.2.2 조건 검사를 통한 내로잉
  • 일반적으로 타입스크립트에서는 변수가 알려진 값과 같은지 확인하는 if문을 통해 변수의 값을 좁히는 방법을 사용한다.
  • 변수가 알려진 값과 동일한 타입인지 확인한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    let scientist = Math.random() > 0.5 ? "Rosalind" : 51;
    // scientist : number | string
    if (scientist === "Rosalind") {
      scientist.toUpperCase(); // OK
    }
    // scientist: string 타입
    scientist.toUpperCase();
    // Error : Property "toUpperCase" does not exist on type "string|number"
    // Property "toUpperCase" does not exist on type "number"
    
    • 조건부 로직을 내로잉할 때, 만약 변수가 여러 타입 중 하나라면, 일반적으로 필요한 타입과 관련된 검사를 원할 것이다.
    • 타입스크립트는 강제로 코드를 안전하게 작성할 수 있도록 돕는다.
3.2.3 typeof 검사를 통한 내로잉
  • 직접 값을 확인해 타입을 좁히기도 하지만, typeof연산자를 사용할 수도 있다.
  • 타입스크립트의 if문에서 타입이 무엇인지 확인한다.

    1
    2
    3
    4
    5
    
    let researcher = Math.random() > 0.5 ? "Rosalind" : 51;
    
    if (typeof researcher === "string") {
      researcher.toUpperCase(); // OK : string
    }
    
  • !를 사용한 논리적 부정과 else문도 잘 작동한다.
    1
    2
    3
    4
    5
    
    if (!(typeof researcher === "string")) {
      reseacher.toFixed(); // OK : number
    } else {
      researcher.toLowerCase(); // OK : string
    }
    
  • 삼항 연산자를 이용해 다시 작성할 수 있다.
    1
    2
    3
    
    typeof researcher === "string"
      ? researcher.toUpperCase(); // OK : string
      : researcher.toFixed(); // OK : number
    

3.3 리터럴 타입

  • 원시 타입 값 중 어떤 것이 아닌 특정 원싯값으로 알려진 타입이다.
  • 원시 타입 string은 존재할 수 있는 모든 문자열의 집합을 나타내지만, 리터럴 타입인 Hypatia는 하나의 문자열만 나타낸다.
  • 만약 변수를 선언하고 직접 리터럴 값을 할당하면, 타입스크립트는 해당 변수에 할당된 리터럴 값으로 유추한다.
  • 타입 애너테이션에서는 리터럴과 원시 타입을 섞어서 사용할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    let lifespan: number | "ongoing" | "uncertain";
    
    lifespan = 89; // OK
    lifespan = "ongoing"; // OK
    
    lifespan = true;
    // Error : Type 'true' is not assignable to 'number|"ongoing"|"uncertain"'
    lifespan = "hi";
    // Error : Type '"hi"' is not assignable to 'number|"ongoing"|"uncertain"'
    
    1
    2
    3
    4
    
    boolean : true | false
    null  undefined  자기자신만을 리터럴 값으로 가진다.
    number: 0|1|2...|0.1|0.2|...
    string: ""|"a"|"b"|"c"|...|"aa"|"ab"|..
    

3.3 리터럴 할당 가능성

  • 0과 1은 동일한 원시 타입을 갖고 있지만 다른 리터럴 타입이기 때문에 할당할 수 없다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    let special: "Ada";
    special = "Ada"; // OK
    special = "Bda";
    // Error : Type "Bda" is not assignable to type "Ada".
    
    let someString = "";
    someString = special; // OK
    
    let someString2: "";
    someString2 = special;
    // Error : Type "Ada" is not assignable to type '""';
    

3.4 엄격한 null 검사

  • 엄격한 null검사는 모든 데이터 타입에 nullundefined를 할당 받을 시 오류를 출력한다.
  • null타입이 설정된 변수에 undefined를 할당할 수 없고, undefined타입이 설정된 변수에 null을 할당받을 수 없다.
  • tsconfig.json"strictNullChecks": true로 설정되어 엄격한 null검사를 사용하는 것인데, 만약 null,undefined를 항상 서브 타입으로 할당할 수 있게 해주려면 false값으로 변경하면 된다.
  • 혹은 애너테이션 또는 유니온 또한 사용할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    let a: null = null; // OK
    let b: undefined = undefined; // OK
    
    b = null;
    // Error : Type "null" is not assignable to type "undefined".
    
    let d: string = null;
    // Error : Type "null" is not assignable to type "string".
    
    let e: string | null = null; // OK
    
    // "strictNullChecks": false
    let f: string = null; // OK
    
3.4.1 십억 달러의 실수
  • null 값을 사용하도록 허용하는 시스템을 말한다.
  • 다음 코드에서 strictNullChecks는 비활성화한다면 코드의 타입이 안전하다고 간주되지만 이는 잘못된 것이다.

    1
    2
    3
    
    let nameMaybe = Math.random() > 0.5 ? "Tony" : undefined;
    
    nameMaybe.toLowerCase();
    
    • 위 예제는 무작위로 출력되는 숫자가 만약 0.5를 넘는다면, nameMaybeTony라는 값을 갖게 되고, 이 외의 경우에는 undefined를 갖게 된다.
    • undefined에는 toLowerCase 메서드를 사용할 수 없지만, strictNullChecks를 비활성화 했기 때문에 이를 인식하지 못 한다.
    • 이를 활성화 시켜주거나 if문으로 활용해 처리해야 한다.
    1
    2
    3
    4
    5
    6
    7
    8
    
    let nameMaybe = Math.random() > 0.5 ? "Tony" : undefined;
    
    nameMaybe.toLowerCase();
    // Error : Object is possibly 'undefined'.
    
    if (nameMaybe) {
      nameMaybe.toLowerCase(); // OK
    }
    
3.4.2 참 검사를 통한 내로잉
  • 자바스크립트에서 또는 truthy&&연산자 또는 if문처럼 true로 간주된다.
  • false,0,-0,0n,"",null,undefined,NaNfalsy로 정의되어 false로 간주된다.

    • 논리 연산자는 참 여부를 검사하는 일도 수행하지만, 참 여부 확인 외에 다른 기능은 제공하지 않기 때문에, 만약 값이 falsy라면 그것이 빈 문자열인지 undefined인지 알 수 없다.
    • 빈 문자열은 string 타입을 갖고는 있지만 falsy로 정의된다.
      • 따라서 toLowerCase 등의 string 메서드를 사용할 수는 없지만 이것을 인식할 수 없다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    let geneticist = Math.random() > 0.5 ? "Barbara" : undefined;
    
    if (geneticist) {
      geneticist.toLowerCase(); // OK: string
    }
    
    geneticist.toUpperCase();
    // Error : Object is possibly 'undefined'.
    geneticist && geneticist.toUpperCase(); // OK: string|undefined
    geneticist?.toLowerCase(); // OK: string|undefined
    
3.4.3 초깃값이 없는 변수
  • 자바스크립트에서 초깃값이 없는 변수는 기본적으로 undefined를 갖고, 타입스크립트는 값이 할당되기 전에 접근하려고 한다면 오류를 나타낸다.
  • ?. 연산자는 오ㅂ ㅂ셔널 체이닝을 적용하고, 옵셔널 체이닝은 해당 값이 존재하지 않는 경우, undefined일 때도 오류를 발생시키지 않고 undefined를 반환한다.
  • mathematiciannull 같은 값이 들어있을 경우 오류가 발생할 수 있다.
  • undefined 값이 들어가 있는 변수를 사용할 때는 ?.연산자를 사용하여 옵셔널 체이닝을 적용하도록 권장한다.

    1
    2
    3
    4
    5
    6
    
    let mathematician: string;
    mathematician?.length;
    // Error : Variable 'mathematician' is used before being assigned.
    
    mathematician = "Mark";
    mathematician.length; // OK: string
    

3.5 타입 별칭

  • 조금 긴 유니언 타입을 가진 경우, 이를 묶어 쉬운 이름을 할당하는 타입 별칭이 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    
    let raw: boolean | string | number | null | undefined;
    let raw1: boolean | string | number | null | undefined;
    let raw2: boolean | string | number | null | undefined;
    // 타입 별칭
    type RawData = boolean | string | number | null | undefined;
    let raw: RawData;
    let raw1: RawData;
    let raw2: RawData;
    
3.5.1 타입 별칭은 자바스크립트가 아니다.
  • 타입 별칭은 애너테이션처럼 자바스크립트로 컴파일되지 않는다.
    • 타입스크립트 타입 시스템에만 존재한다.
  • 타입 별칭은 타입 시스템에만 존재하기 때문에 런타임 코드에서는 참조할 수 없으며, 이에 접근하려고 하면 타입 오류를 나타낸다.

    1
    2
    3
    4
    
    // 3.5의 코드의 자바스크립트 컴파일
    let raw;
    let raw1;
    let raw2;
    
    1
    2
    3
    4
    
    type SomeType = string | undefined;
    
    cosole.log(SomeType);
    // Error : 'SomeType' only refers to a type, but is being used as a value here.
    
3.5.2 타입 별칭 결합
  • 타입 별칭은 다른 타입 별칭을 참조할 수 있으며, 참조할 타입 별칭을 나중에 선언해도 된다.

    1
    2
    3
    4
    5
    6
    
    type Id = number | string;
    type IdMaybe = Id | undefined | null;
    // Id = number|string|undefined|null
    
    type Id2 = Id3 | number | string | undefined;
    type Id3 = boolean;
    

3.6

  • 유니언 타입으로 두 개 이상의 타입 중 하나를 나타낼 수 있다.
  • 타입 애너테이션으로 유니언 타입을 명시적으로 표현할 수 있다.
  • 타입 내로잉으로 값의 가능한 타입을 좁힐 수 있다.
  • 리터럴 타입을 선언할 때는 const를 사용하여 할당하는 것이 안정적이다.
    • 원시 타입이 아닌 리터럴 타입을 할당할 떄는 구체적인 값을 할당하는데, let 키워드를 사용하면 이 값은 언제든지 재할당 될 수 있기 때문에 타입의 안정성을 잃게 된다.
    • 따라서 리터럴 타입을 사용할 떄, 값을 변경할 필요가 없을 때는 const를 사용하여 타입의 안정성을 높인다.
  • 타입스크립트에서 strictNullChecks를 활성화하여 엄격한 null검사를 할 수 있다.
  • 존재하지 않을 수 있는 값이나 할당되지 않은 변수는 undefined를 갖는다.
  • 반복적으로 사용하고자하는 타입은, 타입 별칭에 저장하여 사용할 수 있다.

chap.4

4.1 객체 타입

  • 객체 리터럴을 생성하면, 값과 동일한 속성명과 원시 타입을 갖게 된다.
  • 값의 속성에 접근하려면 value.key , value[key]로 접근을 해야한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    const poet = {
      born: 1935,
      name: "Mary",
    };
    
    poet.born; // number
    poet[name]; // string
    poet.end;
    // Error: Property 'end' does not exist on type '{born: number, name: string}'.
    
4.1.1 객체 타입 선언
1
2
3
4
5
6
7
8
9
10
11
12
let poet: {
  born: number;
  name: string;
};

poet = {
  born: 1954,
  name: "Mary",
};

poet = "sara";
// Error: Type 'string' is not assignable to type '{born: number, name: string}'.
4.1.2 별칭 객체 타입
  • 마찬가지로 별칭 타입을 사용할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    type Poet = {
      born: number;
      name: string;
    };
    
    let poet: Poet; // OK
    
    poet = {
      born: 1935,
      name: "Mary",
    };
    
    poet = "sara";
    // Error: Type 'string' is not assignable to type '{born: number, name:string}'.
    

4.2 구조적 타이핑

  • 타입스크립트의 타입 시스템은 구조적으로 타입화되어 있다.
  • 타입을 충족하는 모든 값을 해당 타입의 값으로 사용할 수 있으며, 매개변수나 변수가 특정 객체 타입으로 선언되면 타입스크립트에 어떤 객체를 사용하든 해당 속성이 있어야 한다.
  • 구조적 타이핑은 덕 타이핑과는 다르며, 타입스크립트의 타입 검사기에서 구조적 타이핑은 정적 시스템이 타입을 검사하는 경우이다.
  • 덕 타이핑은 런타임에서 사용될 때까지 객체 타입을 검사하지 않는 것을 말한다.
  • 자바스크립트는 덕 타입인 반면 타입스크립트는 구조적으로 타입화된다.
  • 덕 타이핑이란 동적 타이핑의 한 종류로 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 말한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    type WithFirstName = {
      firstName: string;
    };
    
    type WithLastName = {
      lastName: string;
    };
    
    const hasBoth = {
      firstName: "Mary",
      lastName: "chang",
    };
    
    let withFirstName: WithFirstName = hasBoth;
    // OK: 'hasBoth'는 'string'타입의 'firstName'을 포함한다.
    let withLastName: WithLastName = hasBoth;
    // OK: 'hasBoth'는 'string'타입의 'lastName'을 포함한다.
    
4.2.1 사용 검사
  • 애너테이션을 사용할 때 애너테이션된 위치에 해당 객체 타입을 할당할 수 있는지 확인한다.
  • 할당하는 값에는 객체 타입의 필수 속성이 있어야 한다.
  • 객체 타입에 필요한 객체가 없다면 타입 오류가 발생한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    type firstNames = {
      first: string,
      last: string;
    };
    
    const examle: firstNames = {
      first: 'Sara',
      last: 'Nio';
    }; // OK
    
    const examleple: firstNames = {
      first: 'Choi',
    };
    // Error: Property 'last' is missing in type '{first: string}'
    // but required in type 'FirstNames'.
    
    type Time = {
      start: Date;
    };
    
    const today: Time = {
      start: "1989-02-03",
      // Error: Type 'string' is not assignable to type 'Date'.
    }
    

초과 속성 검사

  • 객체 타입으로 선언되고, 초깃값에 객체 타입에서 정의된 것보다 많은 필드가 있다면 타입스크립트에서 오류가 발생한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    type Poet = {
      born: number;
      name: string;
    }; // OK
    
    const poetMatch: Poet = {
      born: 1925,
      name: "Min",
    }; // OK
    
    const poetUnmatch: Poet = {
      born: 1832,
      name: "Gone",
      activity: "Cied",
      // Error: Type '{activity: string, born: number, name: string}' is not assign to type 'Poet'.
      // Object literal may only specify known properties,
      // and 'activity' does not exist in type 'Poet'.
    };
    
    const poetUnmatch: Poet = poetMatch; // Ok
    // 초깃값이 Poet와 일치하기 때문에 타입 오류가 발생하지 않는다.
    

4.2.3 중첩된 객ㅊ 타입

  • 객체는 다른 객체의 멤버로 중첩될 수 있고 {...} 객체 타입을 사용한다.
  • 속성의 형태를 객체 타입으로 추출하는 방법도 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    
    type Poem = {
      author: {
        first: string;
        last: string;
      };
      name: string;
    }; // OK
    
    const poemMatch: Poem = {
      author: {
        first: "Sara",
        last: "Man",
      },
      name: "Poe",
    }; // OK
    
    const poemUnmatch: Poem = {
      author: {
        name: "Sara",
      },
      // Error: Type '{name: string}' is not assignable
      // to type '{first: string, last: string}'.
      // Object literal may only specify known properties, and 'name'
      // does not exist in type '{first: string, last: string}'.
    };
    
    type Author = {
      first: string;
      last: string;
    };
    
    type man = {
      author: Author;
      name: string;
    };
    
    const child: man = {
      author: {
        name: "Kin",
        // Error: type '{name: string}' is not assignable to type 'Author'.
        // Object literal may only specify known properties
        // and 'name' does not exist in type 'Author'.
      },
    };
    

4.2.4 선택적 속성

  • 모든 객체에 객체 타입 속성이 필요한 것은 아니며, 타입의 속성 애너테이션 : 앞ㅇ ?를 추가하면 선택적 속성임을 나타낼 수 있다.
  • 선택적 속성과 undefined를 포함한 유니언 타입 속성 사이에는 차이가 있으며, ?을 사용해 선택적으로 선언된 속성은 존재하지 않아도 된다.
  • 필수로 선언된 속성과 |undefined는 그 값이 undefined 일지라고 반드시 존재해야 한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    type Book = {
      author?: string;
      pages: number;
    }; // OK
    
    const ok: Book = {
      author: "Rina",
      pages: 80,
    }; // OK
    
    const notOk: Book = {
      author: "Jin",
      // Error: Property 'pages' is missing in type
      // '{pages: number}' but required in type 'Book'.
    };
    
    type Writer = {
      author: string | undefined;
      editor?: string;
    };
    
    const man = {
      author: undefined,
    }; // OK
    
    const child = {};
    // Error: Property 'author' is missing in type '{}' but required in type 'Writer'.
    

4.3 객체 타입 유니언

4.3.1 유추된 객체 타입 유니언

  • 변수에 여러 객체 타입 중 하나가 될 수 있는 초깃값이 주어지면 해당 타입을 객ㅊ 타입 유니언으로 유추한다.
  • 유니언 타입은 객체 타입을 구성하고 있는 요소를 모두 가질 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    const poem =
      Math.random() > 0.5
        ? { name: "Choi", pages: 7 }
        : { name: "Kim", rthymes: true };
    // name 속성을 항상 가지며,
    // pages와 rthymes가 있을 수도 없을 수도 있다.
    
    poem.name; // string
    poem.pages; // number|undefined
    poem.rthymes; // booleans|undefined
    

4.3.2 명시된 객체 타입 유니언

  • 객체 타입의 조합을 명시하면 객체 타입을 더 명확히 정의할 수 있으며, 타입을 더 많이 제어할 수 있다는 이점이 있다.
  • 값의 타입이 객체 타입으로 구성된 유니언이라면 모든 유니언 타입에 존재하는 속성에 대한 접근만 허용한다.
  • 필수 속성을 항상 갖는 유니언 타입을 작성하면, 항상 갖는 속성에 접근하는 것은 허용되지만, 존재할 수도, 하지 않을 수도 있는 속성에 접근하는 것은 오류가 발생한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    type Poem = {
      name: string;
      pages: number;
    };
    
    type Person = {
      name: string;
      age: number;
    }
    
    type Book = Poem|Person;
    
    type ebook : Book = Math.random() > 0.5
      ? {nmae: "Choi", pages: 5}
      : {name: "Kim", age: 25};
    
    ebook.name; //OK
    ebook.age;
    // Error: Property 'age' does not exist on type 'Book'.
    // Property 'age' does not exist on type 'Poem'.
    
    ebook.pages;
    // Error: Property 'pages' does not exist on type 'Book'.
    // Property 'pages' does not exist on type 'Poem'.
    

4.3.3 객체 타입 내로잉

  • 타입 검사기가 유니언 타입 값에 특정 속성이 포함된 경우에만 코드 영역을 실행할 수 있음을 알게 되면, 값의 타입을 해당 속성을 포함하는 구성 요소로만 좁힌다.

    1
    2
    3
    4
    5
    
    if ("pages" in Book) {
      poem.pages; // OK: poem은 Poem으로 좁혀진다.
    } else {
      poem.rhythms; // OK: poem은 Person으로 좁혀진다.
    }
    
  • 타입스크립트는 if와 같은 형식으로 참 여부를 확인하는 것은 허용하지 않으며, 존재하지 않는 객체의 속성에 접근하려고 시도하면 타입 가드처럼 작동하는 방식으로 사용되더라도 오류로 간주된다.

    1
    2
    3
    
    if(poem.pages){...}
    // Error: Property 'pages' does not exist on type 'Poem|Person'.
    // Property 'pages' does not exist on type 'Person'.
    

4.3.4 판별된 유니언

  • 유니언 타입으로 된 객체는, 객체의 속성이 객체의 형태로 나타내도록 한다.
  • 이러한 타입 형태를 판별된 유니언이라고 부르며, 객체의 타입을 가리키는 속성이 판별값이다.
  • 타입스크립트는 코드에서 판별 속성을 사용해 타입 내로잉을 수행한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    type Pages = {
      name: string;
      pages: number;
      type: "pages";
    };
    
    type Rhymes = {
      name: string;
      rhymes: boolean;
      type: "rhymes";
    };
    
    type Poem = Pages | Rhymes;
    
    const poem: Poem =
      Math.random() > 0.5
        ? { name: "Choi", pages: 7, type: "pages" }
        : { name: "Kim", rhymes: true, type: "rhymes" };
    
    if (poem.type === "pages") {
      console.log(`It's got pages: ${poem.pages}`); // OK
    } else {
      console.log(`It rhymes: ${poem.rhymes}`);
    }
    
    poem.type; // type: "pages" | "rhymes"
    poem.pages;
    // Error: Property 'pages' does not exist on type 'Poem'.
    // Property 'pages' does not exist on type 'Rhymes'.
    

4.4 교차 타입

  • 유니언 타입은 둘 이상의 다른 타입 중 하나의 타입이 될 수 있음을 나타낸다.
  • 타입스크립트에서도 & 교차 타입을 사용해 여러 타입을 동시에 나타낸다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    type Artwork = {
      genre: string;
      name: string;
    };
    
    type Writing = {
      pages: number;
      name: string;
    };
    
    type WrittenArt = Artwork & Writing;
    // {genre:string; name:string; pages:number;} 와 같다.
    
  • 교차 타입은 유니언 타입과 결합할 수 있으며, 하나의 타입으로 판별된 유니언 타입을 설명하는데 매우 유용하다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    type ShortPoem = { author: string } & (
      | { kigo: string; type: "choi" }
      | { meter: number; type: "vill" }
    );
    
    const moringGlory: ShortPoem = {
      author: "Fukuda",
      kigo: "Morning Glory",
      type: "choi",
    }; // OK
    
    const oneArt: ShortPoem = {
      author: "Elizabeth",
      type: "vill",
      // Error: Type '{author: string; type: "vill"}'
      // is not assignable to type 'ShortPoem'
      // Type '{author: string; type: "vill"}' is not assignable to
      // type '{author:string;}' & {metter: number; type: "vill"}'
      // Property 'meter' is missing in type '{author: string; type:"vill"}'
      // but required in type '{meter:number; type:"vill";}'.
    };
    

    4.4.1 교차 타입의 위험성

    • 긴 할당 가능성 오류

      • 유니언 타입과 결합하는 것처럼 복잡한 교차 타입을 만들게 되면 할당 가능성 오류 메세지를 읽기 어려워진다.
      • 타입을 일련의 별칭으로 된 객체 타입으로 분할하면 읽기 쉬워진다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
      type ShortPoemBase = { author: string };
      type Haiku = ShortPoemBase & { kigo: string; type: "haiku" };
      type Vill = ShortPoemBase & { meter: number; type: "vill" };
      type ShortPoem = Haiku | Vill;
      
      const oneArt: ShortPoem = {
        author: "Elizabeth",
        type: "vill",
        // Error: Type '{author: string; type: "vill";}'
        // is not assignable to type 'ShortPoem'.
        // Type '{author: string; type: "vill";}'
        // is not assignable to type 'Vill'.
        // Property 'meter' is missing in type '{author: string; type: "vill";}'.
        // but required in type '{meter: number; type: "vill";}'.
      };
      
    • never

      • 교차 타입은 잘못 사용하기 쉽고 불가능한 타입을 생성한다.
      • 원시 타입의 값은 동시에 여러 타입이 될 수 없기 때문에 교차 타입의 구성요소로 함께 결합할 수 없다.
      • 두 개의 원시 타입을 함께 시도하면 never 키워드로 표시되는 never타입의 된다.
      1
      2
      3
      4
      5
      6
      7
      
      type NotPossible = number & string; // 타입: never
      
      let notNumber: NotPossible = 0;
      // Error: Type 'number' is not possible to type 'never'.
      
      let notString: never = "";
      // Error: Type 'string' is not possible to type 'never'.
      

    4.5

    • 타입스크립트가 객체 타입 리터럴을 해석하는 방법
    • 중첩과 선택적 속성을 포함한 객체 리터럴 타입 소개
    • 객체 리터럴 타입의 유니언 타입 선언, 추론 및 타입 내로잉
    • 판별된 유니언 타입과 판별값
    • 교차 타입으로 객체 타입을 결합하는 방법
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.