YeonjuOHYE / javascript

0 stars 0 forks source link

노노 #25

Closed YeonjuOHYE closed 2 years ago

YeonjuOHYE commented 3 years ago

링크 : https://www.typescriptlang.org/docs/handbook/advanced-types.html

교차 타입 (Intersection Types)

교차 타입은 여러 타입을 하나로 결합 기존 타입을 합쳐 필요한 모든 기능을 가진 하나의 타입을 얻을 수 있다.

function extend<First, Second>(first: First, second: Second): First & Second {
    const result: Partial<First & Second> = {};
    for (const prop in first) {
        if (first.hasOwnProperty(prop)) {
            (result as First)[prop] = first[prop];
        }
    }
    for (const prop in second) {
        if (second.hasOwnProperty(prop)) {
            (result as Second)[prop] = second[prop];
        }
    }
    return result as First & Second;
}

class Person {
    constructor(public name: string) { }
}

interface Loggable {
    log(name: string): void;
}

class ConsoleLogger implements Loggable {
    log(name) {
        console.log(`Hello, I'm ${name}.`);
    }
}

const jim = extend(new Person('Jim'), ConsoleLogger.prototype);
jim.log(jim.name);

유니언 타입

유니언 타입은 값이 여러 타입 중 하나임을 설명 세로 막대 (|)로 각 타입을 구분하여 number | string | boolean은 값의 타입은 number, string 혹은 boolean이 될 수 있음을 나타낸다.

/**
 * 문자열을 받고 왼쪽에 "padding"을 추가합니다.
 * 만약 'padding'이 문자열이라면, 'padding'은 왼쪽에 더해질 것입니다.
 * 만약 'padding'이 숫자라면, 그 숫자만큼의 공백이 왼쪽에 더해질 것입니다.
 */
function padLeft(value: string, padding: string | number) {
    // ...
}

let indentedString = padLeft("Hello world", true); // 컴파일 중에 오류

유니언 타입을 값으로 가지고 있으면, 유니언에 있는 모든 타입에 공통인 멤버에만 접근할 수 있다.

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // 성공
pet.swim();    // 오류
property 'swim' does not exist on type 'Bird | Fish'.
  Property 'swim' does not exist on type 'Bird'.(2

타입 가드와 차별 타입

유니언 타입의 모든 구성 성분을 가지고 있다고 보장되는 멤버에만 접근하려면

let pet = getSmallPet();

if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}

사용자-정의 타입 가드 (User-Defined Type Guards)

TypeScript에는 타입 가드라는 것이 있다. 타입 가드는 스코프 안에서의 타입을 보장하는 런타임 검사를 수행한다는 표현식

타입 서술어 사용하기

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

pet is Fish는 이 예제에서의 타입 서술어 서술어는 parameterName is Type 형태이고, parameterName는 반드시 현재 함수 시그니처의 매개변수 이름이어야 한다.

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

function isFish(pet: Fish | Bird): boolean {
    return (pet as Fish).swim !== undefined;
}
Screen Shot 2021-02-17 at 6 48 03 PM

in 연산자 사용하기 (Using the in operator)

in 연산자는 타입을 좁히는 표현으로 작용. n in x 표현에서, n은 문자열 리터럴 혹은 문자열 리터럴 타입이고 x는 유니언 타입

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

typeof 타입 가드 (typeof type guards)

function isNumber(x: any): x is number {
    return typeof x === "number";
}

function isString(x: any): x is string {
    return typeof x === "string";
}

function padLeft(value: string, padding: string | number) {
    if (isNumber(padding)) {
        return Array(padding + 1).join(" ") + value;
    }
    if (isString(padding)) {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

TypeScript는 typeof를 타입 가드로 인식하기 때문에 typeof x === "number"를 함수로 추상할 필요가 없다. 즉 타입 검사를 인라인으로 작성할 수 있다

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

typeof 타입 가드의 두 가지 다른 형식

※ 여기서 typename은 "number", "string", "boolean" 그리고 "symbol" TypeScript에서 위에 없는 다른 문자열과 비교하는 것을 막지는 않지만, 타입 가드의 표현식으로 인지되지 않는다.

instanceof 타입 가드 (instanceof type guards)

interface Padder {
    getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}

class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder(4) :
        new StringPadder("  ");
}

// 타입은 'SpaceRepeatingPadder | StringPadder' 입니다
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder; // 타입은 'SpaceRepeatingPadder'으로 좁혀집니다
}
if (padder instanceof StringPadder) {
    padder; // 타입은 'StringPadder'으로 좁혀집니다
}

instanceof의 오른쪽은 생성자 함수여야 하며 다음의 과정으로 좁혀가며 타입가드 수행

널러블 타입 (Nullable types)

null과 undefined --strictNullChecks 플래그 선언하고 변수를 선언할 때, 자동으로 null이나 undefined를 포함하지 않는다. 대신에 유니언 타입을 사용하여 명시적으로 포함해야 한다.

