TypeScript

ming·2023년 2월 19일

STUDY SOJU

목록 보기
18/19

타입스크립트란?

  • 자바스크립트에 타입을 부여한 언어
  • 자바스크립트와 달리 브라우저에서 실행하려면 컴파일이 필요하다.


타입스크립트의 장점

  • 에러 사전 방지
// math.js
function sum(a, b) {
  return a + b;
}
// math.ts
function sum(a: number, b: number) {
  return a + b;
}

: 다음과 같이 타입을 지정해 에러를 사전에 방지할 수 있다.

  • 코드 가이드 및 자동 완성 (개발 생산성 향상)
    : VSCode에서 타입에 대한 API를 미리 보기로 띄워줄 수 있음
    API를 다 일일이 치는 것이 아니라 tab으로 빠르고 정확하게 작성 가능



기본 타입

  • 기본 타입의 종류
    Boolean, Number, String, Object, Array, Tuple, Enum, Any, Void, Null, Undefined, Never

  • Tuple
    배열의 길이가 고정되고 각 요소의 타입이 지정되어 있는 배열 형식
let arr: [string, number] = ['hi', 10];
  • Enum
    C, Java와 같은 다른 언어에서 흔하게 쓰이는 타입, 특정 값(상수)들의 집합
enum Avengers { Capt, IronMan, Thor }
let hero: Avengers = Avengers.Capt;
  • Any
    단어 의미 그대로 모든 타입에 대해서 허용
    let str: any = 'hi';
    let num: any = 10;
    let arr: any = ['a', 2, true];
  • Void
    변수에는 undefinednull만 할당하고, 함수에는 반환 값을 설정할 수 없는 타입
let unuseful: void = undefined;
function notuse(): void {
  console.log('sth');
}
  • Never
    함수의 끝에 절대 도달하지 않는다는 의미를 지닌 타입
// 이 함수는 절대 함수의 끝까지 실행되지 않는다는 의미
function neverEnd(): never {
  while (true) {
  }
}


함수


함수의 기본적인 타입 선언

function sum(a: number, b: number): number {
  return a + b;
}

기존 자바스크립트 함수의 선언 방식에서 매개변수함수의 반환 값에 타입을 추가
++ 함수의 반환 값에 타입을 정하지 않을 때는 void라도 사용

함수의 인자

  • 타입스크립트에서는 함수의 인자를 모두 필수 값으로 간주한다
  • 정의된 매개변수 값만 받을 수 있고 추가로 인자를 받을 수 없다
function sum(a: number, b: number): number {
  return a + b;
}
sum(10, 20); // 30
sum(10, 20, 30); // error, too many parameters
sum(10); // error, too few parameters

this

  • 자바스크립트의 this가 잘못 사용되었을 때 감지 가능


인터페이스

  • 상호 간에 정의한 약속 혹은 규칙을 의미
  • 보통 아래와 같은 범주에 대해 약속을 정의할 수 있다
    • 객체의 스펙(속성과 속성의 타입)
    • 함수의 파라미터
    • 함수의 스펙(파라미터, 반환 타입 등)
    • 배열과 객체를 접근하는 방식
    • 클래스

인터페이스 맛보기

let person = { name: 'Capt', age: 28 };
function logAge(obj: { age: number }) {
  console.log(obj.age); // 28
}
logAge(person); // 28

logAge() 함수에서 받는 인자의 형태는 age를 속성으로 갖는 객체
여기에 인터페이스를 적용할 경우 다음과 같다

interface personAge {
  age: number;
}
function logAge(obj: personAge) {
  console.log(obj.age);
}
let person = { name: 'Capt', age: 28 };
logAge(person);

logAge()의 인자는 personAge 라는 타입을 가져야한다
인터페이스에 정의된 속성, 타입의 조건만 만족한다면 객체의 속성 갯수가 더 많아도 상관 없다
인터페이스에 선언된 속성 순서를 지키지 않아도 된다

옵션 속성

interface 인터페이스_이름 {
  속성?: 타입;
}

이처럼 속성의 끝에 ?를 붙인 예시를 작성하면 아래와 같다

interface CraftBeer {
  name: string;
  hop?: number;  
}
let myBeer = {
  name: 'Saporo'
};
function brewBeer(beer: CraftBeer) {
  console.log(beer.name); // Saporo
}
brewBeer(myBeer);

brewBeer() 함수에서 Beer 인터페이스를 인자의 타입으로 선언했음에도 불구하고, 인자로 넘긴 객체에는 hop 속성이 없다. hop옵션 속성으로 선언했기 때문

