1. 함수에서의 타입 정의

보통의 함수에는 매개변수와 반환값이 존재한다.

반환값은 타입을 설정하지 않아도 자동으로 추론되지만

매개변수는 타입을 꼭 설정해줘야한다.

✔️화살표함수도 마찬가지

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

const foo = (a:number,b:number) => a+b;

 

타입 별칭에서도 선택적 프로퍼티를 사용할 수 있듯이 함수의 매개변수도 선택적으로 사용할 수 있다.

 

function foo(name:string, age?:number){
...
}

 

하지만 이 경우 선택적 매개변수는 지정된 타입 혹은 undefined로 추측되기때문에 타입좁히기가 필요하다.

 

function foo(name:string, age?:number){
    if(typeof age === "number"){
    	실행문
    }
}

 

선택적 매개변수는 반드시 모든 변수의 맨 뒤에 와야한다.

 

  • ...rest 매개변수

여러 숫자를 하나의 배열로 묶어주는 역할

때문에 ...rest의 타입을 정의할 땐 number[]로 정의한다.

 

function foo(...rest:number[]){
	실행문
}

 

결국 배열의 형태이기때문에 튜플 타입으로 설정해도 무방하다.

function foo(...rest:[number,number]){
	실행문
}

다만 이렇게 되면
foo(123,10) //정상
foo(12,5,90) //오류

 

 

 

함수의 타입을 정의하는 방법은 크게 두 가지가 있다.

2.함수 타입 표현식

객체에서 자주 써먹었던 타입 별칭을 이용한 방법을 함수에서도 쓸 수 있다.

type Add = (a:number, b:number) => number;

const add:Add = (a,b) => a+b;

 

함수의 선언&구현 부분과 타입을 분리할 수 있어 훨씬 읽기 깔끔한 코드가 된다.

그리고 반환값이 다른 여러 개의 함수가 동일한 타입을 갖고있을 때도 깔끔하게 코드 정리 가능(아래 예시)

 

type Add = (a:number, b:number) => number;

const add:Add = (a,b) => a+b;
const sub:Add = (a,b) => a-b;
const mul:Add = (a,b) => a * b;

 

3.호출 시그니쳐

객체에서의 타입별칭 형식처럼 정의하는 방법이다.

type Func = {
	(a:number, b:number) : number; 
}

이런식

 

4. 함수의 호환성

4-1. 반환값 타입에 따라

기본 타입의 호환성을 따졌을 때 처럼 a =b 일 때 반환값 a가 b의 슈퍼타입일 때 가능하다.

type A = () => number;
type B = () => 100;

let a:A = () => 100;
let b:B = () => 100;

a = b // 가능
b = a // 오류

 

함수 a의 반환값 타입은 number

함수 b의 반환값 타입은 number 리터럴타입으로 a함수타입이 b함수타입의 슈퍼타입

a=b의 관계에서 a가 b의 슈퍼타입이어야한다는 점을 생각하면 당연한 원리이다.

 

4-2. 매개변수의 타입에 따라

  • 매개변수의 개수가 같을 때

하지만 조금 다르게 생각해야 하는 경우가 있다.

type A = (val: number) => void;
type B = (val: 10) => void;

let a:A = (val) => {};
let b:B = (val) => {};

이 경우
b = a // 서브 = 슈퍼 관계일 때 호환된다.

 

왜냐하면

매개변수 타입이 객체 타입일 때를 예로 들어 생각해볼 수 있다.

type A = {
    name:string;
}

type B = {
    name=string;
    age:number
}

let a = (value:A) => {
 console.log(value.name)
}

let b = (value2:B) {
 console.log(value.name)
 console.log(value. age)
}

a = b 결과

let a = (value:A) => {
 console.log(value.name);
 console.log(value.age)
}

매개변수 타입은 A를 따르고 있지만 함수 실행문 부분에서는 age라는 타입 B의 프로퍼티에도 접근하고있기기에 오류가 발생한다.

 

반대로 

b=a 결과
let b = (value2:B) => {
	console.log(value2.name);
}

 

으로 정상적으로 name 프로퍼티에 접근한다.

 

  • 매개변수의 개수가 다를 때 
type A = (a:number; b:number) => void;
type B = (a:number) => void;

이런 경우

let a:A = (a,b) => {};
let b:B = (a) => {};

a = b //일 때 호환된다.

 

 

 

 

서로소 - 겹치는게 하나도 없음

type Customer= {
    name:string;
    point:number;
}

type Admin= {
    name:string;
    sale:number
}

