claire 2022. 7. 15. 17:23

타입스크립트는 자바스크립트에 타입을 부여한 언어이다. 왜 타입스크립트를 써야하나?

- 에러 사전 방지를 위해. 

아래와 같이 작성하면 sum('10','20')과 같이 의도하지 않은 값의 동작을 예방할 수 있다. 

// math.ts
function sum(a: number, b: number) {
  return a + b;
}

- 코드 자동 완성과 가이드

VS code의 내부가 타입스크립트로 작성되어 있어 타입스크립트 개발에 최적화 되어있다. 따라서 VS code에서해당 타입에 대한 apif르 미리보기로 띄워줄  있어 빠르게 자동 완성이 가능하다. 

 

타입스크립트 장점

  1. 타입이 있다는 것이 장점이다.
  2. 타입 안정성이 가장 큰 장점이다. 
  3. 안정성-컴파일 단계에서 미리 오류를 감지할 수 있다.
  4. 가독성-타입을 보고 이 로직이 무엇을 하는지 미리 알 수 있다.

타입스크립트 단점

  1. 초기 설정을 해야한다. (초기 설정이 조금 어려울 수 있다. )
  2. 스크립트 언어의 유연성이 낮아진다.
  3. 컴파일 시간이 길어질 수 있다.

타입 추론, 명시적 코드. 명시적 코드가 아닌 타입스크립트가 직접 추론하도록 하는 것이 좋다. 

 

optional type 

age는 number나 undefined 값을 가진다. 

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

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

별칭 가능. 

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

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

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

함수 리턴값 타입 주기

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

// function playerMaker(name:string):Player{
//     return{
//         name
//     }
// }

const playerMaker=(name:string):Player=>({name})

const nico=playerMaker("nico")
nico.age=12

readonly - 수정 불가

type Age=number;
type Name=string;
type Player={
    readonly name:Name,
    age?:Age
}

const playerMaker=(name:string):Player=>({name})
const nico=playerMaker("nico")
nico.age=12
nico.name="las"

const numbers:readonly number[]=[1,2,3,4]
//readonly엔 push불가
// numbers.push(1)

Tuple - array 생성. 최소한의 길이 가짐. 특정 위치에 특정 타입이 있어야 함. 

const player:[string,number,boolean]=["nico",1,true]

다른 타입들

let a:undefined=undefined
let a:null=null
//빈 값을 주면 typescript는 any라고 생각한다.
//any는 쓰지 않는 것을 권장. 걍 쓰지 마라. 타입스크립트 장점 다 사라짐. 
let a=[]

타입스크립트에만 존재하는 타입

- unknown

let a:unknown;

//아래처럼 쓰면 오류가 발생한다 a가 무슨 타입인지 몰라서
let b=a+1

//타입 체크 후는 괜찮
if(typeof a==='number'){
    let b=a+1
}

if(typeof a==='string'){
    let b=a.toUpperCase()
}

- void

// voud는 아무것도 리턴하지 않는 함수를 대상으로 사용
//void를 따로 지정해줄 필요는 없고 타입스크립트가 자동으로 이 함수가 아무것도 리턴하지 않는다는 것을
//인식한다. 
function hell(){
    console.log('x')
}

- never

//never는 함수가 절대 리턴하지 않을 때 사용. 
//예를 들면 함수에서 exception이 발생할 때. 

//never를 쓰고 return값이 있으면 오류가 발생. 
function hello():never{
    return 'X'
}

//하지만 throw로 오류를 발생시키면 괜찮다. 
function helo():never{
    throw new Error('xxx')
}

function helo(name:string|number){
    if(typeof name==='string'){

    }else if(typeof name==='number'){

    }else{
        //이 name값은 never이다. 
        name
    }
}

Call Signatures

 

 

call signatures는 함수 위에 마우스를 올렸을 때 뜨는 파라미터 타입 정보와 리턴 타입 정보를 말한다. 함수를 어떻게 호출해야 하는지와 반환이 어떻게 되는지 알려주는 정보이다. 즉, 인자의 타입과 함수의 반환 타입을 알려주는 것입니다. 

 

Overloading

실제로 오버로딩 코드를 많이 작성하지는 않을 것이다. 다른 사람들이 만든 외부 라이브러리 사용시 