옵션 속성의 장점

  • 인터페이스를 사용할 때 속성을 선택적으로 적용 가능
  • 인터페이스에 정의되어 있지 않은 속성에 대해서 인지시켜줄 수 있음

읽기 전용 속성

  • 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성
interface CraftBeer {
  readonly brand: string;
}

읽기 전용 배열

  • 배열을 선언할 때 ReadonlyArray<T> 타입을 사용하면 읽기 전용 배열 생성 가능
let arr: ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error
arr.push(4); // error
arr[0] = 100; // error

함수 타입

  • 함수의 타입을 정의할 때에도 사용
    interface login {
    (username: string, password: string): boolean;
    }
    함수의 인자의 타입과 반환 값의 타입을 정함
    let loginUser: login;
    loginUser = function(id: string, pw: string) {
    console.log('로그인 했습니다');
    return true;
    }

클래스 타입

  • C#이나 자바처럼 타입스크립트에서도 클래스가 일정 조건을 만족하도록 타입 규칙을 정할 수 있다
    interface CraftBeer {
    beerName: string;
    nameBeer(beer: string): void;
    }
    class myBeer implements CraftBeer {
    beerName: string = 'Baby Guinness';
    nameBeer(b: string) {
      this.beerName = b;
    }
    constructor() {}
    }

인터페이스 확장

  • 클래스와 마찬가지로 인터페이스도 인터페이스 간 확장이 가능
    interface Person {
    name: string;
    }
    interface Developer extends Person {
    skill: string;
    }
    let fe = {} as Developer;
    fe.name = 'josh';
    fe.skill = 'TypeScript';
    혹은 아래와 같이 여러 인터페이스를 상속받아 사용 가능함
    interface Person {
    name: string;
    }
    interface Drinker {
    drink: string;
    }
    interface Developer extends Person {
    skill: string;
    }
    let fe = {} as Developer;
    fe.name = 'josh';
    fe.skill = 'TypeScript';
    fe.drink = 'Beer';

이넘(Enums)

  • 특정 값들의 집합을 의미하는 자료형

숫자형 이넘

  enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}

위와 같이 숫자형 이넘을 선언할 때 초기 값을 주면 초기 값부터 차례로 1씩 증가

Up - 1
Down - 2
Left - 3
Right - 4

만약 아래와 같이 초기 값을 주지 않으면 0부터 차례로 1씩 증가

enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}

문자형 이넘

  • 숫자형 이넘과 흡사하지만 런타임에서의 미세한 차이가 있음
  • 이넘 값 전부 다 특정 문자 또는 다른 이넘 값으로 초기화 해줘야 한다
    enum Direction {
      Up = "UP",
      Down = "DOWN",
      Left = "LEFT",
      Right = "RIGHT",
    }

복합 이넘 (Heterogeneous Enums)

  • 이넘에 문자와 숫자를 혼합하여 생성할 수는 있지만 권장하지 않음
    enum BooleanLikeHeterogeneousEnum {
      No = 0,
      Yes = "YES",
    }

연산자를 이용한 타입 정의

Union Type

  • 자바스크립트의 OR 연산자(||)와 같이 A이거나 B이다 라는 의미의 타입
    function logText(text: string | number) {
    // ...
    }
    | 연산자를 이용하여 타입을 여러 개 연결하는 방식

Intersection Type

  • 여러 타입을 모두 만족하는 하나의 타입
    interface Person {
    name: string;
    age: number;
    }
    interface Developer {
    name: string;
    skill: number;
    }
    type Capt = Person & Developer;
    Capt는 아래와 같이 정의됨
    {
    name: string;
    age: number;
    skill: string;
    }

Class

readonly

  • 클래스의 속성에 readonly 키워드를 사용하면 아래와 같이 접근만 가능
    class Developer {
      readonly name: string;
      constructor(theName: string) {
          this.name = theName;
      }
    }
    let john = new Developer("John");
    john.name = "John"; // error! name is readonly.
    readonly를 사용하면 constructor() 함수에 초기 값 설정 로직을 넣어줘야 하므로 다음과 같이 인자에 readonly 키워드를 추가해서 코드를 줄일 수 있다
    class Developer {
    readonly name: string;
    constructor(readonly name: string) {
    }
    }

