본문 바로가기

TypeScript

TIL no.55 - TypeScript 를 사용하는 이유

이 포스트로 간단하게라도 당장 TypeScript의 타입을 적용해서 사용할 수 있게 되었으면 하는 의도로 포스팅하였습니다.

 

TypeScript 란 ?

JavaScript 의 모든 기능을 포함하며 JavaScript 에서 변수나 매개변수, 함수, 함수에서의 return 값들의  타입을 명확하게 지정해주어야 하는 언어이자 라이브러리를 말합니다. 자바스크립트에서 타입을 명시해줘야 하는 좀 더 엄격해진 언어라고도 하며 자바스크립트의 컴파일러 이기도 합니다.

타입스크립트는 굉장히 객체지향적인 언어입니다. 컴파일 타임 오류를 잡을 수 있으며 결과에서 발생하는 에러들을 쉽게 발견할 수 있도록 효율적으로 디버깅 가능하게 해주는 언어입니다.

 

 타입스크립트는 아주아주 유연한 언어인 자바스크립트와는 다르게 에러를 많이 보여줍니다.

변수나 함수, 매개변수의 타입을 지정해주지 않을 때도 빨간 줄, 타입을 다르게 지정해주었을 때도 빨간 줄.... 

그래서 어렵다고 생각하고 다가가기가 겁나는 언어라고 생각하지만 사실 타입스크립트는 새로운 다른 언어라기보다 자바스크립트로 구성되어 있기 때문에 자바스크립트만 안다면 쉽게 다가갈 수 있습니다.

타입들만 익히면 데이터 구조의 에러를 쉽게 파악할 수 있고 간결하게 구조가 잘잡혀 있는 코드를 작성할 수 있습니다.

 

 

자바스크립트 실행기 : 브라우저, node 

 

function getStudentDetails(studentID: number): {
	return
	{
    studentID: number,
    	studentName: string,
    	age: number,
    	gender: string,
        graduated: boolean
    };
}

위와 같이 이 함수가 반환할 객체의 구조 각각의 타입을 상세하게 지정해줍니다.

타입을 더 자세하게 명시할 수록 타입스크립트는 우리 코드를 더 명확하고 정리되게 구조되도록 도와줄 것입니다.

 

타입스크립트에게 우리가 더 많은 정보를 제공할 수록 우리에게 더 많은 도움을 줄 수 있습니다.

 

그런데 위에 반환할 타입을 모두 적어주니 조금 복잡하고 지저분해 보이지 않나요?

좀 더 정리된 코드를 구조화 시키기 위해서 interface를 사용할 수 있습니다.

 

interface Student {

	studentID: number,
    	studentName: string,
    	age: number,
    	gender: string,
        graduated: boolean
}


function getStudentDetails(studentID: number):Student {
    	return null;
    }

인터페이스를 사용하면 위와 같은 구조로 코드를 작성할 수 있습니다.

따로 타입으로 들어갈 것들을 인터페이스의 구조 안에 넣어주면 Student 라는 것을 타입으로 간단하게 사용할 수 있습니다.

 

또한 Student 객체의 타입이 재사용될 경우가 있다면 반복되는 interface를 작성할 필요 없이 Student를 타입으로서 재사용하면 되기 때문에 반복되는 코드를 줄이고 간결하게 작성할 수 있습니다.

 

 

인터페이스를 지정해주면 반드시 Student 안에 있는 프로퍼티를 모두 포함한 객체만을 반환해야합니다.

만일 type 인 Student 에서 있을 수도 있고 없을 수도 있는 프로퍼티가 있다면 물음표( ! ) 기호를 사용해서 해당 프로퍼티가 없어도 컴파일 에러없이 작동할 수 있도록 조금 유연하게 작성할 수 있습니다.

이렇게 작성하는 것을 Optional Type으로 작성한다고 합니다.

 

interface Student {

	studentID: number,
    	studentName: string,
    	age?: number,
    	gender?: string,
        graduated: boolean
}

function getStudentDetails(studentID: number):Student {
    	return null;
    }

 

 

만약에 아무것도 반환하지 않는 함수를 작성할 때에는

 

function saveStudentDetails(student: Student): void {

    }

void를 넣어줄 수 있습니다.

 

saveStudentDetails({
	studentID: 201525142,
    	studentName: 'haneul',
    	age?: '16',
    	gender?: 'male',
        graduated: true
})

또한 이렇게 넣어줄 객체를 변수로 따로 빼서 코드를 더 보기 좋게 작성할 수 있습니다.

let student1 = {

	studentID: 201525142,
    	studentName: 'haneul',
    	age?: '16',
    	gender?: 'female',
        graduated: true
        
      }

saveStudentDetails(student1);

 

 

 

메소드를 인터페이스 내에 정의하는 방법 

