03.TS 활용

Updated:


TypeScript 활용

1.작성자와 사용자의 관점으로 코드 보기

타입 시스템

  • 컴파일러에게 사용하는 타입을 명시적으로 지정하는 시스템

  • 컴파일러가 자동으로 타입을 추론하는 시스템

타입스크립트의 타입 시스템

  • 타입을 명시적으로 지정할 수 있습니다

  • 타입을 명시적으로 지정하지 않으면, 타입스크립트 컴파일러가 자동으로 타입을 추론

KakaoTalk_20210508_094902955

🔷 타입이란 해당 변수가 할 수 있는 일을 결정합니다

// JavaScript

//f1 이라는 함수의 body에서는 a를 사용할 것입니다.
// a가 할 수 있는 일은 a 의 타입이 결정합니다.

function f1(a) {
  return a;
}

🔷 함수 사용법에 대한 오해를 야기하는 자바스크립트

// JavaScript

// (f2 실행의 결과가 NaN 을 의도한 것이 아니라면)
// 이 함수의 작성자는 매개변수 a 가 number 타입이라는 가정으로 함수를 작성했습니다.

function f2(a) {
  return 3 * 38;
}

// 사용자는 사용법을 숙지하지 않은 채, 문자열을 사용하여 함수를 실행했습니다.

console.log(f2(10)); // 380
console.log(f2("mark")); // NaN

🔷 타입스크립트의 추론에 의지하는 경우

// 타입스크립트 코드지만,
// a 의 타입을 명시적으로 지정하지 않은 경우이기 때문에 a 는 any 로 추론됩니다.
// 함수의 리턴 타입은 number 로 추론됩니다. (NaN 도 number 의 하나입니다)

function f3(a) {
  return a * 38;
}

// 사용자는 a 가 any 이기 때문에, 사용법에 맞게 문자열을 사용하여 함수를 실행했습니다.

console.log(f3(10)); // 380
console.log(f3("Mark") + 5); // NaN

📌 위의 코드 들과 같이 type 을 명시 하지 않고 사용하면 error 가 발생되기 때문에 noImplicitAny 를 사용함!

  • 타입을 명시적으로 지정하지 않은 경우, 타입스크립트가 추론 중 any 라고 판단하게 되면, 컴파일 에러를 발생시켜 명시적으로 지정하도록 유도합니다. (더 실수를 줄일 수 있게 됨)

🔷 noImplicitAny 에 의한 방어

// error TS7066: parameter 'a' implicitly has an 'any' type.

function f3(a) {
  return a * 38;
}

// 사용자의 코드를 실행할 수 없습니다. 컴파일이 정상적으로 마무리 될 수 있도록 수정해야 합니다.

console.log(f3(10)); // 380
console.log(f3("Mark") + 5); // NaN

🔷 number 타입으로 추론된 리턴 타입

// 매개변수의 타입은 명시적으로 지정했습니다.
// 명시적으로 지정하지 않은 함수의 리턴 타입은 number 로 추론됩니다.

function f4(a: number) {
  if (a > 0) {
    return a * 38;
  }
}

// 사용자는 사용법에 맞게 숫자형을 사용하여 함수를 실핼했습니다.
// 해당 함수의 리턴 타입은 number 이기 때문에, 타입에 따르면 이어진 연산을 바로 할 수 있습니다.
// 하지만 실제 undefined + 5 가 실행되어 NaN 이 출력됩니다.

console.log(f4(5)); // 190
console.log(f4(-5) + 5); // NaN
  • 위의 같은경우, null, undefined 이 number type 이 아닌데도 f4 가 number 로 추론되는 경우가 발생합니다. (이것을 방지 하기 위해 strictNullChecks 옵션을 사용)

📌 strictNullChecks 옵션을 켜면 : 모든 타입에 자동으로 포함되어 있는 nullundefined 를 제거해 줍니다.

🔷 number | undefined 타입으로 추론된 리턴 타입 (strictNullchecks 를 킨상태)

// 매개변수의 타입은 명시적으로 지정했습니다.
// 명시적으로 지정하지 않은 함수의 리턴 타입은 number | undefined 로 추론됩니다.

function f4(a: number) {
  if (a > 0) {
    return a * 38;
  }
}

// 사용자는 사용법에 맞게 숫자형을 사용하여 함수를 실핼했습니다.
// 해당 함수의 리턴 타입은 number | undefined 이기 때문에, 타입에 따르면 이어진 연산을 바로 할 수 없습니다.
// 컴파일 에러를 고쳐야하기 때문에 사용자와 작성자가의 의도가 같아야 합니다.

console.log(f4(5)); // 190
console.log(f4(-5) + 5); // error TS2532: Object is possibly 'undefined'.

