TypeScript_Handbook

Juyeon.it·2022년 6월 11일
0

타입스크립트란?

자바스크립트에 타입을 부여한 언어

  • 자바스크립트와 달리 브라우저에서 실행하려면 컴파일 과정을 거쳐야함

장점(드디어 각종 에러에서 벗어날 수 있는 것인가)

  • 에러의 사전 방지
  • 코드 자동 완성과 가이드

Fundamentals

기본 타입

String

let str: string = 'hi';

Number

let num: number = 10;

Boolean

let isLoggedIn: boolean = false;

Object
Array

let arr1: number[] = [1,2,3];
let arr2: Array<number> = [1,2,3];

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: 변수에는 undefined, null만 할당하고, 함수에는 반환 값을 설정할 수 없는 타입

let unuseful: void = undefined;
function notuse(): void {
  console.log('sth');
}

Null
Undefined
Never: 함수의 끝에 절대 도달하지 않는다는 의미를 지닌 타입

// 이 함수는 절대 함수의 끝까지 실행되지 않는다는 의미
function neverEnd(): never {
  while (true) {

  }
}

함수

자바스크립트 함수 선언 방식에서 매개변수와 함수의 반환 값에 타입을 추가

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

optional argument 설정

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

변수 디폴트 값 설정

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

Rest 문법 적용

function sum(a: number, ...nums: number[]): number {
  const totalOfNums = 0;
  for (let key in nums) {
    totalOfNums += nums[key];
  }
  return a + totalOfNums;
}

인터페이스

상호 간에 정의한 규칙을 말함

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 {
  readonly brand: string;
}

읽기 전용 배열

let arr: ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error
arr.push(4); // error
arr[0] = 100; // error

인터페이스에서 정의하지 않은 속성들을 추가하고 싶을 때

interface CraftBeer {
  brand?: string;
  [propName: string]: any;
}

인터페이스에서 함수의 타입 정의하기

interface login {
  (username: string, password: string): boolean;
}

let loginUser: login;
loginUser = function(id: string, pw: string) {
  console.log('로그인 했습니다');
  return true;
}

인터페이스 확장

interface Person {
  name: string;
}
interface Developer extends Person {
  skill: string;
}
let fe = {} as Developer;
fe.name = 'josh';
fe.skill = 'TypeScript';

하이브리드 타입: 여러 가지 타입 조합

interface CraftBeer {
  (beer: string): string;
  brand: string;
  brew(): void;
}

function myBeer(): CraftBeer {
  let my = (function(beer: string) {}) as CraftBeer;
  my.brand = 'Beer Kitchen';
  my.brew = function() {};
  return my;
}

let brewedBeer = myBeer();
brewedBeer('My First Beer');
brewedBeer.brand = 'Pangyo Craft';
brewedBeer.brew();

Enums

숫자형 Enum

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

enum Response {
  No = 0,
  Yes = 1,
}

function respond(recipient: string, message: Response): void {
  // ...
}

respond("Captain Pangyo", Response.Yes);

문자형 Enum

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

숫자형 Enum과 문자형 Enum을 혼합한 복합 Enum이 있지만 사용을 권고 하지 않음

런타임 시점에서 Enum은 실제 객체 형태로 존재함

enum E {
  X, Y, Z
}

function getX(obj: { X: number }) {
  return obj.X;
}
getX(E); // 이넘 E의 X는 숫자이기 때문에 정상 동작

-> 그렇지만 컴파일 시점에서는 keyof 대신 keyof type of를 사용할 것

enum LogLevel {
  ERROR, WARN, INFO, DEBUG
}

// 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
type LogLevelStrings = keyof typeof LogLevel;

function printImportant(key: LogLevelStrings, message: string) {
    const num = LogLevel[key];
    if (num <= LogLevel.WARN) {
       console.log('Log level key is: ', key);
       console.log('Log level value is: ', num);
       console.log('Log level message is: ', message);
    }
}
printImportant('ERROR', 'This is a message');

Reverse Mapping

  • Enum에만 존재하는 특징임
  • Enum의 key로 value를 얻을 수 있고 value로 key를 얻을 수도 있음
enum Enum {
  A
}
let a = Enum.A; // 키로 값을 획득 하기
let keyName = Enum[a]; // 값으로 키를 획득 하기

Type

Union Type

  • 자바스크립트의 OR 연산자와 비슷
  • any를 사용하는 것보다 타입스크립트의 장점을 살리면서 코딩 가능
// logText의 파라미터 text에는 string이나 number가 모두 올 수 있음
function logText(text: string | number) {
  // ...
}

Intersection Type

  • 여러 타입을 모두 만족하는 하나의 타입
interface Person {
  name: string;
  age: number;
}
interface Developer {
  name: string;
  skill: number;
}

type Capt = Person & Developer;

// 이것은 결국
// {
//  name: string;
//  age: number;
//  skill: string;
// }

Class

readonly

class Developer {
    readonly name: string;
    constructor(readonly name: string) {
        this.name = theName;
    }
}
let john = new Developer("John");
john.name = "John"; // error! name is readonly.

Accessor: 객체의 특정 속성의 접근과 할당에 대해 제어 가능(get, set 사용)

class Developer {
  private name: string;
  
  get name(): string {
    return this.name;
  }

  set name(newValue: string) {
    if (newValue && newValue.length > 5) {
      throw new Error('이름이 너무 깁니다');
    }
    this.name = newValue;
  }
}

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

타입을 함수의 파라미터처럼 사용하는 것, 즉 함수를 호출할 때 함수 안에서 사용할 타입을 넘겨줄 수 있음

function getText<T>(text: T): T {
  return text;
}