Abstract Class

  • 인터페이스와 비슷한 역할을 하면서도 조금 다르다
  • 추상 클래스는 특정 클래스의 상속 대상이 되는 클래스이며 좀 더 상위 레벨에서 속성, 메서드의 모양을 정의한다
  abstract class Developer {
  abstract coding(): void; // 'abstract'가 붙으면 상속 받은 클래스에서 무조건 구현해야 함
  drink(): void {
    console.log('drink sth');
  }
}
class FrontEndDeveloper extends Developer {
  coding(): void {
    // Developer 클래스를 상속 받은 클래스에서 무조건 정의해야 하는 메서드
    console.log('develop web');
  }
  design(): void {
    console.log('design web');
  }
}
const dev = new Developer(); // error: cannot create an instance of an abstract class
const josh = new FrontEndDeveloper();
josh.coding(); // develop web
josh.drink(); // drink sth
josh.design(); // design web

제네릭

제네릭(Generics)의 사전적 정의

  • 제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징
  • 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용

제네릭의 한 줄 정의와 예시

  • 제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것
    function getText<T>(text: T): T {
    return text;
    }
    위 함수는 제네릭 기본 문법이 적용된 형태. 함수를 호출할 때 아래와 같이 함수 안에서 사용할 타입을 넘겨줄 수 있다.
    getText<string>('hi');
    getText<number>(10);
    getText<boolean>(true);
    위 코드 중 getText<string>('hi')를 호출 했을 때 함수에서 제네릭이 아래와 같이 동작
    function getText<string>(text: T): T {
    return text;
    }
    제네릭 타입이 <string>이 되는 이유는 getText() 함수를 호출할 때 제네릭(함수에서 사용할 타입) 값으로 string을 넘겼기 때문
     getText<string>();
    함수의 인자로 hi 라는 값을 아래와 같이 넘기게 되면
    getText<string>('hi');
    getText 함수는 아래와 같이 타입을 정의한 것과 같다
    function getText<string>(text: string): string {
    return text;
    }
    위 함수는 입력 값의 타입이 string이면서 반환 값 타입도 string이어야 한다

제네릭을 사용하는 이유

  • 함수의 인자로 어떤 타입이 들어갔고 어떤 값이 반환되는지는 알 수 있게 하기 위함이다

타입 추론(Type Inference)

  • 타입스크립트가 코드를 해석해 나가는 동작

타입 추론의 기본

  let x = 3;

위와 같이 x에 대한 타입을 따로 지정하지 않더라도 일단 xnumber로 간주된다. 이렇게 변수를 선언하거나 초기화 할 때 타입이 추론된다. (++ 변수, 속성, 인자의 기본 값, 함수의 반환 값 등을 설정할 때)

가장 적절한 타입(Best Common Type)

  • 몇 개의 표현식(코드)을 바탕으로 타입을 추론하는데, 그 표현식을 이용하여 추론된 가장 근접한 타입Best Common Type이라고 한다.

문맥상의 타이핑(Contextual Typing)

  • 코드의 위치(문맥)를 기준으로 타입을 추론하는 방식

예시 코드

  window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button);   //<- OK
  console.log(mouseEvent.kangaroo); //<- Error!
};

위 코드를 타입스크립트 검사기 관점에서 보면 window.onmousedown에 할당되는 함수의 타입을 추론하기 위해 window.onmousedown 타입을 검사한다. 타입 검사가 끝나고 나면 함수의 타입이 마우스 이벤트와 연관이 있다고 추론하기 때문에 mouseEvent 인자에 button 속성은 있지만 kangaroo 속성은 없다고 결론을 내린다.

타입스크립트의 타입 체킹

  • 타입 체크는 값의 형태에 기반하여 이루어져야 한다. 이걸 Duck Typing 또는 Structural Subtyping 이라고 한다.
    • Duck Typing : 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 의미. 동적 타이핑의 한 종류
    • Structural Subtyping : 객체의 실제 구조나 정의에 따라 타입을 결정하는 것을 의미

타입 호환(Type Compatibility)이란?

  • 타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미
     interface Ironman {
     name: string;
    }
    class Avengers {
     name: string;
    }
    let i: Ironman;
    i = new Avengers(); // OK, because of structural typing
    C#이나 Java였다면 위 코드에서 에러가 날 것이다. Avengers 클래스가 명시적으로 Ironman 인터페이스를 상속받아 구현하지 않았기 때문이다. 위와 같은 코드가 타입스크립트에서 정상적으로 동작하는 이유는 자바스크립트의 작동 방식과 관련이 있다. 기본적으로 자바스크립트는 객체 리터럴이나 익명 함수 등을 사용하기 때문에 명시적으로 타입을 지정하는 것보다는 코드의 구조 관점에서 타입을 지정하는 것이 더 잘 어울린다.

