타입스크립트 입문! 메모장
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 내부에 정의된 함수를 메서드라고 한다.