JavaScript에는 타입을 명시하지 않아도 코드작성에 큰 무리가 없다.
그로인해 원하는 타입이 아닌 다른 타입의 데이터가 입력됨에 따른 에러를 핸들링하는데 어려움을 가질 때가 있다.
이를 보안하기 위한 js에 타입명시가 추가된 상위호환인 TypeScript 기술을 공부하려 한다.
해당 내용은 땅콩코딩 - YouTube 를 참고하여 학습을 진행하였다.
먼저 ts를 사용하기 위해 npm을 사용해 설치를 해주어야한다.
npm -i typescript
설치한 후에는 .ts 파일을 만들고 저장한다.
// app.ts function logName (name: string) { console.log(name); } logName('jack');
ts파일을 js로 컴파일 하는 작업이 필요한데 해당 명령어를 사용하여 컴파일을 진행한다.
tsc app.ts
해당 명령어를 작성하면 app.js라는 파일이 생성됨을 알 수 있다.
// app.js "use strict"; function logName(name) { console.log(name); } logName('jack');
컴파일을 진행하고 ts파일을 보게되면 함수 이름에 경고 표시가 되어있는데,
이는 VScode의 버그같은 것으로, 다음 명령어를 작성하여 ts의 환경설정 파일을 생성하면 된다.
tsc -init
매번 ts파일을 작성하고 컴파일을 해줘야하는 번거러움이 있다.
다음 명령어는 nodemon과 같이 실시간으로 ts파일의 변동이 있다면 컴파일을 해주게 된다.
tsc -w app.ts
ts에서 타입을 정의 해주지 않아도 할당된 데이터의 타입을 확인하여 타입을 지정해 준다.
let student = { name: 'Jake', course: 'Getting Started with TypeScript', codingIQ: 80, code: function () { console.log('brain is working hard'); } };
타입추론으로 인해student.name
의 타입이 문자열로 지정되었기 때문에 문자열이 아닌 타입을 집어 넣게 된다면 컴파일 오류가 발생한다.
student.name = 100;
ts에서는 각 변수 및 함수등 선언된 모든 곳에 타입을 지정/선언을 할 수 있다.
let studentID: number = 12345; let studentName: string = 'Hanbin Lee'; let age: number = 21; let gender: string = 'male'; let subject: string = 'Javascript'; let courseCompleted: boolean = false; function getStudentDetails (studentID: number){ return { studentID: 12345, studentName: 'Hanbin Lee', age: 21, gender: 'male', subject: 'Javascript', courseCompleted: true }; }
함수 자체에 대한 타입도 지정할 수 있는데, 해당 함수의 리턴값이 void한지 아닌지에 대해 지정해 줄 수 있다.
function getStudentDetails (studentID: number) : object { // or 'void' or 'any' return { studentID: 12345, studentName: 'Hanbin Lee', age: 21, gender: 'male', subject: 'Javascript', courseCompleted: true }; }
함수의 리턴값에 대해서도 타입지정이 가능하다.
function getStudentDetails (studentID: number) : { studentID: number; studentName: string; age: number; gender: string; subject: string; createdDate: Date; } { return { studentID: 12345, studentName: 'Hanbin Lee', age: 21, gender: 'male', subject: 'Javascript', courseCompleted: true }; }
매번 타입을 지정하는 것은 매우 비효율적이다. 코드의 재사용을 위한 방법으로 인터페이스가 있다.
interface
를 사용하여 타입지정을 변수에 할당하듯 사용하면 된다.
인터페이스의 변수 이름은 첫 글자를 대문자로 하여 짓는다.
interface Student { studentID: number; studentName: string; age: number; gender: string; subject: string; courseCompleted: boolean; } function getStudentDetails (studentID: number) : Student { return { studentID: 12345, studentName: 'Hanbin Lee', age: 21, gender: 'male', subject: 'Javascript', courseCompleted: true }; }
인터페이스를 타입으로 가지는 값은 인터페이스 값의 구조에 따라야 한다. 구조에 따르지 않는다면 오류를 발생시킨다.
이를 유연하게 하기 위한 방법으로,
인터페이스의 프로퍼티를 옵셔널 속성으로 정의하고 싶다면 ?
를 해당 프로퍼티 뒤에 입력해주면된다.
리턴값에 해당 프로퍼티값이 없더라도 오류를 발생 시키지 않는다.
interface Student { studentID: number; studentName: string; age?: number; gender: string; subject: string; courseCompleted: boolean; } function getStudentDetails (studentID: number) : Student { return { studentID: 12345, studentName: 'Hanbin Lee', // age: 21, gender: 'male', subject: 'Javascript', courseCompleted: true }; }
인터페이스에 메소드를 정의해 줄 수 있다.
방법으로는 두가지 방법이 있다.
interface Student { addComment (comment: string): string; addComment: (comment: string) => string; }
인터페이스의 프로퍼티로 생성된 값을 수정이 되지 않게 바꾸는 방법이 있다.
readonly
를 원하는 프로퍼티 앞에 입력해주면 된다.
interface Student { readonly studentID: number; studentName: string; age?: number; gender: string; subject: string; courseCompleted: boolean; }
인터페이스는 ts로 작성된 코드가 js로 컴파일 될 때, 지워지기 때문에 js코드에서는 나타나지 않는다. 이는 코드가 렌더링될 때 아무런 영양을 끼치지 않고, 오직 ts를 작성할 때 코드에 대한 정보만 제공하는 존재이다.
해당 프로퍼티를 열거형 타입으로 지정해 줄 수 있다.
인터페이스처럼 enum
을 사용하여 타입을 지정하여 사용하면 된다.
// app.ts enum GenderType { Male, Female } interface Student1 { gender: GenderType; } function getStudentDetails (studentID: number): Student1 { return { gender: GenderType.Male }; }
// app.js var GenderType; (function (GenderType) { GenderType[GenderType["Male"] = 0] = "Male"; GenderType[GenderType["Female"] = 1] = "Female"; })(GenderType || (GenderType = {})); function getStudentDetails(studentID) { return { gender: GenderType.Male }; }
enum
은 인터페이스와 다르게 js코드에도 코드가 존재하는데, 이는 코드가 런타임에 존재하는 실제 객체이기 때문이다.
enum
을 작성하고 컴파일을 하면 선언된 순서에 따라 js코드에서 순차적으로 증가하는 숫자로 할당된다.
숫자가 아닌 문자형으로 할당되기를 원한다면 enum
을 선언할 때, string enum
을 사용하면 된다.
// app.ts enum GenderType { Male = 'male', Female = 'female' }
// app.js var GenderType; (function (GenderType) { GenderType["Male"] = "male"; GenderType["Female"] = "female"; })(GenderType || (GenderType = {})); function getStudentDetails(studentID) { return { gender: GenderType.Male }; }
더욱 간단한 방법으로 열거형타입을 지정해 줄 수 있다.
따로 enum
을 사용하지 않고 원하는 프로퍼티에 파이프라인을 입력 해주면 된다.
interface Student1 { gender: 'male' | 'female'; } function getStudentDetails (studentID: number): Student1 { return { gender: 'female' }; }
// app.js function getStudentDetails(studentID) { return { gender: 'female' }; }
당연하게도 enum
객체를 생성하지 않았기 때문에 js코드가 더욱 가독성이 좋아진 것을 확인할 수 있다.
타입을 any
로 지정할 경우. 해당 변수에 어떤 타입이와도 오류없이 사용할 수 있다.
let someValue: any = 5; someValue = 'hello'; someValue = true;
하지만, 타입스크립트에서는 타입에 관한 많은 정보를 명시할수록 개발자의 의도에 정확한 코드를 작성할 수 있다.
따라서, 동적데이터를 받는 상황에서 데이터의 타입이 정확하지 않은 등 특별한 이유가 있지 않다면 사용을 지양해야한다.
변수의 타입이 한 가지가 아닌, 두가지 이상의 타입이 들어온다면 파이프라인을 사용하여 Union 타입으로 선언해주면 된다.
let price: number | string = 4000; price = 'free';
만일 union 타입을 여러번 사용해야한다면 타입의 재사용을 위해 변수형태로 새로운 타입을 선언한 뒤 사용하면된다.
type NumOrStr = number | string; let price: NumOrStr = 4000; price = 'free';
아래 예제와 같이 물품의 가격을 지정해주는 함수를 생성하고 실행한다면 타입오류를 발생시킨다.
type NumOrStr = number | string; let itemPrice: number; const setItemPrice = (price: NumOrStr) : void => { itemPrice = price; } setItemPrice(5000); // type error.
itemPrice
변수의 타입은 숫자이고 함수에 들어가는 인자의 타입은 union 타입으로 지정했기 때문인데,
이를 해결하는 방법은 typeof
를 사용하여 타입에 따른 분기를 해주면 된다.
이처럼 유니언타입을 사용할 때 코드 검증을 수행하는것을 타입가드(Type Guard)
라고 한다.
typeof
외 다른 방법으로도 코드 검증을 수행할 수 있다.
Type Guard - Reference
type NumOrStr = number | string; let itemPrice: number; const setItemPrice = (price: NumOrStr) : void => { if(typeof price === 'string'){ itemPrice = 0; }else{ itemPrice = price; } } setItemPrice(5000); // type error.
타입스크립트에서는 함수의 매개변수(parameter)와 리턴값의 타입을 명시해 줄 수 있다.
function sendGreeting (message: string, userName: string) : void { console.log(`${message}, ${userName}`); } sendGreeting('Hello', 'Mr.Lee');
함수에 들어갈 두번째 인자(argument)를 생략하고자 한다면,
?
를 사용하여 생략하고자 하는 매개변수를 선택적 매개변수로 지정해주면 된다.
function sendGreeting (message: string, userName?: string) : void { console.log(`${message}, ${userName}`); } sendGreeting('Hello');
하지만, 함수에서 두 번째 인자가 생략된다면 값이 없기 때문에 함수를 실행할 경우
Hello, undefined
라는 결과물이 나온다.
따라서, 이를 방지하기 위해 기본 매개변수(Default Parameter)를 사용하여 기본값을 지정해줘야 한다.
function sendGreeting (message: string, userName = 'there') : void { console.log(`${message}, ${userName}`); } sendGreeting('Hello'); // Hello, there
기본값을 지정해줄 때 지정값의 타입을 생략하는 이유는,
타입스크립의 타입추론으로 인해 기본값의 타입을 추론하여 타입을 알아서 지정해주기 때문에 생략이 가능하다.
타입스크립트는 자바스크립트를 상위호환하기 때문에 js의 화살표 함수 또한 사용할 수 있다.
const sendGreeting = (message: string, userName = 'there') : void => { console.log(`${message}, ${userName}`); } sendGreeting('Hello'); // Hello, there
ts에서도 객체지향적으로 프로그래밍이 가능하다.
비슷한 특성의 객체를 각각 만들기에는 효율적인 유지보수에 도움이 되지 않는다.
클라스를 사용하여 비슷한 특성의 객체를 쉽게 만들 수 있다.
let fullName: string; let age: number; let jobTitle: string; let hourlyRate: number; let workingHoursPerWeek: number; let printEmployeeDatails = (fullName: string, jobTitle: string, hourlyRate: number, workingHoursPerWeek: number) : void => { console.log(`${fullName}의 직업은 ${jobTitle} 이고 일주일의 수입은 ${hourlyRate * workingHoursPerWeek} 달러 이다.`); };
클라스형식으로 작성한다면...
class Employee { fullName: string; age: number; jobTitle = '주니어 개발자'; hourlyRate: number; workingHoursPerWeek: number; printEmployeeDatails = (): void => { console.log(`${this.fullName}의 직업은 ${this.jobTitle} 이고 일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러 이다.`); } }
해당 클라스를 사용하여 여러 객체를 생성할 수 있다.
let employee1 = new Employee(); employee1.fullName = 'Hanbin Lee'; employee1.age = 21; employee1.hourlyRate = 40; employee1.workingHoursPerWeek = 35; employee1.printEmployeeDatails(); // Hanbin Lee의 직업은 주니어 개발자 이고 일주일의 수입은 1400 달러 이다. let employee2 = new Employee(); employee2.fullName = 'Coding Kim'; employee2.age = 23; employee2.hourlyRate = 40; employee2.workingHoursPerWeek = 40; employee2.printEmployeeDatails(); // Coding Kim의 직업은 주니어 개발자 이고 일주일의 수입은 1600 달러 이다.
constructor
를 사용하여 객체를 생성할 때 초기값을 지정해 준다.
class Employee { fullName: string; age: number; jobTitle = '주니어 개발자'; hourlyRate: number; workingHoursPerWeek: number; constructor(fullName: string, age: number, hourlyRate: number, workingHoursPerWeek: number){ this.fullName = fullName; this.age = age; this.hourlyRate = hourlyRate; this.workingHoursPerWeek = workingHoursPerWeek; } printEmployeeDatails = (): void => { console.log(`${this.fullName}의 직업은 ${this.jobTitle} 이고 일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러 이다.`); } }
클라스를 통해 객채를 생성할 때
constructor
에 정의된 매개변수의 값의 맞게 전달인자가 입력 되어야 한다.
let employee1 = new Employee('Hanbin Lee', 21, 40, 35); employee1.printEmployeeDatails(); // Hanbin Lee의 직업은 주니어 개발자 이고 일주일의 수입은 1400 달러 이다.
코드의 예상치 못한 버그와 안정성을 높이기 위해 클라스 외부로 부터 생성된 객채의 프라퍼티의 접근을 제한하고 싶을 때,
public
private
protected
을 사용할 수 있다.
사용방법은 원하는 프로퍼티 및 메소드 명 앞에 작성해주면 된다.
class Employee { public fullName: string; private _age: number; jobTitle = '주니어 개발자'; hourlyRate: number; workingHoursPerWeek: number; ... }
public
은 클라스의 외부에서도 접근이 가능하게 한다.
접근 제한자를 지정하지 않는다면 public
이 기본으로 적용된다. 따라서 따로 지정하지 않아도 된다.
private
은 클라스 내에서만 접근이 가능하게 한다. 따라서 클라스 외부에서는 접근이 불가능 하다.
클라스를 사용하여 객체를 생성한 후
private
로 지정된 프라퍼티는 클라스 외부에서는 수정 및 호출이 불가능하다.
private
로 선언할 때 네이밍 룰로 해당 변수앞에 _
언더스코어를 붙힌다.
protected
는 클라스 내부와 상속받은 자식 클라스에서 접근이 가능하게 한다. private
와 마찬가지로 클라스 외부에서는 접근이 불가능 하다.
클라스의 프로퍼티를 private
으로 지정해도 get
과 set
을 사용하면 클라스 외부에서 접근할 수 있다.
class Employee { private _fullName: string; age: number; jobTitle = '주니어 개발자'; hourlyRate: number; workingHoursPerWeek: number; constructor(fullName: string, age: number, hourlyRate: number, workingHoursPerWeek: number){ this._fullName = fullName; this.age = age; this.hourlyRate = hourlyRate; this.workingHoursPerWeek = workingHoursPerWeek; } get fullName () { return this._fullName; } set fullName (value: string) { this._fullName = value; } printEmployeeDatails = (): void => { console.log(`${this._fullName}의 직업은 ${this.jobTitle} 이고 일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러 이다.`); } }
let employee1 = new Employee('Hanbin Lee', 21, 40, 35); console.log(employee1.fullName); // Hanbin Lee employee1.fullName = 'Coding Kim'; console.log(employee1.fullName); // Coding Kim
constructor
내에서 access modifiers
를 바로 지정하여 코드를 더욱 간결하게 만들 수 있다.
class Employee { constructor( private _fullName: string, private _age: number, private _hourlyRate: number, public workingHoursPerWeek: number, public jobTitle = '주니어 개발자'){ } get fullName () { return this._fullName; } set fullName (value: string) { this._fullName = value; } printEmployeeDatails = (): void => { console.log(`${this._fullName}의 직업은 ${this.jobTitle} 이고 일주일의 수입은 ${this._hourlyRate * this.workingHoursPerWeek} 달러 이다.`); } }