interface Student {

	studentID: number,
    	studentName: string,
    	age?: number,
    	gender?: string,
        graduated: boolean,
      //  addComment: (comment: string): string,
        addComment?: (comment: string) => string
}

addComment 를 작성했듯이 작성할 수 있습니다.

addComment 라는 이름의 함수이며 comment 라는 이름을 가진 string 타입의 매개변수를 가지먀며 string 값을 반환하는 메소드입니다.

 

 

 

 

Read Only 프로퍼티

readonly 읽기 전용 프로퍼티로 지정할 수 있고 읽기 전용은 다른 값으로 바꾸려고 한다면 에러를 보여줍니다.

interface Student {

	readonly studentID: number,
    	studentName: string,
    	age?: number,
    	gender?: string,
        graduated: boolean,
      //  addComment: (comment: string): string,
        addComment?: (comment: string) => string
}

 

 

 

 

Enum 열거형 : 숫자형 열거형

 

연관된 아이템들을 함께 묶어서 표현할 수 있는 수.

 

enum GentderType {
	Male,
   	 Female
    }

 

이렇게 설정하고 기존의 인터페이스를 아래와 같이 수정해주겠습니다.

 

interface Student {

	readonly studentID: number,
    	studentName: string,
    	age?: number,
    	gender?: GenderType,
        graduated: boolean,
      //  addComment: (comment: string): string,
        addComment?: (comment: string) => string,
}

위와 같이 string 값으로 지정되어 있던 gender의 타입을 GenderType 으로 지정해줍니다.

그 다음으로,

 

function getStudentDetails(studentID: number): {
	return
	{
    studentID: number,
    	studentName: string,
    	age: number,
    	gender: GenderType.Female,
        graduated: boolean
    };
}

function saveStudentDetails (student: Student): void {

}

saveStudentDetails(student1);

이렇게 gender의 타입을 바꾸어 지정해주고 saveStudentDetails 에서 넣어주고 있는 student1 변수에 가서도 타입을 다시 지정해줍니다.

 

let student1 = {

	studentID: 201525142,
    	studentName: 'haneul',
    	age?: '16',
    	gender?: GenderType.Femail,
        graduated: true
        
      }

 

만약에 문자형 열거형으로서 enum 을 사용하고 싶지 않다면

문자형 열거형으로서 아래와 같이 각각의 값을 할당해주면 됩니다.

 

enum GentderType {
	Male = 'male',
   	Female = 'female',
    	 genderNeutral = 'genderNeutral'
    }

 

 

 

리터럴 타입

 

interface Student {

	readonly studentID: number,
    	studentName: string,
    	age?: number,
    	gender: 'male' | 'female' | 'genderNeutral',
        graduated: boolean,
      //  addComment: (comment: string): string,
        addComment?: (comment: string) => string
}

enum을 사용했을 때랑 똑같은 기능이지만 이 부분에서는 훨씬 간결해보이게 작성할 수 있습니다.

나머지 값들은 enum이전에 타입을 지정하였던 것처럼 다시 바꾸면 됩니다.

 

function getStudentDetails(studentID: number): {
	return
	{
    studentID: number,
    	studentName: string,
    	age: number,
    	gender: 'female',
        graduated: boolean
    };
}

function saveStudentDetails (student: Student): void {

}

saveStudentDetails(student1);

 

 

any 타입  |  유니언 타입 

 

타입스크립트 정의를 설명할 때 말했다시피 타입스크립트는 타입에 관한 더 많은 정보를 명시할 수록 더 좋습니다.

그렇기 때문에 any 는 특정 타입을 명시적으로 정해놓을 수 있을 경우에만 사용하는 것이 좋습니다.

any 는 변수로 들어올 값이 어떠한 타입인지 미리 예측할 수 없을 때, 다양한 타입이 가능할 경우 사용합니다.

 

여러 타입이 들어올 수 있지만 그 중에서 몇가지 제한된 타입으로 정해져 있을 경우에는 any 보다 더 명시적으로 지정할 수 있습니다.

바로 유니언 타입을 사용하면 됩니다.

 

let price: number | string = 5;
price = 'free';
price = true;

// boolean 을 지정하면 타입에러가 발생합니다.

위와 같이 작성하면 price 의 값이 number 이거나 string 이여도 가능합니다.

 

그런데 만약에 코드내에 number 와 string 이 둘다 가능한 타입이 여러 군데에 사용되어야 한다면 어떻게 해야 할까요?

똑같이 반복되는  number | string   이 부분을 재사용 가능한 코드로 사용해야 합니다.

 

똑같이 반복되는 코드를 타입으로 지정해놓고 사용하기 위해서 Type Aliases 사용할 수 있습니다.

 

 

Type Aliases