Soundness란?

  • 타입스크립트는 컴파일 시점에 타입을 추론할 수 없는 특정 타입에 대해서 일단 안전하다고 보는 특성이 있다. 이걸 "들리지 않는다(it is said to not be sound)"라고 표현한다.

타입 별칭 (Type Aliases)

  • 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미
    // string 타입을 사용할 때
    const name: string = 'capt';
    // 타입 별칭을 사용할 때
    type MyName = string;
    const name: MyName = 'capt';
    위와 같이 string, number와 같은 간단한 타입 뿐만 아니라 interface 레벨의 복잡한 타입에도 별칭을 부여할 수 있다.
    type Developer = {
    name: string;
    skill: string;
    }
    제네릭도 사용 가능하다.
    type User<T> = {
    name: T
    }

타입 별칭의 특징

  • 타입 별칭은 새로운 타입 값을 하나 생성하는 것이 아니라 정의한 타입에 대해 나중에 쉽게 참고할 수 있게 이름을 부여하는 것

type vs interface

  • 타입 별칭과 인터페이스의 가장 큰 차이점은 타입의 확장 가능 / 불가능 여부
    • 인터페이스는 확장이 가능한데 반해 타입 별칭은 확장이 불가능
    • 따라서, 가능한한 type 보다는 interface로 선언해서 사용하는 것을 추천

d.ts 파일

타입스크립트 선언 파일

  • 타입스크립트 선언 파일 d.ts는 타입스크립트 코드의 타입 추론을 돕는 파일. 전역 변수로 선언한 변수를 특정 파일에서 import 구문 없이 사용하는 경우 해당 변수를 인식하지 못한다. 그럴 때 아래와 같이 해당 변수를 선언해서 에러가 나지 않게 할 수 있다.
    declare const global = 'sth';

전역 변수와 전역 함수에 대한 타입 선언

  • 타입스크립트 파일에서 사용할 순 있지만 선언되어 있지 않은 전역 변수나 전역 함수는 아래와 같이 타입을 선언할 수 있다.
    // 전역 변수
    declare const pi = 3.14;
    // 전역 함수
    declare namespace myLib {
    function greet(person: string): string;
    let name: string;
    }
    myLib.greet('캡틴');
    myLib.name = '타노스';

유틸리티 타입

유틸리티 타입이란?

  • 이미 정의해 놓은 타입을 변환할 때 사용하기 좋은 타입 문법
  • 유틸리티 타입을 쓰면 훨씬 더 간결한 문법으로 타입을 정의할 수 있다.

자주 사용되는 유틸리티 타입

Partial

  • 파셜(Partial) 타입은 특정 타입의 부분 집합을 만족하는 타입을 정의
    interface Address {
    email: string;
    address: string;
    }
    type MayHaveEmail = Partial<Address>;
    const me: MayHaveEmail = {}; // 가능
    const you: MayHaveEmail = { email: 'test@abc.com' }; // 가능
    const all: MayHaveEmail = { email: 'capt@hero.com', address: 'Pangyo' }; // 가능

Pick

  • 픽(Pick) 타입은 특정 타입에서 몇 개의 속성을 선택(pick)하여 타입을 정의
    interface Hero {
    name: string;
    skill: string;
    }
    const human: Pick<Hero, 'name'> = {
    name: '스킬이 없는 사람',
    };
    type HasThen<T> = Pick<Promise<T>, 'then' | 'catch'>;
    let hasThen: HasThen<number> = Promise.resolve(4);
    hasThen.th // 위에서 'then'만 선택하면 'then'만 제공, 'catch' 선택하면 'catch만 제공'

Omit

  • 오밋(Omit) 타입은 특정 타입에서 지정된 속성만 제거한 타입을 정의
    interface AddressBook {
    name: string;
    phone: number;
    address: string;
    company: string;
    }
    const phoneBook: Omit<AddressBook, 'address'> = {
    name: '재택근무',
    phone: 12342223333,
    company: '내 방'
    }
    const chingtao: Omit<AddressBook, 'address'|'company'> = {
    name: '중국집',
    phone: 44455557777
    }

맵드 타입

맵드 타입(Mapped Type)이란?

  • 기존에 정의되어 있는 타입을 새로운 타입으로 변환해 주는 문법
  • 자바스크립트 map() API 함수를 타입에 적용한 것과 같은 효과

맵드 타입의 기본 문법

  • 자바스크립트의 map 함수를 타입에 적용했다고 보면 된다.
    { [ P in K ] : T }
    { [ P in K ] ? : T }
    { readonly [ P in K ] : T }
    { readonly [ P in K ] ? : T }

0개의 댓글