Typescript

타입스크립트 입문! 메모장

변기원 2022. 6. 29. 20:42

Typescript는 Javascript의 Superset이다.

자바스크립트가 있는데 타입스크립트는 왜 만들어졌는가?

자바스크립트는 동적 타입언어이다. 말은 좋아 보이지만 타입 안정성이 보장되지 않는다는 뜻.

함수로 인자를 보내서 1+2를 하고 싶었는데 "1"+"2"가 들어가면 자바스크립트가 3이 아니라 12로 만들어준다.

이런 유연함이 편하긴 하지만 실제 서비스에서는 독이 된다.

"1"과 "2"를 더하면 차라리 런타임 이전에 에러를 보여주는 게 개발자에게 더 좋을 것이다.

 

자바스크립트는 동적타입언어이기 때문에 타입이 런타임에서 결정된다. 즉, 타입에러가 런타임에서 발생한다.

런타임에 에러가 발생하면 프로그램자체가 멈춰버릴 수 있다. 

타입스크립트는 정적타입언어이다. 즉 컴파일과정에 타입이 다 결정된다. 런타임에서 발생되던 그 에러를

컴파일과정에서 잡아낼 수 있다. 

개발자가 개발단계에서 에러처리를 할 수 있기때문에 훨씬 안정적이다.

 

이 글은 일단 타입스크립트를 배우기 위한 메모장으로 사용하겠습니다.

개발환경설정

$ npm install -g typescript
$ tsc app.ts
$ tsc --init
$ tsc -w app.ts

타입추론

//app.ts

let a=1;
a='hello'

에러 발생: Type ${string} is not assignable to type 'number'

a를 재할당 하기 전에 변수의 타입을 number로 추론했기 때문.

타입스크립트는 타입 표기가 없는 경우 이렇게 타입을 유추해낼 수 있다.

 

타입명시

let name:string = 'YSKim' ; // 작동 코드
let studentId:number = '201511796' ; // 타입 에러 발생
let name: 201511796; // 타입 에러 발생
let name:string = 'YSKim';
let studentId:number = 201511796;
let group:string = 'H&B'

function getStudentInfo1(studentId: number) 
: object {   // 객체를 반환한다고 타입 명시 (문제는 생기지 않지만 타입명시를 더 해주는 것이 좋음)
    return null
}

function getStudentInfo2(studentId: number) : { 
   name:string;
   studentId:number;
   group:string;
}{   // 정확히 어떤 객체를 반환하는 지에 대해 구체적인 타입 명시
    return null;
}

getStudentInfo1()보다는 getStudentInfo2()가 좋다.

getStudentInfo1()도 컴파일상 문제는 없지만 객체가 가진 타입을 명시하는 게 더 좋다. 

 

 

인터페이스(Interface)

interface Student { // 인터페이스 선언 (대문자로 시작)
   name:string;  
   studentId:number;
   group:string;
}

function getStudentInfo2(studentId: number) : Student // 인터페이스를 통해 간단하게 타입 명시
{   
    return {
      name: 'YSKim',
      studentId: 201511796,
      group: 'H&B',    
    };  // 반환되는 값들은 type이 올발라야 합니다.
}

인터페이스 타입이 여러 객체에서 재사용 가능해짐

인터페이스(Interface) Optional

타입을 명시했는데 값을 보내지 않으면 에러 발생 :

Property 'group' is missing in type '{}' but required in type 'Student'.

interface Student { // 인터페이스 선언 (대문자로 시작)
   name:string;  
   studentId:number;
   group?:string;   // group에 optional을 부여
}

물음표 쓰면 된다. 

 

인터페이스 응용

let student1:Student={   // 학생 생성
 name: 'SYKim',
 studentId: 20125,
 group: 'M&S', 
}

function saveStudentInfo (student:Student) //인터페이스를 사용하여 argument 선언이 편리해짐
:void {  //해당 함수는 반환값이 존재하지 않으므로 type은 void가 됩니다.
}

