Typescript 총정리

이유정·2023년 5월 19일
0

Typescript

목록 보기
1/5
post-thumbnail

Type

객체

const player : {
	name:string, 
    age?:number
}= {
	name: "nico"
}

문제1)


=> player라는 객체가 있는지 없는지부터 써줘야 한다.

if(player.age && player.age < 10){

}

alias(별칭)

type Player = {
    name:string, 
    age?:number
}

const yudang : Player = {
    name:"leeyujeong"
}

const minsu :Player = {
    name: "kimminsu",
    age: 27
}

void

unknown

never

사용빈도 void > unknown >>>> never

Functions

call signature

  • 함수 위에 마우스를 올렸을 때 보게 되는 것
  • 함수의 인자타입을 알려준다.
  • 함수의 반환타입도 알려준다.
    (엄청 많이씀)

=> 함수의 call signature 타입을 만들어보자.

예시1) 이런식으로 {} 를 쓰면, 이런 에러가 나온다. 타입이 void가 돼서 에러다. void는 함수인데 아무 값도 반환하지 않는 건데 number타입이 되어야해서.

overloading

  • 내가 직접 overloading 함수를 많이 쓰게 되진 않을것임
  • 그러나, 패키지나 라이브러리들은 오버로딩을 엄청 많이 사용한다.

위 call signature을 이렇게 써도 된다.

오버로딩이란?

  • 함수가 여러개의 call signature을 가지고 있을 때 발생시킨다.
  • 즉, 여러개의 call signature라는 뜻이다.

문제1) 똑똑한 타입스크립트는 숫자와 문자를 더하면 안된다고 경고한다.

해결)
이렇게 조건을 걸어서 b 타입이 number일때 return 값을 구분해줘서 써주면 해결된다.

문제2) 실제 개발할 때 마주할 overloading / 이런식으로 라이브러리나 패키지를 디자인한다.

문제3) call signature의 파라미터 개수가 다를때는?

해결3) c는 optional 하기 때문에 c?:number 이렇게 적어줘야 한다.

이런식으로 조건을 걸어준다.

type Add = {
    (a:number, b:number) : number
    (a:number, b:number, c:number) : number
}

const add:Add = (a,b,c?:number) => {
    if(c) return a+b+c //c가 있다면 return a+b+c
    return a+b
}

polymorphism (다형성)

  • 타입스크립트가 어떻게 다형성을 줄까? "generic"
    문제1) 어떤 배열의 타입을 받는지에 따라서 계속 써줘야 하나?

generic이란?

  • 타입의 placeholder 같은 것.
  • 쓰는이유: call signature 를 작성할 때, 들어올 확실한 타입을 모를때 generic을 사용한다.
  • 타입스크립트가 타입을 유추하도록 만든것이다.

=> 타입스크립트에게 이 call signature가 제네릭을 받는다는 걸 알려줘야 한다.
예시1) 이런식으로 제네릭을 써준다. 어떤 단어든 상관없음

제네릭 타입으로 call signature 타입 추론한 예시1)

제네릭 타입으로 call signature 타입 추론한 예시1)

return 값이 제네릭 타입으로 지정하고 싶을 때)

위 제네릭 사용 방법은 여러개 중 하나며 좀 어려운 편임. 이것보다 다른 방법이 더 자주쓰임

T는 배열에서 오고, 함수의 첫번째 파라미터에서 오는거라고 타입스크립트에게 알려준것.

제네릭 사용법) 빨간색 부분에서 나 제네릭 사용할거야~ 타입스크립트한테 알려주고,
파란색 부분에서 사용할거야
~ 알려준거임.!

generic을 이용해서 call signature을 직접 만드는 경험은 적을 것임

우리가 자주 쓰는 generic 방법

일반함수에서)

function superPrint<T>(a:T[]){
    return a[0]
}

const a = superPrint([1,2,3,4])
const b = superPrint([true, false, true])
const c = superPrint(["이건", "어때"])
const d = superPrint([1, false, "메롱"])

제네릭을 이용해 타입을 생성할 수도 있고, 확장시킬 수도 있고, 어떤 경우에는 코드를 저장한다.

type Player<E> = {
    name:string, 
    extraInfo:E
}

const nico: Player<{favFood:string}> = {
    name: "nico",
    extraInfo: {
        favFood:"kimchi"
    }
}

위 코드를, 이렇게 확장시킬 수 있음
확장1)

확장2)

타입을 재사용할 수 있다) lynn에게도 Player 타입 적용

const lynn:Player<null> = {
    name: "lynn",
    extraInfo: null
}

제네릭 사용하는 또 다른 방법1) 기본적으로 타입스크립트 타입은 제네릭으로 만들어져 있다.