type Food = {
    name:string;
    price:number;
}

type Super = Customer | Admin | Food ;

function cart(super:Super){
    if("point" in super ) {
    	console.log(`${ super.name} 의 포인트는 ${super.point} 점이다`)
    } else if ("sale" in super) {
    	console.log(`${super.name}의 판매량은 ${super.sale}이다.`)
    } else {
    	console.log(``${super.name}의 가격은 ${super.price} )
    }
}

 

📌문제점

코드가 길어질 경우 타입의 프로퍼티만 보고 어느 타입인지 알기가 어려움

프로퍼티 네임이 변경될 경우 모든 코드 수정이 필요, 오류발생 가능성 올라감

 

📌해결법

각 타입에 태그 프로퍼티를 추가해서 서로소 관계로 만든다.

type Customer= {
    tag: "CUSTOMER"
    name:string;
    point:number;
}

type Admin= {
    tag: "ADMIN"
    name:string;
    sale:number
}

type Food = {
    tag:"FOOD"
    name:string;
    price:number;
}

 

이제 Customer 타입이면서 동시에 Admin 타입인 경우는 없다. 각각의 tag 프로퍼티에 들어간 값은 고유값이기때문.

집합 도형의 개념을 떠올려보면 Customer, Admin, Food 타입은 교집합을 그리지 않고 각각 독립된 동그라미 형태로 존재한다.

function cart(super:Super){
    if(super.tag === " CUSTOMER") {
    	console.log(`${ super.name} 의 포인트는 ${super.point} 점이다`)
    } else if (super.tag === " ADMIN") {
    	console.log(`${super.name}의 판매량은 ${super.sale}이다.`)
    } else {
    	console.log(``${super.name}의 가격은 ${super.price} )
    }
}

 

1.타입 추론

타입 추론이 되는 경우

  • 초기값이 설정된 변수와 객체

    let num1 = 100; // number type으로 추론
    let str1 = "type" // string type으로 추론

    let object1 = {
        name: "이름", // string으로 추론
        age: 100, //number으로 추론
        skill : {
            kill: "test" //string으로 추론
        }
    }
 

 

  • 객체와 배열의 구조분해할당

    let object1 = {
        name: "이름",
        age: 100,
        skill : {
            kill: "test"
        }
    }

    let {name, age, skill} = object1; //각각 string,number,string으로 추론

    let [one,two,three] = [true,122,"문자"] //각각 boolean,number,string 으로 추론
 

 

  • 함수의 반환값

    function foo(){ //number type으로 추론
        return 12345;
    }
 

 

  • 함수의 매개변수에 기본값이 설정된 경우

    function foo(value = "test"){ // value는 string type으로 추론
        return 12345;
    }
 

 

타입 추론 안되는 경우

  • 함수 파라미터가 타입 정의되어있지 않은 경우

    function foo(value){ // 오류
        return 12345;
    }

 

 

암시적으로 any타입이 추론되는 경우

  • 초기값 설정 안한 변수

그 후에 어떤 값을 할당하느냐에 따라 타입이 변화한다.


    let type; // any
    type = 100 // any -> 아직 변수에 100이 담기지 않은 상태, 줄바꿈이 되어야 담긴다
    type; // number
    type ="test" //any
    type; //string
    type = true //any
    type; //boolean
 

 

 

const 상수


  let unm1 = 100; //number type으로 추론
  const num2 = 100 //100으로 추론
 

 

const는 초기화 할 때 설정한 값을 변경할 수 없기때문에 가장 좁은 타입으로 추론된다.

 

 

2. 타입 단언

  • 특정 값을 원하는 타입으로 단언할 수 있다.
type Test = {
    name:string;
    age:number;
}

let obj:Test = {}


obj.name="이름";
obj.age=100;

 

name과 age 프로퍼티 두 개를 갖는 타입으로 타입을 정의했지만 

객체 자체는 빈 값으로 초기화한다면 오류메시지가 뜬다.

 

이 때 

let obj = {} as Type

으로 타입단언을 이용하면 오류가 사라진다.

 

  • 초과 프로퍼티에 의한 오류 해제 가능
type Test = {
    name:string;
    color:string;
}


let dog:Test = {
    name:"멍멍",
    color:"점박",
    breed:"믹스"
} as Test

 

다음과 같은 초과 프로퍼티 상황에도 타입단언을 이용해서 눈속임할 수 있다.

 

  • 타입 단언 사용조건

값과 타입 서로가 서로의 슈퍼 혹은 서브타입이어야한다.

