타입스크립트란, 기존 자바스크립트 언어에서 타입체크 기능이 추가적으로 구현된 언어이다.
(TS에서 JS는 동작하지만, JS에서 TS는 동작하지 않는다. 즉, JS ⊂ TS 관계이다.)
JS와 TS에 대한 코드 차이를 살펴보자.
//JavaScript
function plus(a,b){
return a + b ;
}
let a = 10 ;
let b = 20 ;
console.log(plus(a,b)) ; // 30
a = '10' ;
b = '20' ;
console.log(plus(a,b)) ; // 1020
일반적인 상황이라면, plus는 숫자를 더하는 함수일 것이다. 하지만, JS에서는 문자열까지 더할 수 있다. 이것은 우리가 원치 않는 결과이지만, 오류를 내지는 않는다.
//TypeScript
function plus ( a : number , b : number ){
return a + b ;
}
let a = 10 ;
let b = 20 ;
console.log(plus(a,b)) ;
a = '10' ; // error -> string 타입은 number 타입에 할당 불가
b = '20' ; // error
console.log(plus(a,b)) ;
TypeScript에서는 변수의 자료형을 명시해주기 때문에, 원하지 않은 결과에 대해서 오류로 나타나게 된다.
TypeScript는 웹브라우저에서 바로 사용할 수는 없고, TS를 컴파일하여 JS코드로 변환(컴파일)한 다음에야 웹브라우저에서 해석할 수 있다.
Prettier - Code formatter(코드 스타일),ESLint (코드 검사, 코드 퀄리티)npm i -g typescript - TS를 JS로 컴파일해주는 컴파일러를 설치한 것.ts 파일 생성이제 tsc 명령어를 통해 TS를 JS로 컴파일이 가능하다.
TS파일을 만드는 것은 간단하다.
확장자를 .ts로 만들기만 하면 된다.
app.ts에 함수를 하나 만들어보자.
function myFunc(name : string){
console.log(name) ;
}
myFunc("myName") ;
string 자료형을 매개변수로 받는 함수이다.
이제 이 파일을 실행시켜보자.
TS를 컴파일 하는 명령어는 tsc이다.
터미널에 tsc app.ts를 입력해보면, app.js라는 파일이 생성된다.
//app.js
function myFunc(name) {
console.log(name);
}
myFunc("myName");
이렇게 생성된 app.js파일을 node app.js를 통해 실행하면 된다.
코드를 살펴보면, 타입스크립트에서 명시했던 데이터 타입( name : string )들이 사라진 것을 볼 수 있다.
이 예시에서 볼 수 있듯이, TS의 역할은 개발자의 실수를 줄이고 코드 퀄리티를 높이는 역할일 뿐, 근본은 JS로 실행되는 것이다.
우리는, TS의 설정 초기값을 만들어줄 수 있다.
터미널에 tsc --init를 입력하면, tsconfig.json이 생성되고, 필요한 설정값들을 주석을 풀어서 사용하면 된다.
우리가 타입스크립트를 이용하면서, .ts 파일의 코드를 수정했다면, tsc를 이용해 계속해서 다시 컴파일해줘야 한다.
이러한 번거로움을 해결하기 위해, tsc -w를 이용한다. w는 watch를 의미하고, 계속 파일을 지켜본다는 의미이다.
위 예시의 경우에는, 터미널에서 tsc -w app.ts로 입력하면 되겠다. 이 후에, 해당 터미널에서 작동하면서 코드의 변화에 따라 자동으로 app.js 파일로 컴파일 해준다.
다시 짚고 넘어가자면, TS에서 string 으로 초기화된 변수에, int 자료형을 재할당 할 수 없다.
let myName = "Lee" ;
myName = 1 ; // Error
즉, 초기화 단계에서 할당된 값을 기준으로, myName이라는 변수의 자료형을 추론하는 것이다.
물론 타입추론을 활용해서 써도 괜찮지만, 타입 추론이 모호한 경우가 발생할 때에는, 타입을 명시하는 것이 좋다. 그냥, 되도록이면 타입을 명시해서 사용하는 습관을 들이자.
property들에 대해서도 마찬가지이다.
let student = {
name : 'kim',
course : '1',
score : 100,
grade : function (){
console.log('A')
}
}
student.name = "lee" ;
student.course = '2' ;
student.score = 50 ;
student.grade = function (){
console.log('C') ;
}
student.grade() ; // C
이제, TS에서 사용할 수 있는 데이터타입들에 대해 알아보자.
TS의 데이터타입은 총 3가지로 나눌 수 있다.
number, string, boolean, null, undefinedobject, array, tupleany, unknownTS는 콜론을 이용하여 데이터타입을 지정할 수 있다.
let a : string = "kim" ;
이제 a라는 변수에는 string을 제외한 다른 자료형을 사용할 수 없다.
또한, TS에서는 함수에 대한 return 타입까지 지정해줄 수 있다.
function plus(a: number, b: number): number {
return a + b;
}
function plusAll(a: any, b: any) {
return a + b;
}
console.log(plus(1, 2)); // 3
console.log(plusAll("1", "2")); // "12"
물론, 함수가 아무것도 return 하지 않게 하려면, void 타입의 사용도 가능하다.
또한, 특이하게도 중괄호{}를 통해 사용자가 지정한 타입의 사용도 가능하다. 다음의 예시를 보자.
function getInfo(name: string): {
id: number;
name: string;
age: number;
} {
console.log("getInfo 함수 실행")
return { id: 1, name: "lee", age: 3 };
}
이 코드에서는, 데이터타입을 id(숫자형),name(문자형), age(숫자형) 의 객체를 return 하는 function이다.
이런 방식으로, 사용자가 객체를 정의해서 데이터타입으로 사용할 수 있다.
그런데, 이런식으로 작성하면 객체 내의 타입들이 많아졌을 때 코드가 지저분해질 것이다.
이를 인터페이스를 이용해서 해결한다.
인터페이스는 interface 키워드를 이용한다.
또한 TS에서 interface의 이름에 I를 붙이는 네이밍 컨벤션은 없다.
다음의 코드를 보자.
interface Student {
id: number;
name: string;
age: number;
}
이제 Student라는 타입이 생성된 것과 같다.
이제 이 타입을 사용해서, 위에서 생성했던 function을 다시 정의해보자.
function getInfo(name: string): Student {
console.log("getInfo 함수 실행")
return { id: 1, name: "lee", age: 3 };
}
함수의 return 타입을 Student 타입으로 지정을 했기 때문에, 타입에 맞는 값을 return하지 않거나, 특정 프로퍼티를 빠뜨리면 에러가 발생한다.
만약 내가, 특정 프로퍼티는 있어도 되고, 없어도 되게 만들고 싶다면 어떻게 해야할까?
이 때는, 선택적 프로퍼티를 사용한다. 사용방법은 다음과 같다.
interface Student {
id: number;
name: string;
age?: number;
}
이제 age의 값은 function의 return값에 있어도 되고, 없어도 되는 프로퍼티가 된다.
당연히, function의 매개변수에도 이 optional 기호 ? 를 사용 가능하다.
function plus(a: number, b?: number): number {
return a + (b !== undefined ? b : 1);
}
다만, 선택적 매개변수 기호 ?와 default값을 동시에 설정할 수 없다. (function plus(a? :number = 1 ) (x)) 이 점을 주의하자.
인터페이스에서, 각 프로퍼티들에 자료형을 지정해줄 수 있는 것처럼, 인터페이스의 메소드들에도 어떤 타입을 return할지 지정해줄 수 있다.
interface Student {
id: number;
name: string;
age: number;
setName : (name : string) => void ;
getName : () => string ;
}
즉, 인터페이스에 대해 정리하면 다음과 같다.
1. 인터페이스는 string, number 처럼 데이터 타입처럼 사용이 가능하다.
2. 선택적 프로퍼티를 사용할 수 있고, 이 때, 기호는 ? 를 사용한다.
3. 인터페이스 안에 메소드를 선언 가능하다. ( 단, 인터페이스를 상속받는 클래스는 반드시 그 메소드에 대해 오버라이딩을 해야한다.)
4. 인터페이스를 클래스에 implements할 수 있다.
다음의 상황을 생각해보자.
interface Student{
name : string ;
gender? : string ;
}
gender는 string으로 지정되어 있긴 하지만, 실제로 받는 값은 male과 female 둘 중 하나일 것이다.
따라서, 이 경우에 값에 제한을 두기 위해 열거형을 사용할 수 있다.
enum GenderType{
male = 0 ,
female
}
interface Student{
name : string ;
gender? : GenderType ;
}
class myStudent implements Student {
name: string;
gender?: GenderType;
constructor(name: string, gender?: GenderType) {
this.name = name;
this.gender = gender;
}
}
let a = new myStudent("lee" , GenderType.male) ;
따라서, 새로운 객체를 만든다면, gender 프로퍼티에는 GenderType.male , GenderType.female 과 같이 사용하면 된다.