type A = Array<number>
const a : A = [1,2,3,4]

또 다른 방법2) <> 이걸보면 제네릭을 사용했다고 보면된다.

또 다른 방법3) useState의 call signature가 number 타입의 useState가 되는 것

useState<number>()

Classes and Interfaces

Classes

  • 타입스크립트로 객체지향 프로그래밍을 해볼 것임. (클래스 같은 객체지향)
  • 타입스크립트가 객체지향 코드를 안전하게 만들어준다.
  • 타입스크립트가 반복되는 코드를 쓰지 않도록 막아준다.
class Player {
    constructor(
        private firstName:string, 
        private lastName: string, 
        public nickName: string
    ){}
}

const nico = new Player("nico", "las", "니꼬")

nico.firstName //private이기 때문에 안됨 
//js로 어떻게 컴파일 되어있나 
class Player {
    constructor(firstName, lastName, nickName) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.nickName = nickName;
    }
}
const nico = new Player("nico", "las", "니꼬");
nico.firstName; //private이기 때문에 안됨 

abstract classes

  • 타입스크립트와 객체지향 프로그램이 가지고 있는 훌륭한 것 중 하나는 추상클래스 Abstract Class다.
  • 다른 클래스가 상속받을 수 있는 클래스다.
  • 추상클래스는 직접 새로운 인스턴스를 만들 수 없다. (오직 다른 곳에서 상속받을 수 있는 클래스)

추상 클래스 안의 메소드 && abstract method (잘 이해 안갔음 ㅎㅎ,,)

예시) private로 만든 메소드는 이렇게 쓸 수 없다. / 근데 자바스크립트에서는 에러를 못 발견해낸다. 타입스크립트는 에러가 나기 전에 나를 보호해준다.

추상 메소드

  • 추상 메소드를 만들려면 메소드를 클래스 안에서 구현하지 않으면 된다.
  • 추상 클래스 안에서는 추상 메소드를 만들 수 있다.
  • 그러나, 메소드를 구현하면 안되고, 메소드의 call signature만 적어둬야 한다.
  • 추상 메소드는 추상 클래스를 상속받는 모든 것들이 구현을 해야하는 메소드를 의미한다.

에러를 확인해보자) 추상 메소드 getNickName() 을 실행시키지 않은 문제

문제 해결) 추상 메소드를 실행시키는데 nickName에 접근하기 위해서 private => protected 로 바꿨다.

객체 property는 모르지만 타입은 알 때

type Words = {
    [key: string] : string
}

let dict: Words = {
    "딸기": "맛있어요",
    "수박" : "씨 좀 빼줘요"
}

어떤 에러일까? ) words는 intializer가 없고 Contructor에서 정의된 sign이 아니라는 에러

//이렇게 안썼다고 뭐라 하는건데 하지만 나는 constructor가 words를 지정해주길 바라지 않는다. 
class Dict {
    constructor(private words : Words){

    }
    
}
class Dict {
    private words : Words //words를 initializer없이 선언해주고
    constructor(){
        this.words = {} //constructor에서 수동으로 초기화해줬다. 
    }
    
}

Interfaces

위 예시에서, 누군가가 kimchi.def = "김치 중국꺼" 임의로 데이터를 바꿀 수 없게 readonly 를 붙여서도 방지할 수 있다.

class Word {
    constructor(
        public readonly term: string,
        public readonly def: string
    ){}
}

구체적인 값을 가지게 하는 type

type Team = "red" | "blue"
type Health = 1 | 5
 interface Player {
 	nickname: string, 
    team: Team, 
    health: Health
 }

이처럼 type은 객체의 모양을 특정하는데 사용되기도 하고, 특정 값을 가지게 하기도 하고 , alias로 사용되기도 한다.

그러나, interface는 딱 한가지 용도로만 쓰인다.
: 객체의 모양을 특정하는데 사용된다!!!
=> interface를 쓰는이유? 더 객체지향 프로그래밍처럼 보여서 이해가 쉽다.

클래스 처럼 사용가능한 interface

interface User {
   readonly name: string
}

interface Player extends User{

}

const nico : Player = {
    name: "nico"
}

=> 이걸 type을 이용해서 코드를 쓰려면?

type User = {
    name: string
}

type Player = User &{

}

const nico : Player = {
    name: "nico"
}

property를 축적시킬 수 있는 interface

인터페이스를 3번 각각 만들어도, 타입스크립트가 알아서 하나로 합쳐준다.

interface User {
    name: string
}
interface User {
    lastName: string
}
interface User {    
    health: number
}

const nico: User = {
    name:"nico",
    lastName: "lee",
    health: 3
}

추상화를 원할 때 클래스와 interface를 사용할 때의 차이점에 대해 알아보자.