type StrOrNum = number | string;
let price = StrOrNum;
let orderName = StrOrNum;

위와 같이 작성할 수 있습니다.

 

 

타입 가드

type StrOrNum = string | number;
let itemPrice: number;

const setItemPrice = (price: StrOrNum): void => {
	itemPrice = price;
};

setItemPrice(50);

이와 같은 코드를 작성하면 setItemPrice 함수 내의 itemPrice 에서 에러를 알려줍니다.

price는 string | number 값이지만 itemPrice 는 number 타입으로만 지정되어 있기 때문입니다.

이러한 에러를 없애기 위해서는 조건문과 typeof 연산자 를 사용해서 작성해주어야 합니다.

 

type StrOrNum = string | number;
let itemPrice: number;

const setItemPrice = (price: StrOrNum): void => {

	if(typeof price === 'string') {
    	itemPrice = 0;
    } 	else {
    	itemPrice = price;
    }
};

setItemPrice(50);

위와 같이 조건에 맞추어 작성해 줄 수 있습니다.

 

 

 

선택적 매개 변수와 기본 매개변수

타입스크립트는 함수에 정의된 모든 매개 변수가 함수에 필요하다고 가정합니다.

매개변수와 argument 의 수가 일치하지 않으면 에러 메세지를 나타냅니다.

여기서 필요한 개념이 바로 선택적 매개 변수 Oprional Parameter 입니다.

위에서 optional type을 지정해주었을 때 물음표 ( ? ) 를 사용했던 것처럼 작성하면 됩니다.

선택적 매개변수를 사용할 시 유의할 점은 매개변수에 들어올 값이 많을 때 필수적 매개변수 뒤에 선택적 매개변수를 위치시켜야 한다는 점입니다.

 

선택적 매개 변수를 사용해서 값을 반환할 경우, 선택적으로 들어가지 않은 값은 undefined 로 반환됩니다.

이 부분을 다른 기본 값으로 넣어주기 위한 개념이 기본 매개 변수입니다.

 

function sayHi (message: string, userName = 'there'): void {
	console.log(`${message}, ${userName}`);
}

sayHi('Hello');

//Hello, there 이 출력됩니다.

userName 매개변수 옆에 기본 매개 변수로 there을 작성해주었습니다.

기본 매개 변수를 지정해 줄 경우에는 타입스크립트가 타입 추론을 할 수 있기 때문에 타입을 지정해주지 않아도 되며 Optional Parameter로 굳이 물음표를 사용하지 않아도 됩니다.

 

 

 

클래스와 객체의 연관성

 

 

OOP 객체 지향언어에서의 Class 는 객체를 만들어 내기 위한 설계도, 생산틀을 말합니다.

객체 지향 프로그래밍은 여러가지 변수와 함수들로 구성된 코드들에서 연관된 변수와 함수를 한 덩어리로 묶어서 구조화하여 표현한 프로그래밍 스타일을 말합니다. 객체의 개념을 사용하여 코드를 작성하는 것입니다.

실제 연관되어 있는 변수와 함수를 객체와 같은 단위로 쪼개고 객체들이 서로 상호 작용함으로서 시스템이 동작됩니다.

 

이렇게 Class를 이용해 붕어빵 틀이 여러 가지 붕어빵을 만들어내듯 여러 가지 객체를 만들어낼 수 있습니다.

객체는 클래스를 통해 만들어 질 수 있습니다.

 

class Baby {
	fullName: string;
    age: number;
    gender: string;
    birth: number;


  printBabyDetails = (): void => {
      console.log(`${this.fullName}는 현재 ${this.age} 이고 ${this.gender} 이며 
      ${this.birth} 에 생일이다.`)
  }

}

클래스 내에 정의된 변수 : 프로퍼티

클래스 내에 정의된 함수 : 메소드

 

이 와 같이 Baby 라는 클래스를 생성하였으며, 클래스를 통해서 객체를 생성할 때 클래스의 새로운 인스턴스를 만들어낸다고 표현합니다.

 

class Baby {
	fullName: string;
    age: number;
    gender: string;
    birth: number;


  printBabyDetails = (): void => {
      console.log(`${this.fullName}는 현재 ${this.age} 이고 ${this.gender} 이며 
      ${this.birth} 에 생일이다.`)
  }

}



let Baby1 = new Baby();

생성자 함수를 이용해 새로운 인스턴스를 생성시킬 수 있습니다.

 

 

아직 프로퍼티의 값이 할당되지 않았기 때문에 아래와 같이 값을 할당해주어야 합니다.

 

let Baby1 = new Baby();
Baby1.fullName = 'angel';
Baby1.age = 4;
Baby1.gender = 'male';
Baby1.birth = '2017.07.03';
Baby1.printBabyDetails();