본문 바로가기

TypeScript

Typescript 문법

Types of typescript - 1

//**Alias 타입 = ? -> 값을 넣어도 되고 안 넣어도 됨
//**Type 값 미리 지정 -> 앞에 type이 붙은 Player가 이것 -> 생성한 타입은 변수명 뒤에 ': + 타입명' 식으로 사용해줌

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

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

const lynn : Player = {
    name : "lynn",
    age : 12
}


//구지 그럴 필요는 없지만 type을 아래와 같이 사용 가능
type Age = number;

type Player2 ={
    age? : Age;
}


//**함수의 return값 지정 (특히 return값으로 객체를 선택할 때의 형식 지정)
function playerMaker(name:string){
    return{
        name:name //혹은 name만
    }//name을 담고 있는 객체를 return. -> 그냥 name만 return하면 문자열 return
}

const pla = playerMaker("nico");
//pla.age = 12; -> player자료형이 아니기에 오류 발생

function playerMaker2(name:string) : Player{ //함수가 return 하는 자료형
    return{
        name
    }
}

const pla2 = playerMaker2("nico");
pla2.age = 12;

//**함수의 return값 지정(화살표)
const playerMaker3 = (name:string) => (name)

const playerMaker4 = (name:string) => ({name})
//그 외 타입 any(모든게 가능한 타입), null, undefined

 

Types of typescript - 2

//**readOnly1

type Age = number;
type Name = string;
type Player = {
    readonly name:Name //readonly가 특이하게 쓰임
    age?:Age
}

const playerMaker = (name:string) : Player => ({name})
const nico : Player = playerMaker("nico")
nico.age = 12
//nico.name = "las" -> readonly라 오류 발생


//**readOnly2
const num : number[] = [1,2,3,4]
const num2 : readonly number[] = [1,2,3,4] 

num.push(4);
//num2.push(4); -> readonly때문에 오류 발생


//**Tuple 
//항상 정해진 위치에 정해진 타입을 가져야 하는 array

const player : [string, number, boolean] = ["nico", 12, false]
const player2 : readonly [string, number, boolean] = ["nico", 12, false] //readonly 사용

 

Types of TypeScript - 3

//**Typescript 독특한 타입들

//1. unkown
//type 검사 후 쓸 수 있는 타입
let a:unknown;

// let b = a+1; -> unknown이라 불가능

if(typeof a === 'number'){ // unkown의 type 검사
    let b = a+1;
}

//2. void
//일반적인 void와 같다
function hello(){ //void는 입력하지 않아도 설정됨
    console.log('x')
}

//3. never 
//절대로 return하지 않음.

function hello2():never{
    throw new Error("xxx") ///오류를 발생시키는 문장
}

function hello3(name : string|number){
    if(typeof name === "string"){ //type이 string

    }else if(typeof name === "number"){ //type이 number

    }else{ //여기 type이 never -> 정상적이면 절대 실행되면 안됨

    }
}

 

call signiture

//**call signature
//함수 위에 마우스를 올렸을 때 나오는 메시지
//함수의 타입을 미리 만들어서 지정해 줄 수가 있다

type Add =(a:number, b:number) => number; //함수 타입 short cut
type Add2 = {
	(a:number, b:number) : number;
} //이것도 함수 타입. 위와 같은 걸 길고 복잡하게 만들기

const add = (a:number, b:number) => (a+b)
const add2:Add = (a,b) => a+b; //a와 b, 그리고 return의 타입을 미리 지정해 줬기 때문에 오류 x

 

오버로딩

 //**오버로딩
 //하나의 함수에 여러개의 call signiture를 가짐

type Config = {
    path : string,
    state : object
}
type Push = {
    (path:string):void 
    (config: Config):void //인자의 type 형태가 위와 다름
}

const push:Push = (config) => {
    if(typeof config === "string") 
        console.log(config) //여기선 string type
    else 
        console.log(config.path); //여기선 config type
}

//**오버로딩(타입의 개수가 다름)

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

const add:Add = (a, b, c?:number) => { //c는 선택할 수도 안 할수도 있기에 ?:를 달아줘야함
    return a+b
    //c가 있을 때도 작동하게 하려면 if(c)를 이용한 문장을 넣어줘야됨
}

 

다형성과 제너릭

//**다형성(polymorphos)
//poly = 많은, morphos = 구조

//배열을 받고 배열의 요소를 출력해주는 함수 작성(타입 상관 없이)
type SuperPrint = {
    (arr: number[]):void
    (arr: boolean[]):void
}

const superPrint:SuperPrint = (arr) => {
    arr.forEach(i => console.log(i))
}

superPrint([true, false])
superPrint([3, 4, 5])
//superPrint(["3", "4"])          -> string형이 선언에 없기에 오류
//superPrint([1, 2, true, false]) -> 이런 이상한 call signiture가 없기에 오류

//-> 위의 오류를 깔끔하게 해결하는 법(다형성, 제너릭 사용)


//**generic사용
//어떤 타입이 들어올지 확실히 모를 때
/*
'제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 
하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.'
*/

type SuperPrint2 ={
 <Goguma>(arr: Goguma[]):void //Goguma가 제너릭 이름
 //void대신 Goguma를 쓰면 return도 가능
}
type SuperPrint3 = <T>(arr: T[]) => void // 위와 같음

const superPrint2:SuperPrint2 = (arr) => {
    arr.forEach(i=>console.log(i))
}

superPrint2([true, false])
superPrint2([3, 4, 5])
superPrint2(["3", "4"]) //제너릭은 call signature를 사용자가 요청하는 대로 생성함
const a = superPrint2([1, 2, true, false]) 
//a.toUpperCase() -> 에러발생 
//그러면 any와 무슨 차이가 있냐고 할 수도 있지만 이런 상황에서 제너릭은 타입 보호를 해준다