saveStudentInfo(student1) //생성한 학생을 saveStudentInfo의 argument로 대입

인터페이스 read-only property

interface Student { 
   name:string;  
   readonly studentId:number; //studentId를 readonly로 선언
   group:string;
}

function saveStudentInfo (student:Student) 
:void {  
    student.studentId= 100100100; //에러 발생
}

studentId는 read-only property를 가지고 있어서 할당할 수 없다는 에러 발생

 

열거형 (enum)

interface Singer:{
     readonly Id:number,
     name:string,
     company:string,
     age?:number,
     gender: string,
     genre: string,
}

위와 같은 인터페이스가 있을 때, gender는 "Female" or "Male"밖에 없겠지. 값을 이 두 가지로 제한하고 싶을 때 사용한다.

enum GenderType{   // 열거형 선언
    Male,
    Female
}

interface Singer:{
     readonly Id: number,
     name:string,
     company:string,
     age?:number,
     gender: GenderType,   // string이 아닌 GenderType이라는 custom type을 넣어줌
     genre: string,
}

function getSingerDetails(Id:number):Singer{
   return{
       Id: 2222,
       name:'ksy',
       company:'M&S'
       age:26,
       gender: GenderType.Female,    // 'Female'이라고 입력할 시 에러 발생
       genre:'Jazz'
   }
}

위와 같이 열거형을 선언하여 지정할 수 있다.

이때 컴파일된 js 파일을 보면

var GenderType;
(function (GenderType) {
    GenderType[GenderType["Male"]=0]="Male";
    GenderType[GenderType["Female"]=0]="Female";
})(GenderType || (GenderType={}));

이렇게 되어있다.

Male이 0이고 Female은 1이다. 이것을 숫자열거형 이라고 한다. 열거형의 디폴트는 숫자 열거형이다.

만약 숫자 열거형이 아니라 문자 열거형을 사용하고 싶다면?

enum GenderType{   // 열거형 선언
    Male='male',
    Female='female'
}
// app.js

var GenderType;
(function (GenderType) {
    GenderType["Male"]="male";
    GenderType["Female"]="Female";
})(GenderType || (GenderType={}));

이렇게 변경된다.

숫자 열거형은 GenderType에 속하는 값이 늘어날 때 자동적으로 고유의 숫자 값을 부여하는 기능을 사용할 수 있지만

문자 열거형은 불가능하다. 대신 가독성이 좋다.

 

리터럴타입

interface Singer:{
     readonly Id:number,
     name:string,
     company:string,
     age?:number,
     gender: 'male' | 'female',    
     genre: string,
}

훨씬 간단. 해당 필드 값이 2개일 경우는 열거형보다 리터럴이 간단하다.

// app.ts

function getSingerDetails(Id:number):Singer{
   return{
       Id: 2222,
       name:'ksy',
       company:'M&S'
       age:26,
       gender: 'female',    
       genre:'Jazz'
   }
}

any Type

// app.ts

let value: any = 100; //number
value= 'ysKim'; // string
value= true;  // boolean

이러면 왜 타입스크립트를 쓸까? any type이 싫어서 타입스크립트를 쓰는 거 아닌가?

any Type은 작업 중인 코드의 타입 명시가 매우 어려운 경우(서트파티 라이브러리에서 동적 콘텐츠를 가지고 와서 프로그램 실행 시에는 변수의 타입을 알 수 없어 타입 지정이 어려운 경우) 사용한다.

이해는 잘 안 되지만 any라는 것을 알고 있다면 분명히 이슈가 발생했을 때 떠오를 것이다.

Union Type

// app.ts

let value: number | string

Type Aliases

union type이 반복되면 코드 가독성이 안 좋다. 이때 type aliases를 사용한다.

// app.ts

let value: number | string;
let value2: number | string;
let value3: number | string;

이것을

// app.ts

type=StrOrNum = number | string;