아래 코드는 T라는 변수 타입을 받고, 인자 값으로는 배열 형태의 T를 받는다.
예를 들어 [1,2,3]을 배열을 받고 number를 돌려줌

function logText<T>(text: T[]): T[] {
  console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용합니다.
  return text;
}

첫 번째 코드를 제네릭 인터페이스로 쓰면?

interface GenericLogTextFn {
  <T>(text: T): T;
}
function logText<T>(text: T): T {
  return text;
}
let myString: GenericLogTextFn = logText; // Okay

Generics class

class GenericMath<T> {
  pi: T;
  sum: (x: T, y: T) => T;
}

let math = new GenericMath<number>();

제네릭 제약 조건을 사용하여 두 객체 비교하기

// 첫번째 인자로 받는 개체에 없는 속성은 접근할 수 없게 제한함
function getProperty<T, O extends keyof T>(obj: T, key: O) {
  return obj[key];  
}
let obj = { a: 1, b: 2, c: 3 };

getProperty(obj, "a"); // okay
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않습니다.

타입 추론

타입스크립트가 코드를 해석해 나가는 동작을 의미함
보통 변수를 선언하거나 초기화 할 때 그리고 변수, 속성, 함수의 반환 값 등을 설정할 때 타입이 추론됨
타입 추론 방식

  • Best Common Type: 코드를 바탕으로 추론한 가장 근접한 타입
  • 문맥 상으로 결정
    타입 체킹
  • Duck Typing: 동적 타이핑의 한 종류로 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 의미함
  • Structural Subtyping: 객체의 실제 구조나 정의에 따라 타입을 결정하는 것을 말함

타입 호환

타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미함

구조적 타이핑: 코드 구조 관점에서 타입이 서로 호환되는지의 여부를 판단하는 것

interface Avengers {
  name: string;
}

let hero: Avengers;
// 타입스크립트가 추론한 y의 타입은 { name: string; location: string; } 입니다.
let capt = { name: "Captain", location: "Pangyo" };
hero = capt;
// capt의 속성 중에 name이 있기 때문에 capt이 hero 타입에 호환될 수 있음

Soundness: 타입스크립트는 컴파일 시점에 타입을 추론할 수 없는 특정 타입은 일단 안전하다고 봄

타입 별칭(Type Aliases)

특정 타입이나 인터페이스를 참조할 수 있는 타입 변수
새로운 타입 값을 생성하는 것이 아니라 정의한 타입에 대해 나중에 쉽게 참고할 수 있게 이름만 붙이는 것

// string 타입을 사용할 때
const name: string = 'capt';

// 타입 별칭을 사용할 때
type MyName = string;
const name: MyName = 'capt';

type vs. interface
인터페이스는 확장 가능, 타입은 확장 불가능
-> 좋은 소프트웨어는 언제나 확장이 용이해야 한다는 원칙에 따라 가능한 type 보다는 interface로 선언해서 사용하는 것이 좋다

Usage

모듈: ES6+의 Modules의 개념과 유사하며 export, import와 같은 키워드를 사용한다
선언 파일

  • d.ts 파일은 타입스크립트 코드의 타입 추론을 돕는 파일
  • 타입스크립트 파일에서 사용할 수는 있지만 선언되어 있지 않은 전역변수나 전역 함수를 아래와 같이 선언함
// 전역 변수
declare const pi = 3.14;

// 전역 함수
declare namespace myLib {
  function greet(person: string): string;
  let name: string;
}

myLib.greet('캡틴');
myLib.name = '타노스';

인덱싱

// 타입스크립트에서 배열 요소와 객체의 속성에 접근 할 때는 인터페이스를 이용하자
interface StringArray {
  [index: number]: string;
}

const arr: StringArray = ['Thor', 'Hulk'];
arr[0]; // 'Thor'

Utility type

  • 이미 정의해 놓은 타입을 변환할 때 사용하기 좋은 타입 문법
  • 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: 특정 타입에서 몇 개의 속성을 선택하여 타입을 정의
interface Hero {
  name: string;
  skill: string;
}
const human: Pick<Hero, 'name'> = {
  name: '스킬이 없는 사람',
};
  • 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
기존에 정의되어 있는 타입을 새로운 타입으로 변환해주는 문법

// 기본 문법
{ [ P in K ] : T }
{ [ P in K ] ? : T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ? : T }

// 기본 예제
type Heroes = 'Hulk' | 'Thor' | 'Capt';
type HeroProfiles = { [K in Heroes]: number };
const heroInfo: HeroProfiles = {
  Hulk: 54,
  Thor: 1000,
  Capt: 33,
}

// 실용 예제
// 사용자 프로필 조회 API
interface UserProfile {
  username: string;
  email: string;
  profilePhotoUrl: string;
}

function fetchUserProfile(): UserProfile {
  // ...
}

// 프로필 정보 수정 API
interface UserProfileUpdate {
  username?: string;
  email?: string;
  profilePhotoUrl?: string;
}

function updateUserProfile(params: UserProfileUpdate) {
  // ...
}

// 이때 동일한 타입에 대해 반복 선언하는 것은 좋지 않음
type UserProfileUpdate = {
  username?: UserProfile['username'];
  email?: UserProfile['email'];
  profilePhotoUrl?: UserProfile['profilePhotoUrl'];
}

// 이것에 keyof를 적용하면?
type UserProfileUpdate = {
  [p in keyof UserProfile]?: UserProfile[p]
}

Config

tsconfig

tsconfig.json 파일은 타입스크립트를 자바스크립트로 변환할 때의 설정을 정의해놓는 파일
'tsc app.ts'라고 치면 app.ts파일이 app.js파일로 컴파일 됨

@types 라이브러리:일반적으로 index.d.ts 파일과 package.json 파일로 구성되어 있음

참고자료

TypeScript Handbook

0개의 댓글