🔷 그러면 명시적으로 리턴 타입을 지정해야 할까요?

// 매개변수의 타입과 함수의 리턴 타입을 명시적으로 지정했습니다.
// 실제 함수 구현부의 리턴 타입과 명시적으로 지정한 타입이 일치하지 않아 컨파일 에러가 발생합니다.

// error TS2366: Function lacks ending return statement and return type does not inc:

function f5(a: number): number {
  if (a > 0) {
    return a * 38;
  }
}
  • 위의 에러를 방지하려면 noImplicitReturns 를 사용합니다.

    • noIplicitReturns 옵션을 켜면 : 함수내에서 모든 코드가 값을 리턴하지 않으면, 컴파일 에러를 발생시킨다

🔷 모든 코드에서 리턴을 직접해야 합니다. (return 을 강제적으로 하게 되는것이 noIplicitReturns )

// if 가 아닌 경우 return 을 직접 하지 않고 코드가 종료된다.

// error TS7030: Not all code paths return a value

function f5(a: number): number {
  if (a > 0) {
    return a * 38;
  }
}

📌 매개변수에 object 가 들어오는 경우

// JavaScript

function f6(a) {
  return `이름은 ${a.name} 이고, 연령대는 ${
    Math.floor(a.age / 10) * 10
  }대 입니다`;
}

console.log(f6({ name: "Jacob", age: 88 })); // 이름은 Jacob 이고, 연령대는 80대 입니다
console.log(f6("Jacob")); // 이름은 undefined 이고, 연령대는 NaN대 입니다.
  • 그래서 Type 을 명시 해주면…
function f7(a: { name: string; age: number }): string {
  return `이름은 ${a.name} 이고, 연령대는 ${
    Math.floor(a.age / 10) * 10
  }대 입니다`;
}

console.log(f7({ name: "Jacob", age: 88 })); // 이름은 Jacob 이고, 연령대는 80대 입니다
console.log(f7("Jacob")); // error TS2345: Argument of type 'string' is not assignable to parameter of type '{ name: string; age: number; }'
  • 매번 type 을 길게 쳐야되기 때문에 나만의 타입을 만들어서 사용해야 한다
interface PersonInterface {
  name: string;
  age: number;
}

type PersonTypeAlias = {
  name: string;
  age: number;
};

function f8(a: PersonInterface): string {
  return `이름은 ${a.name} 이고, 연령대는 ${
    Math.floor(a.age / 10) * 10
  }대 입니다`;
}

console.log(f8({ name: "Jacob", age: 88 })); // 이름은 Jacob 이고, 연령대는 80대 입니다
console.log(f8("Jacob")); // error TS2345: Argument of type 'string' is not assignable to parameter of type '{ name: string; age: number; }'

2.Structual Type System vs Nominal Type System

Structural type sysyem

  • 구조가 같으면, 같은 타입이다.

  • TypeScript 가 해당되는 언어

// 명시적으로 이 타입이 뭐라고 안해도 구조가 같으면 같은 타입으로 취급한다

interface IPerson {
  name: string;
  age: number;
  speak(): string;
}

type PersonType = {
  name: string;
  age: number;
  speak(): string;
};

let personInterface: IPerson = {} as any;
let personType: PersonType = {} as any;

personInterface = personType;
personType = personInterface;

nominal type sysyem

  • 구조가 같아도 이름이 다르면, 다른 타입입니다.

  • 대표적으로 C 나 Java 에서 따르는 type system

// 극단적으로 TS 를 nominal type system으로 만든 경우

type PersonID = stirng & { readonly brand: unique symbol };

function PersonID(id: string): PersonID {
  return id as PersonID;
}

function getPersonByID(id: personID) {}

getPersonByID(PersonID("id-aaaaaa"));
getPersonByID("id-aaaaaa"); // error TS2345: Argument of type 'string' is not assignable to parameter of type 'PersonID'. Type 'sting' is not assignable to type '{ readonly brand: unique symbol; }'

duck typing

  • 만약 어떤 새가 오리처럼 걷고, 헤엄치고, 오리처럼 소리를 낸다면 앞으로 그 새를 오리라고 부를 것이다 라는 것

  • Python 에서 사용되는 typing 임

  • 주의! TS 는 duck typign 이 아니다!! (비슷하긴 하지만 원칙적으로 다른것임)

class Duck:
    def sound(self):
        print u"꽥꽥"

class Dog:
    def sound(self):
        print u"멍멍"


def get_sound(animal):
    animal.sound()


def main():
    bird = Duck()
    dog = Dog()
    get_sound(bird)
    get_sound(dog)

3.타입 호환성(Type Compatibility)

서브 타입 (1)

