TIL_TypeScript

Hanbin Lee·2021년 7월 31일
0

TIL

목록 보기
1/3
post-thumbnail

JavaScript에는 타입을 명시하지 않아도 코드작성에 큰 무리가 없다.
그로인해 원하는 타입이 아닌 다른 타입의 데이터가 입력됨에 따른 에러를 핸들링하는데 어려움을 가질 때가 있다.
이를 보안하기 위한 js에 타입명시가 추가된 상위호환인 TypeScript 기술을 공부하려 한다.

해당 내용은 땅콩코딩 - YouTube 를 참고하여 학습을 진행하였다.


Get Start

먼저 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


Type inference(타입추론)

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를 사용하여 타입지정을 변수에 할당하듯 사용하면 된다.
인터페이스의 변수 이름은 첫 글자를 대문자로 하여 짓는다.

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

해당 프로퍼티를 열거형 타입으로 지정해 줄 수 있다.
인터페이스처럼 enum을 사용하여 타입을 지정하여 사용하면 된다.

Numeric 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을 사용하면 된다.

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
    };
}

리터럴 타입(Literal Type)

더욱 간단한 방법으로 열거형타입을 지정해 줄 수 있다.
따로 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 타입

타입을 any로 지정할 경우. 해당 변수에 어떤 타입이와도 오류없이 사용할 수 있다.

let someValue: any = 5;
someValue = 'hello';
someValue = true;

하지만, 타입스크립트에서는 타입에 관한 많은 정보를 명시할수록 개발자의 의도에 정확한 코드를 작성할 수 있다.
따라서, 동적데이터를 받는 상황에서 데이터의 타입이 정확하지 않은 등 특별한 이유가 있지 않다면 사용을 지양해야한다.


Union 타입

변수의 타입이 한 가지가 아닌, 두가지 이상의 타입이 들어온다면 파이프라인을 사용하여 Union 타입으로 선언해주면 된다.

let price: number | string = 4000;
price = 'free';

Type Aliases

만일 union 타입을 여러번 사용해야한다면 타입의 재사용을 위해 변수형태로 새로운 타입을 선언한 뒤 사용하면된다.

type NumOrStr = number | string;

let price: NumOrStr = 4000;
price = 'free';

Type Guard

아래 예제와 같이 물품의 가격을 지정해주는 함수를 생성하고 실행한다면 타입오류를 발생시킨다.

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.

TS Function

타입스크립트에서는 함수의 매개변수(parameter)와 리턴값의 타입을 명시해 줄 수 있다.

function sendGreeting (message: string, userName: string)
: void {
  console.log(`${message}, ${userName}`);
}

sendGreeting('Hello', 'Mr.Lee');

선택적 매개변수(Optional Parameter)

함수에 들어갈 두번째 인자(argument)를 생략하고자 한다면,
?를 사용하여 생략하고자 하는 매개변수를 선택적 매개변수로 지정해주면 된다.

function sendGreeting (message: string, userName?: string)
: void {
  console.log(`${message}, ${userName}`);
}

sendGreeting('Hello');

기본 매개변수(Default Parameter)

하지만, 함수에서 두 번째 인자가 생략된다면 값이 없기 때문에 함수를 실행할 경우
Hello, undefined 라는 결과물이 나온다.
따라서, 이를 방지하기 위해 기본 매개변수(Default Parameter)를 사용하여 기본값을 지정해줘야 한다.

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

sendGreeting('Hello');

// Hello, there

기본값을 지정해줄 때 지정값의 타입을 생략하는 이유는,
타입스크립의 타입추론으로 인해 기본값의 타입을 추론하여 타입을 알아서 지정해주기 때문에 생략이 가능하다.

화살표 함수(Arrow Function)

타입스크립트는 자바스크립트를 상위호환하기 때문에 js의 화살표 함수 또한 사용할 수 있다.

const sendGreeting = (message: string, userName = 'there')
: void => {
  console.log(`${message}, ${userName}`);
}

sendGreeting('Hello');
// Hello, there

객체지향 프로그래밍(OOP)

ts에서도 객체지향적으로 프로그래밍이 가능하다.

Class

비슷한 특성의 객체를 각각 만들기에는 효율적인 유지보수에 도움이 되지 않는다.
클라스를 사용하여 비슷한 특성의 객체를 쉽게 만들 수 있다.

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

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 달러 이다.

접근 제한자(Access Modifiers)

코드의 예상치 못한 버그와 안정성을 높이기 위해 클라스 외부로 부터 생성된 객채의 프라퍼티의 접근을 제한하고 싶을 때,
public private protected 을 사용할 수 있다.
사용방법은 원하는 프로퍼티 및 메소드 명 앞에 작성해주면 된다.

class Employee {
  public fullName: string;
  private _age: number;
  jobTitle = '주니어 개발자';
  hourlyRate: number;
  workingHoursPerWeek: number;
  ...
}

Public

public은 클라스의 외부에서도 접근이 가능하게 한다.
접근 제한자를 지정하지 않는다면 public이 기본으로 적용된다. 따라서 따로 지정하지 않아도 된다.

Private

private은 클라스 내에서만 접근이 가능하게 한다. 따라서 클라스 외부에서는 접근이 불가능 하다.

클라스를 사용하여 객체를 생성한 후 private로 지정된 프라퍼티는 클라스 외부에서는 수정 및 호출이 불가능하다.

private로 선언할 때 네이밍 룰로 해당 변수앞에 _언더스코어를 붙힌다.

Protected

protected는 클라스 내부와 상속받은 자식 클라스에서 접근이 가능하게 한다. private와 마찬가지로 클라스 외부에서는 접근이 불가능 하다.

Getter & Setter

클라스의 프로퍼티를 private으로 지정해도 getset을 사용하면 클라스 외부에서 접근할 수 있다.

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} 달러 이다.`);
  }
}
profile
안녕하세요 백엔드 개발자 이한빈 입니다 :)

0개의 댓글