//generic 2개 사용
type SuperPrint2 ={
 <T, G>(arr: T[], arr2 : G): G 
}

 

제너릭 결론

//내가 직접 제너릭을 만드는 경우는 많지 않고, 다른 패키지를 이용할 때, 제너릭을 사용하게 된다

//## 함수에서 제너릭 사용
function superPrint<T>(a: T[]){
    return a[0]
}

const a = superPrint([1,2,3,4])
const b = superPrint<number>([1,2,3,4]) //원한다면 타입을 명시할 수도 있음

//## 타입문에서 제너릭 사용
/*
제너릭을 사용해 타입문을 만든다면(아래의 E와 F)
그 타입을 이용해서 변수를 만들 때, 제너릭이 무엇인지 명시해주어야 한다.
*/
type Player<E> = {
    name : string
    extraInfo : E
}

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

type ZYI<F> = {
	(a:number, b:number) : F;
} 

const zyi:ZYI<number> = (a, b) => a+b

//위의 코드와 형태는 다르고 내용은 같은 코드
type Player2<E> = {
    name : string
    extraInfo : E
}

type AdvancedPlayer2 = Player<{favFood : string}>

const nico2 : AdvancedPlayer2 = {
    name : "nico",
    extraInfo : {
        favFood : "kimchi"
    }
}

//기본적인 타입스크립트의 타입들은 제너릭으로 만들어져 있다
//## 배열 대신 Array 사용하기

type A = number[]
type B = Array<number> //Array형 역시 제너릭으로 만들어진 타입
let a1:A = [1,2,3,4]
let a2:B = [1,2,3,4]

 

클래스

//## 접근 권한 설정
class Player{
    constructor( 
        private firstName:string,
        private lastName:string,
        public nickname:string
    ){} //생성자를 만들어주고 입력 매개변수 설정 + 접근 권한 설정
}

const zyi = new Player("nico", "las", "이주야");

//nico.firstName -> private라서 오류 발생
zyi.nickname


//##추상 클래스 
//다른 클래스들이 상속받을 수 있는 클래스
//직접 인스턴스를 제작할 수는 없음
abstract class User{
    constructor( 
        private firstName:string,
        private lastName:string,
        protected nickname:string
    ){} //생성자를 만들어주고 입력 매개변수 설정 + 접근 권한 설정
    
    //이걸 private으로 만들면 더 이상 작동 x -> 메소드에서도 접근권한 설정 가능
    getFullName(){ 
        return `${this.firstName} ${this.lastName}`
    }

    //##추상 메소드
    //상속받는 모든 것들이 구현 해야하는 메소드
    //callsignature을 적어서 작성
    abstract getNickName(arr:string):void //함수의 추상 메소드
}

class Player2 extends User {
    getNickName(){
        console.log(this.nickname) 
        /*private이라면 직접 접근은 x, 그런데 protected로 바꿔줘서 
        상속받은 자식 클래스에서도 접근 가능*/
    }
}

//const zyi = new User("1", "2", "3") -> 추상 클래스로 직접 만들려 해서 오류
const zyi2 = new Player2("nico", "las", "이주야");

zyi2.getFullName()

 

클래스2

type Words = {
    [word : string] : string //key의 타입, value의 타입 명시
}


//## 간단한 단어 프로그램 만들기

class Dict{
    // constructor(
    //     private words : Words
    // ){} 기본적인 word라는 변수의 선언 방법

    private words : Words
    constructor(){
        this.words = {
            "갤" : "갤갤갤"
        } //수동으로 초기화
    }
    //아래는 function이 아니라 method임 
    add(word : Word){  //클래스를 타입으로 사용
        if(this.words[word.term] === undefined){
            this.words[word.term] = word.def;
        }
    }
    def(term:string){
        return this.words
    }
}

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

const kimchi = new Word("kimchi", "한국의 음식");
const sushi = new Word("sushi", "일본의 음식");

const dict = new Dict()

dict.add(kimchi);
dict.add(sushi);

console.log(dict.def("kimchi"));

 

interface

//## 특정 값만 사용 가능

type Team = "red" | "blue" | "yellow"
type Health = 1 | 5 | 10

const team : Team = "red";
//const health : Health = 2; -> 1/5/10 중의 값이 아니면 에러


//## interface

type Player1 = {
    nickname : string
}

interface Player2 { //interface는 객체의 타입만을 설명할 수 있는 키워드
    nickname : string
}

 

interface 상속

//## interface 상속
interface User1 {
    name : string
}

interface Player1 extends User1{
}

const nico1 : Player1 = {
    name : "nico"
}

//위와 같은걸 type으로 쓴다면
type User2 {
    name : string
}

type Player2 = User2 & {
}

const nico2 : Player2 = {
    name : "nico"
}

 

interface 합치기

//## interface 합치기
interface User{
    name : string
}
interface User{ //같은 이름을 가진 interface를 쓰면 하나로 합쳐짐
    sex : string
}
interface User{
    age : number
}

const jiwon : User = {
    name : "jiwon",
    sex : "male",
    age : 24
}

 

interface 장점

//아래의 User를 추상클래스로 구현하게 되면 js코드에 User에 대한 
//일반 클래스가 자동으로 생성된다
//하지만 interface로 User를 만들고 extends대신 implements를 쓰면
//User에 대한 클래스가 생성되지 않는다.

interface User{
    firstNmae: string
    sayHi(name:string):string
}

class Player implements User{
    constructor(
        public firstNmae : string
    ){}
    sayHi(){
        return "hoho";
    }
}

//##interface를 타입처럼 사용 가능
const user : User = {
    firstNmae : "!",
    sayHi(name){
        return "1"
    }
};