패키지나 라이브러리는 오버로딩을 많이 사용한다. 

오버로딩은 함수가 여러개의 call signatures를 가지고 있을 때 발생시킨다.  서로 다른 여러 개의 call signatures가 있는 함수가 오버로딩이다. 

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

//add의 타입을 알기에 a,b를 추론할 수 있게 되는 것이다. 
const add:Add=(a,b)=>{
    if(typeof b==='string')return a
    return a+b
}
type Config={
    path:string,
    state:object
}

type Push={
    (path:string):void
    (config:Config):void
}

const push:Push=(config)=>{
    if(typeof config==='string')console.log(config)
    else{
        console.log(config.path,config.state)
    }
        
}
type Add={
    //서로 다른 call signature에 인자 갯수도 다름.
    (a:number,b:number):number
    //c는 옵션이다. 
    (a:number,b:number,c:number):number 
}

// 따라서 c가 number일 것이라고 알려줘야 한다. 
const add:Add=(a,b,c?:number)=>{
    return a+b
}

add(1,2)
add(1,2,3)

polymorphism (다형성)

 다형성이란? - poly는 많은 이라는 뜻을 가지고 있고 morphos는 형태나 구조를 말한다. 

여러 다른 형태를 말한다. 

타입스크립트에서 함수는 다른 2-3개의 인자를 가질 수 있다. 또는 타입스크립트에서 함수는 string이나 object를 첫번째 파라미터로 가질 수 있다. 

 

<generic>

generic은 타입의 placeholder와 같은 것이다. concrete type 대신 이것을 쓸 수 있다.  placeholder는 call signature를 요구하는 대로 생성한다. 

즉, 제네릭은 내가 요구한 대로 signatuer를 생성해줄 수 있는 도구이다. 

// 아래처럼 하는게 답이 아니다. 이 타입들은 concrete Type이 아니다. 
// concrete type은 앞서 배운 string, number, boolean...등을 말한다. 
// type SuperPrint={
//     (arr:number[]):void
//     (arr:boolean[]):void
//     (arr:string[]):void
// }

//제네릭 사용
type SuperPrint={
    // <>안에 원하는 제네릭 이름을 넣어준다. 
    // 이것이 이 call signature가 제네릭을 받는다는 것을 알려주는 방법이다. 
    <T>(arr:T[]):void
}

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

//타입스크립트는 이 값들을 보고 타입을 유추하고 기본적으로 그 유추한 타입으로 call signature를 보여준다. 
// placeholder를 통해 call signature를 작성하면 타입스크립트는 placeholder대신 타입스크립트가
// 발견한 타입으로 바꿔준다. 
superPrint([1,2,3,4])
superPrint([true,false,true])
superPrint(["a","b","c"])
superPrint(["a","b","c",false])
// 아래처럼 하는게 답이 아니다. 이 타입들은 concrete Type이 아니다. 
// concrete type은 앞서 배운 string, number, boolean...등을 말한다. 
// type SuperPrint={
//     (arr:number[]):void
//     (arr:boolean[]):void
//     (arr:string[]):void
// }

//제네릭 사용
type SuperPrint=<T,M>(a:T[],b:M)=>T

const superPrint:SuperPrint=(a)=>a[0]

//타입스크립트는 이 값들을 보고 타입을 유추하고 기본적으로 그 유추한 타입으로 call signature를 보여준다. 
// placeholder를 통해 call signature를 작성하면 타입스크립트는 placeholder대신 타입스크립트가
// 발견한 타입으로 바꿔준다. 
superPrint([1,2,3,4],"x")
superPrint([true,false,true],1)
superPrint(["a","b","c"],false)
superPrint(["a","b","c",false],[])

제네릭 쓰이는 예시

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

const a=superPrint([1,2,3,4])

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

type NicoExtra={
    favFood:string
}

type NicoPlayer=Player<NicoExtra>

const nico:NicoPlayer={
    name:'nico',
    extraInfo:{
        favFood:"kimchi"
    }
}

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

제네릭은 함수 말고도 많이 쓰인다. 

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

function printAllNumbers(arr:Array<number>){

}

//리액트의 useState를 사용하면 타입스크립트는 state의 타입을 알 수 없어서 제네릭을 보내면
//useState의 call signature가 number 타입의 useState가 된다. 
useState<number>()