// sub1 타입은 sup1 타입의 서브 타입이다.
// sup1 이 범위가 더 크기 때문에 error 발생
let sub1: 1 = 1;
let sup1: number = sub1;
sub1 = sup1; // error! Type 'number' is not assignable to type '1'.

//  sub2 타입은 sup2 타입의 서브 타입니다.
let sub2: number[] = [1];
let sub2: object = sub2;
sub2 = sup2; // error! Type '{}' is missing the following properties from type 'number[]': length, pop, push, concat, and 16 more.

// sub3 타입은 sup3 타입의 서브 타입이다.
let sub3: [number, number] = [1, 2];
let sup3: number[] = sub3;
sub3 = sup3; // error! Type 'number[]' is not assignable to type '[number, number]'. Target requires 2 element(s) but source may have fewer.

// sub4 타입은 sup4 타입의 서브 타입이다
// any는 다 들어가기 때문에 true
let sub4: number = 1;
let sup4: any = sub4;
sub4 = sup4;

// sub5 타입은 sup5  타입의 서브 타입이다.
let sub5: never = 0 as never;
let sup5: number = sub5;
sub5 = sup5; // error! Type 'number' is not assignable to type 'never'.

class Animal {}
class Dog extends Animal {
  eat() {}
}

// sub6 타입은 sup6 타입의 서브 타입이다.
let sub6: Dog = new Dog();
let sup6: Animal = sub6;
sub6 = sup6; // error! Property 'eat' is missing in type 'SubAnimal' but required in type 'SubDog'.

🔷 1. 같거나 서브 타입인 경우, 할당이 가능합니다 => 공변

// primitive type
let sub7: string = "";
let sup7: string | number = sub7;

// object - 각각의 프로퍼티가 대응하는 프로퍼티와 같거나 서브타입이어야 한다.
let sub8: { a: string; b: number } = { a: "", b: 1 };
let sup8: { a: string | number; b: number } = sub8;

// array -object 와 마찬가지

let sub9: Array<{ a: string; b: number }> = [{ a: "", b: 1 }];
let sup9: Array<{ a: string | number; b: number }> = sub8;

🔷 2. 함수의 매개변수 타입만 같거나 슈퍼타입인 경우, 할당이 가능하다. => 반병

class Person {}
class Developer extends Person {
  coding() {}
}
class StratupDeveloper extends Developer {
  burning() {}
}

// tellme 는 함수 인데 Developer를 인자로 받고 다시 Developer return 함
function tellme(f: (d: Developer) => Developer) {}

// Developer => Developer 에다가 Developer => Developer 를 할당하는 경우
// 같은거 이것만 반병에 해당됨
tellme(function dToD(d: Developer): Developer {
  return new Developer();
});

// Developer => Developer 에다가 Person => Developer 를 할당하는 경우
tellme(function pToD(d: Person): Developer {
  return new Developer();
});

// Developer => Developer 에다가 StartipDeveloper => Developer 를 할당하는 경우
tellme(function sToD(d: StratupDeveloper): Developer {
  return new Developer();
});

// strictFunctionTypes 옵션을 켜면 : 함수를 할당할 시에 함수의 매개변수 타입이 같거나 슈퍼타입인 경우가 아닌 경우, 에러를 통해 경고한다.

4.타입 별칭 (Type Alias)

  • interface 랑 비슷해 보입니다

  • Primitive, Union Type, Tuple, Function

  • 기타 직접 작성해야하는 타입을 다른 이름을 지정할 수 있습니다.

  • 만들어진 타입의 refer 로 사용하는것이지 타입을 만드는것은 아닙니다

Aliasing Primitive

type MyStringType = string;

const str = "world";

let myStr: MyStringType = "hello";
myStr = str;

// 별다르 의미는 없지만, 프로젝트에서 유효 ID 를 따른 형태로 쓰고 싶을때 가끔 쓰입니다.

Aliasing Union Type

// 많이 사용되는 Type

let person: string | number = 0;
person = "Jacob";

type StringOrNumber = string | number;

let another: StringOrNumber = 0;
another = "Anna";

// 1.유니온 타입은 A 도 가능하고 B 도 가능한 타입
// 2. 길게 쓰는걸 짧게

Aliasing Tuple

let person: [string, number] = ["Mark", 35];

type PersonTuple = [string, number];

let another: PersonTuple = ["Anna", 24];

// 튜플 타입에 별칭을 줘서 여러군데서 사용할 수 있게 합니다.

Aliasing Function

type EatType = (food: string) => void;

📌 interface 와 alias 구분 사용법

  • interface: 어떤 타입이 타입으로써의 목적이나, 존재가치가 명확할 때 주로 사용

  • alias: 어떤 대상을 그냥 가릴킬 때나, 별명으로써만 존재를 하고 싶을 때 주로 사용

Reference

Leave a comment