예를 들어 


    let val01 = 100 as string //오류
 

 

  • 다중 단언

위와 같은 경우에도 꼼수가 하나 있는데 바로 타입 단언을 여러 번 하는 것도 가능하다.


    let num = 10 as any as string
 

 

이렇게 두 번에 걸쳐 타입선언을 하면 number타입으로 정의된 변수를 string타입으로 선언할 수도 있지만

나중에 오류 발생 가능성이 높아 주의해야 함

 

  • const 단언

const 타입으로 단언하면 변수를 const로 선언한 것과 비슷하다.


    let num = 10 as const //num의 타입은 10이 된다
    num = 100; //오류

    let cat = {
        name:"야옹" //모든 프로퍼티는 읽기전용인 readonly가 된다
    } as const
    cat.name="냐옹" //오류

 

 

오류가 발생하는 부분은 터미널에 이렇게 표시된다.

 

  • Non Null 단언

뒤에 !  를 붙이면 이 값은 undefined/null이 아닐 것으로 단언할 수 있다.

type Post = {
    title:string;
    author?:string
}
let post:Post = {
    title:"제목"
}
cosnt test:number = post.author!.length;

 

Post타입에는 title과 author가 있지만 author는 있을 수도, 없을 수도 있는 프로퍼티이다.

이런 경우 맨 아랫줄은 오류가 발생하는데 이 때 Non Null단언 활용 가능

 

3.타입 좁히기

 

function foo (val: number | string) { }

에서 파라미터는 number타입이 될 수도, string 타입이 될 수도 있다.

function foo (val: number | string) {
    val.toFixed();
    val.toUpperCase();
}

파라미터가 숫자일지 문자일지 모르는 상태이기때문에 실행문은 모두 오류가 발생한다.

이 때 타입 좁히기를 사용할 수 있음

 

3-1) typeof ===

function foo (val: number | string) {
    if( typeof val === "number" ) {
        val.toFixed();
    }
else if( typeof val === "string" ) {
    	val.toUpperCase();
    }
}

 

3-2) instanceof

이번엔 내장 클래스 타입을 보장할 수 있는 타입가드이다.

function foo (val: number | string | Date) {
    if( typeof val === "number" ) {
    	val.toFixed();
    } else if( typeof val === "string" ) {
        val.toUpperCase();
    } else if (val instanceof Date) {
    	console.log(val)
    }
}

instanceof 타입가드는 내장 클래스, 직접 만든 클래스에만 사용 가능하다.

 

3-3) in 타입가드

type Test = {
    test1: string;
    test2: number;
}

function foo(val : number | string | Test) {
    if("test2" in val) {
    	console.log(`테스트2 : ${test2}`)
    }
}

 

test2라는 프로퍼티가 val 안에 있다면 콘솔창에 텍스트를 출력한다.

대수타입

여러가지 타입을 합성해서 만드는 타입이다.

 

 

합집합(Union)타입

   
      let test:string | number;
 

 

바( | )를 이용해서 정의한다.

유니온타입으로 묶인 타입들은 슈퍼-서브관계가 아니어도 상관없음

 

  • 배열에도 사용 가능

      let Arr : (string | number | boolean)[]=["test",12,true]

 

  • 객체 type에도 사용 가능
   
    type One = {
        name:string;
        type:string;
    }

    type Two = {
        name:string;
        price:number;
    }

    type Unions = One | Two
 

 

예시로 든 type Unions인 객체들은 크게 세 가지로 나뉠 수 있다.

 

- name과 type 프로퍼티 두 가지만 갖거나

- name과 price 프로퍼티 두 가지만 갖거나

- name과 type과 price 프로퍼트 세 가지 모두 갖거나

 

 

교집합(intersection)타입

   
        let test: string & number;
 

 

& 을 이용한다.

 

유니온타입에서는 타입들간의 관계가 중요하지 않았지만 인터섹션타입은 타입끼리 겹치는 부분이 필요하다.

때문에 기본 타입들간의 인터섹션은 잘 이용하지 않고 객체 타입에서 많이 이용함.


    type Type1 = {
        name: string;
        color: string;
    }

    type Type2 = {
        name:string;
        skill:string
    }

    type Intersection = Type1 & Type2
 

 

& 연산자를 알고있다면 교집합타입 이해가 쉽다.

Type1과 Type2의 프로퍼티를 모두 갖고있어야 한다는 뜻

 

 

 

 

1.사전 지식