fullName과 sayHi 메소드를 구현해야 한다고 에러 뜸 !!!

abstract class User { 
    constructor(
        protected firstName: string,
        protected lastName: string
    ){}
    abstract sayHi(name:string): string
    abstract fullName(): void
}


class Player extends User {
    fullName(){
        return `${this.firstName} ${this.lastName}` //protected는 추상 클래스로부터 상속받은 클래스들이 property에 접근하도록 도와준다.
    }
    sayHi(name: string){
        return `Hello ${name}. My name is ${this.fullName()}`
    }
}
  1. 추상 클래스는 인스턴스를 만드는 것을 허용하지 않는다.
    new User() //에러
  2. 상속받는 클래스가 어떻게 동작해야할지 알려주기 위해서 추상클래스를 사용하는 것이다.
  3. 자바스크립트에서 추상클래스가 없기 때문에 결국, 클래스로 변하게 된다.
  4. 그럼 왜 추상클래스를 사용하는가?=> 다른 클래스들이 표준화된 모양, 표준화된 property와 메소드를 갖도록 해주는 청사진을 만들기 위해 사용한다.
    인터페이스는 가볍다. 컴파일함면 js로 바뀌지 않고 사라진다.
    따라서, 위의 추상클래스를 인터페이스로 바꿔보자.
interface User { 
    firstName: string,
    lastName: string
    sayHi(name:string): string
    fullName(): void
}

class Player implements User {
    constructor(
        public firstName: string,
        public lastName: string
    ){}
    fullName(){
        return `${this.firstName} ${this.lastName}` //protected는 추상 클래스로부터 상속받은 클래스들이 property에 접근하도록 도와준다.
    }
    sayHi(name: string){
        return `Hello ${name}. My name is ${this.fullName()}`
    }
}
  • contructor가 없고, abstract sayHI() 이런 부분도 없다.
  • 대신, object나 클래스의 모양을 묘사하도록 해준다.
  • 그러면서도, 자바스크립트 코드로 컴파일되지는 않는다. (추상클래스를 사용할 때는 그 클래스들이 자바스크립트에서 보였음)
  • 인터페이스를 상속할 때에는 property를 private으로 만들지 못한다.
  • 자바스크립트 코드를 보게 되면 더이상 추상 클래스를 추가로 사용하지 않는다.
  • 파일사이즈를 줄이고 싶을 때 쓴다.

이런식으로 추가도 가능하다. (한 클래스에서 여러 개의 인터페이스를 상속할 수 있다.)

인터페이스를 만들어두고 팀원이 원하는 각자의 방식으로 클래스를 상속하도록 하는건 멋진 방법이다!!! 만약 모두가 같은 인터페이스를 사용한다면, 같은 property와 method를 가지게 된다.

생성한 인터페이스를 당연히 타입으로도 사용가능하다.

  • argument에 인터페이스를 씀으로써 오브젝트의 모양을 지정해 줄 수 있다.

type과 interface 비교

타입스크립트에게 오브젝트의 모양을 알려주기 위해서는 인터페이스를 쓰고, 나머지 상황에서는 타입을 쓴다!!

Polymorphism

  • 다형성은 다른 모양의 코드를 가질 수 있게 해준다.
  • 다형성을 이룰 수 있는 방법은 제네릭을 사용하는 것
  • 제네릭은 placeholder 타입을 쓸 수 있도록 한다.
  • 때가 되면, 타입스크립트가 placeholder 타입을 concrete타입으로 바꿔준다. => 코드를 더 이쁘게 해줄뿐만 아니라 같은 코드를 다른 타입에 대해서 쓸 수 있도록 해준다.

타입스크립트에 의해 이미 선언된 자바스크립트의 웹 스토리지 API를 위한 인터페이스다.

제네릭을 사용해서 나만의 로컬 스토리지 API를 가상으로 만들어보자.

제네릭을 클래스로 보내고,

클래스는 제네릭을 인터페이스로 보낸 뒤에

인터페이스는 제네릭을 사용한다.

메소드를 만들어보자.

  • set 메소드 => api의 디자인의 구현을 보여주기 위함
  • remove 메소드 => key를 받아서 로컬 스토리지로부터 지울 것임.
  • get 메소드 => key를 받는 get. 이건 T를 리턴해줄 것이다.
interface SStorage<T>{
    [key: string] : T
}
class LocalStorage<T> {
    private storage:SStorage<T> = {}
    set(key:string, value: T){
        this.storage[key] = value; 
    }
    remove(key:string){
        delete this.storage[key]
    }
    get(key:string): T{
        return this.storage[key]
    }
    clear(){
        this.storage = {}
    }
}


profile
팀에 기여하고, 개발자 생태계에 기여하는 엔지니어로

0개의 댓글