let value: StrOrNum;
let value2: StrOrNum;
let value3: StrOrNum;

이런 식으로 쓸 수 있다.

 

타입 가드(Type Guard)

function sample(data: number | string) : void {
  // data 로 number 타입이 넘어올 수도 있고 string 타입일 수도 있다.
	return data+1 // error
}

data가 string일 수도 있기 때문에 위 코드는 에러가 난다.

이때 타입 가드를 쓴다. 컴파일러가 타입을 예측할 수 있도록 코드를 작성해서 에러가 발생하지 않도록 예방한다.

타입 가드는 typeof, instanceof를 쓴다.

function sample(data: number | string) : void {
  // data 로 number 타입이 넘어올 수도 있고 string 타입일 수도 있다.
  	if(typeof data === 'number'){
    	return data+1 
    }
}

이렇게 구현할 수 있다.

원시타입은 typeof를 사용하면 되고 

클래스 객체는 instanceof를 사용하면 된다.

 

함수 반환 타입 / 매개 변수 반환 타입

//app.ts

function sendMessage(message, userName):void{   // 함수의 반환 타입 명시
   console.log(`&{message},${userName}`);
}

sendMessage('Hello','Swim')

함수의 리턴이 없으면 void라고 명시한다.

//app.ts

function sendMessage(message: string, userName: string):void{   // 함수의 반환 타입 명시
   console.log(`&{message},${userName}`);
}

sendMessage('Hello','Swim')

함수의 매개변수에도 타입을 지정한다.

 

 

선택적 매개변수

//app.ts

function sendMessage(message: string, userName?: string):void{   // 물음표 사용
   console.log(`&{message},${userName}`);
}

sendMessage('Hello')

물음표를 주면 매개변수를 안 보내도 에러가 뜨지 않는다.

주의: 선택적 매개변수는 비선택적 매개변수보다 항상 뒤에 온다.

위의 결과는 

Hello, undefined입니다.

보기 안 좋다.

기본 매개 변수

//app.ts

function sendMessage(message: string, userName='Hmm'):void{   // 물음표 사용
   console.log(`&{message},${userName}`);
}

sendMessage('Hello')

userName을 안보내면 Hmm이 들어간다.

기본 매개변수의 역할을 생각해보면 선택적 매개변수 코드나 타입을 명시할 필요가 없어진다.

기본적으로 저 값이 들어간다는 뜻은 이미 저 변수가 선택적이라는 뜻을 내포하기 때문.

타입을 지정하지 않아도 된다는 뜻은 타입 추론과 관련이 있다. 디폴트 값이 string으로 들어갔기 때문에 string타입이 적용된다.

 

 

Class를 통해 객체 만들어보기

// app.ts

let name: string;
let age: number;
let singTitle: string;
let albumCount:number;

let printSingerInfo = (name: string, age: number, singTitle: string, albumCount: number): void => {
   console.log(`${name}은 ${age}살이고, 현재까지 ${albumCount}개의 앨범을 냈습니다. 최근  ${name}가 낸 대표곡의 이름은 ${singTitle}이라는 노래입니다.`)
}

위 코드를 클래스를 활용해서 가독성이 더 좋게 만들어보자

class Singer{
  name: string;
  age: number;
  singTitle: string;
  albumCount:number;

  printSingerInfo = (): void => {
     console.log(`${this.name}은 ${this.age}살이고, 현재까지 ${this.albumCount}개의 앨범을 냈습니다. 최근  ${this.name}가 낸 대표곡의 이름은 ${this.singTitle}이라는 노래입니다.`)
  }
}

짧고 가독성도 좋다.

class내부에서는 let, const 등의 변수 선언 명시가 필요하지 않다.

class 내부에 정의된 변수를 프로퍼티라고 한다.

반가운 this를 드디어 활용해본다. this덕분에 매개변수를 쓸 필요도 없어졌다.

class 내부에 정의된 함수를 메서드라고 한다.