타입스크립트의 호환을 이해하기 위해서

  • 부모타입=슈퍼타입=상위타입
  • 자식타입=서브타입=하위타입
  • 업캐스팅 : 서브타입을 슈퍼타입 값으로 취급한다. 즉, 작은놈을 큰놈에 집어넣음
  • 다운캐스팅: 슈퍼타입을 서브타입으로 취급한다. 큰놈을 작은놈에 집어넣음

집합의 개념으로 생각 해 보면 업캐스팅은 대부분의 경우 가능하지만 다운캐스팅은 불가한 경우가 많다.

 

let num1:number = 10 //넘버타입
let num2:20 = 20 //넘버 리터럴타입

① num1 = num2 //가능
② num2 = num1 //오류

 

(A) = (B) 일 때 B를 A로 취급한다, B를 A에 집어 넣는다 라고 생각하면 이해가 쉽다.

 

넘버타입은 넘버리터럴타입보다 집합범위가 넓기때문에 ①은 가능, ②는 불가능하다.

 

 

2.특이한 놈들

2-1) unknown 타입

  • 타입 계층 최상단에 있다.
  • 모든 타입의 슈퍼타입(부모타입)
  • 모든 타입은 unknown 으로 업캐스팅 가능 / unknown 타입의 값은 어떤 타입의 변수에도 할당 불가능
  • 다만 any타입에는 할당 가능하다.
 let num:number;
 let Un:unknown;

 num = Un // 이건 안 되고

 let anyVar:any;
 anyVar = Un //이건 된다

 

2-2) any 타입

  • 타입 계층도를 무시하며 자유롭게 업캐스팅, 다운캐스팅이 가능하다.
  • 모든 타입의 슈퍼타입이 될 수도, 서브타입이 될 수도 있다.
  • 그렇기때문에 any타입을 쓰는 것은 타입스크립트를 쓰지 않는것이나 다를 게 없어 주의가 필요

 

2-3) never 타입

  • 타입 계층 최하단에 있다.
  • 공집합이기때문에 어떤 타입도 다운캐스팅할 수 없다.(any 포함)
  • 모든 타입의 서브타입
  • 어떤 타입으로든 업캐스팅 가능하다.

 

2_4) void 타입

  • void의 서브타입으로는 undefined와 never가 있다.
  • 반환값이 void인 함수에서는 undefined를 반환해도 된다.
  • void타입의 변수에 들어갈 수 있는 타입은 undefined와 never뿐이다.

 

 

3.객체 호환

객체 호환 역시 "포함"의 개념으로 이해하면 쉽다.

type Book = {
    name:string;
    price:number;
}

type CookBook = {
    name:string;
    price:number;
    skill:string
}

let book1:Book = {
    name:"일반책",
    price:"1000"
}

let book2:CookBook={
    name:"요리책"
    price:2000,
    skill:"한식"
}

book1=book2 // 가능
book2=book1 // 오류

 

언뜻 보면 더 많은 프로퍼티를 갖고있는 CookBook이 슈퍼타입으로 보일 수 있겠다.

하지만 Book타입은 name과 price, 두 가지 프로퍼티만 갖고있으면 되고

CookBook타입은 name과 price에 이어 skill 프로퍼티까지 갖고있어야 하기 때문에

Book타입보다 CookBook타입으로 정의하는게 더 까다롭다고 볼 수 있다.

 

결론 : 객체 타입에서는 프로퍼티 갯수가 적을수록 슈퍼타입이다.

 

초과 프로퍼티

  • 객체 리터럴로 초기화할 때 실행되는 타입스크립트 문법
  • 타입에 정의된 프로퍼티 외 다른 초과된 프로퍼티를 갖는 객체를 변수에 할당하지 못하게 한다.
type Book = {
    name:string;
    price:number;
}

type CookBook={
    name:string;
    price:number;
    skill:string
}

let sellbook:Book={
    name:"요리책",
    price:22000,
    skill:"양식"
}

 

객체 sellbook의 타입은 name과 price만을 갖는 Book타입으로 정의했지만 

정작 초기값 설정은 skill프로퍼티까지 갖고있는 CookBook 타입으로 설정해서 오류 발생

 

  • 초과프로퍼티 오류를 피하기 위해서
let sellCookBook:CookBook = {
    name:"요리책",
    price:22000,
    skill:"양식"
}

let sellBook:Book = sellCookBook

 

이처럼 객체 리터럴 타입으로 초기값을 설정하지 않고,

변수에 담아 전달하면 초과프로퍼티를 피할 수 있다.

 

+ Recent posts