TypeScript는 JavaScript와 맞추기 위해 null과 undefined를 다르게 처리

선택적 매개변수와 프로퍼티

--strictNullChecks를 적용하면, 선택적 매개변수가 "| undefined"를 자동으로 추가

function f(x: number, y?: number) {
    return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // 오류, 'null'은 'number | undefined'에 할당할 수 없습니다

선택적 프로퍼티도 마찬가지

class C {
    a: number;
    b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // 오류, 'undefined'는 'number'에 할당할 수 없습니다
c.b = 13;
c.b = undefined; // 성공
c.b = null; // 오류, 'null'은 'number | undefined'에 할당할 수 없습니다.

타입 가드와 타입 단언 (Type guards and type assertions)

널러블 타입이 유니언으로 구현되기 때문에, null을 제거하기 위해 타입 가드를 사용할 필요가 있다

function f(sn: string | null): string {
    if (sn == null) {
        return "default";
    }
    else {
        return sn;
    }
}
function f(stringOrNull: string | null): string {
  return stringOrNull ?? "default";
}

컴파일러가 null이나 undefined를 제거할 수 없는 경우, 타입 단언 연산자를 사용하여 수동으로 제거할 수 있다 방법 :구문은 !를 후위 표기. identifier!는 null과 undefined를 identifier의 타입에서 제거

function broken(name: string | null): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // 오류, 'name'은 아마도 null 입니다
  }
  name = name || "Bob";
  return postfix("great");
}

function fixed(name: string | null): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // 성공
  }
  name = name || "Bob";
  return postfix("great");
}

예제는 중첩 함수를 사용. 왜냐하면 컴파일러가 중첩 함수안에서는 null을 제거할 수 없기 때 (즉시-호출된 함수 표현은 예외). 특히 외부 함수에서 호출될 경우, 중첩 함수에 대한 모든 호출을 추적할 수 없기 때문

타입 별칭 (Type Aliases)

별칭은 실제로 새로운 타입을 만드는 것이 아니라-그 타입을 나타내는 새로운 이름 을 만드는 것. 원시 값의 별칭을 짓는 것은 문서화의 형태로 사용할 수 있지만, 별로 유용하지 않는다.

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === "string") {
        return n;
    }
    else {
        return n();
    }
}

인터페이스처럼, 타입 별칭은 제네릭이 될 수 있다 - 타입 매개변수를 추가하고 별칭 선언의 오른쪽에 사용

type Container<T> = { value: T };
type Tree<T> = {
    value: T;
    left: Tree<T>;
    right: Tree<T>;
}

교차 타입

type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;

※ 타입 별칭을 선언의 오른쪽 이외에 사용하는 것은 불가

type Yikes = Array<Yikes>; /

인터페이스 vs. 타입 별칭 (Interfaces vs. Type Aliases)

대부분 동일한 기능을 제공하지만 한 가지 큰 차이점은 타입은 new properties 추가하기 위한 재할당이 불가능하지만 인터페이스는 항상 확장가능한다.

type Window = {
  title: string
}

type Window = {
  ts: import("typescript")
}

// Error: Duplicate identifier 'Window'.
interface Window {
  title: string
}

interface Window {
  ts: import("typescript")
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});

소프트웨어의 이상적인 특징은 확장에 개방되어 있기 때문에, 가능하면 항상 타입 별칭보다 인터페이스를 사용해여 한다. 반면에, 만약 인터페이스로 어떤 형태를 표현할 수 없고 유니언이나 튜플 타입을 사용해야 한다면, 일반적으로 타입 별칭을 사용해야 한다.

문자열 리터럴 타입 (String Literal Types)

문자열 리터럴 타입은 문자열에 값을 정확하게 지정할 수 있게 해준다. 예제에서 문자열 리터럴 타입은 유니언 타입, 타입 가드, 그리고 타입 별칭과 잘 결합된다.

type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if (easing === "ease-in") {
            // ...
        }
        else if (easing === "ease-out") {
        }
        else if (easing === "ease-in-out") {
        }
        else {
            // 오류! null이나 undefined를 전달하면 안됩니다
        }
    }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // 오류: "uneasy"는 여기서 허용하지 않습니다

타입은 오버로드를 구별하기 위해 같은 방법으로 사용할

function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... 더 많은 오버로드 ...
function createElement(tagName: string): Element {
    // ... 이곳에 코드를 ...
}

숫자 리터럴 타입 (Numeric Literal Types)

명시적으로 작성되는 경우는 거의 없지만, 이슈를 좁히고 버그를 잡는데 유용할 수 있다.

function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
    // ...
}

열거형 멤버 타입 (Enum Member Types)

열거형 멤버는 모든 멤버가 리터럴로-초기화될 때 타입을 가집니다. 싱글톤 타입을 이야기 할때 여기서는 열거형 멤버 타입과 숫자/문자열 리터럴 타입을 얘기하지만 대부분 사람들은 싱글톤타입을 리터럴과 동의어(interchagable)처럼 사용한다.