데브코스

[11주차 - DAY3] 리터럴, 클래스

미안하다 강림이 좀 늦었다 2024. 5. 8. 15:25

 

 

리터럴 타입

특정 값을 나타내는 타입으로, 해당 값이 정확하게 일치해야 한다.

리터럴 타입은 코드의 가독성을 높이고, 잘못된 값이 들어노는 것을 막을 수 있게 해 준다.

문자형, 숫자형, 불리언형, 객체형이 있다.

 

문자열 리터럴 타입

result에는 'success'라는 문자열과 'error'라는 문자열만 들어갈 수 있다. line 3처럼 다른 값을 넣으면 아래 사진과 같은 에러가 발생한다. 숫자형과 불리언형도 동일하게 사용하면 된다.

let result: 'success' | 'error';
result = 'success'; // 허용
result = 'pending'; // 에러

 

객체 리터럴 타입

객체 리터럴은 객체의 속성명뿐만 아니라 그 값 또한 일치해야 한다. line 3처럼 'Lee'와 30이 아닌 다른 값을 넣으면 에러가 발생한다.

let person: {name: 'Lee', age: 30};
person = {name: 'Lee', age: 30}; // 허용
person = {name: 'Choi', age: 40}; // 에러

 

 

타입 별칭(Type Alias)

반복되는 코드를 재사용하기 위해 타입을 재정의 하는 것을 말한다.

 

동서남북 중 하나의 값을 가지는 두 변수 direction1, direction2가 있다고 가정해 보자. 그러면 아래와 같이 리터럴 타입 부분이 중복된다.

let direction1: 'North' | 'East' | 'South' | 'West';
direction1 = 'North';

let direction2: 'North' | 'East' | 'South' | 'West';
direction2 = 'East';

타입 별칭을 이용해 중복되는 코드를 없애보자.

다음 코드와 같이 CardinalDirection이라는 타입을 정의해서 사용한다. 이 CardinalDirection이 타입 별칭이다.

type CardinalDirection = 'North' | 'East' | 'South' | 'West';
let direction1: CardinalDirection = 'North';
let direction2: CardinalDirection = 'East';

 

 

유니온 타입

제한된 타입을 동시에 지정하고 싶을 때 사용한다.

다음 코드에서 anyValue 변수에는 문자형과 숫자형의 값을 담을 수 있다.

let anyValue: number | string;
anyValue = 100;
anyValue = 'abc';

 

함수의 파라미터에도 유니온 타입을 사용할 수 있다. 

let numStr: number | string = 100;

function convertToString(value: number | string) :string {
    return String(value);
}

function convertToNumber(value: number | string): number {
    return Number(value);
}

하지만 동일한 유니온 타입 코드가 반복되고 있다. 따라서 다음과 같이 타입 별칭을 사용할 수 있다.

type strOrNum = number | string;
let numStr: strOrNum = 100;

function convertToString(value: strOrNum) :string {
    return String(value);
}

function convertToNumber(value: strOrNum): number {
    return Number(value);
}

 

 

타입 가드

유니온 타입의 경우 대입하는 과정에서 오류가 발생할 수 있기 때문에 typeof 연산자를 사용하여 타입 검증을 수행하는 것을 말한다.

 

아래와 같은 타입과 변수들이 있다.

type strOrNum = number | string;
let numStr2: strOrNum = 100;
let item: number;

여기서 item 변수에 numStr의 값을 담는다고 해보자.

지금은 numStr이 숫자형이라서 문제가 발생하지 않지만 numStr은 문자형일 수도 있고, 그럴 경우 오류가 발생한다..

item = numStr // ?

따라서 다음과 같이 typeof 연산자를 사용하여 타입 검증을 수행하여 값을 담는다. 이것을 타입 가드라고 한다.

if(typeof numStr2 === 'number') {
    item = numStr2;
}

 

 

Array와 Tuple

Array

배열은 길이가 가변적이고, 동일한 타입의 요소로 구성된다.

배열의 타입은 다음과 같이 지정할 수 있다.

let numbers: number[] = [1, 2, 3, 4, 5];
let fruits: string[] = ['apple', 'banana', 'orange'];

 

유니온 타입으로도 다음과 같이 지정할 수 있다.

let mixedArray: (number | string)[] = [1, 'two', 3, 'four'];

 

별도의 타입을 지정하지 않아도 타입 추론으로 자료형이 결정되기는 한다. 아래 사진에서 number형 배열로 지정된 것을 확인할 수 있다.

let infer = [1, 2, 3];

 

읽기만 가능한 배열은 다음과 같이 선언할 수 있다.

let readOnlyArray: ReadonlyArray<number> = [1, 2, 3];

 

Tuple

튜플은 길이가 고정적이고, 각 요소의 타입이 정해져 있다.

다음과 같이 [] 안에 각 요소의 타입을 순서대로 작성하면 된다.

let greeting: [number, string, boolean] = [1, 'hello', true];

 

 

클래스

클래스는 객체의 설계도, 생산 틀 같은 개념이고, 객체는 클래스의 실체이다. 예로 표현하자면 클래스는 쿠키 틀, 객체는 그 틀로 찍어내서 만든 쿠키 같은 것이다.

클래스는 다음과 같이 선언한다. 멤버 변수를 속성, 프로퍼티라고도 하며, 멤버 함수는 메서드라고 부른다.

 class 클래스이름 {
     // 멤버 변수
     // ...
     
     // 멤버 함수
     // ...
 }

 

접근 지정자

접근 지정자에는 세 종류가 있다.

접근 지정자 설명
public 외부에서도 직접 접근할 수 있다.
private 그 클래스의 내부에서만 접근할 수 있다.
protected 상속 관계일 때만 접근할 수 있다.

 

멤버 변수는 보통 public으로 선언하지 않는다. private인 멤버 변수 앞에는 언더바(_)를 붙이는 규칙이 있다. 참고로 아무 접근 지정자도 적지 않으면 기본 값이 public이기 때문에 public으로 설정된다.

class Employee {
    private _empName: string;
    private _age: number;
    private _empJob: string;
}

 

생성자

다른 언어에서는 보통 생성자는 클래스의 이름과 동일하지만 타입스크립트에서는 constructor()가 생성자다.

class Employee {
    private _empName: string;
    private _age: number;
    private _empJob: string;

    // 생성자
    constructor(empName: string, age: number, empJob: string) { 
        this._empName = empName; // this는 자기 자신을 나타냄
        this._age = age;
        this._empJob = empJob
    }
}

객체는 다음과 같이 생성한다. 객체의 생성과 동시에 생성자가 호출된다. 위 코드의 생성자 동작에서 볼 수 있듯이 파라미터의 값들이 멤버 변수에 저장된다.

const emp1 = new Employee('lee', 20, 'developer');

 

생성자를 저렇게 작성해도 오류가 발생하지는 않지만 멤버 변수를 선언하는 부분과 중복되는 코드가 많다. 그래서 다음 코드와 같이 수정하면 멤버 변수의 선언과 초기화를 동시에 수행할 수 있다.

class Employee {
    constructor(
        private _empName: string, 
        private _age: number, 
        private _empJob: string
    ) {}
}

 

getter

멤버 변수가 private로 지정되어 있기 때문에 지금 상태에서는 멤버 변수의 값을 가져올 수 없다. 외부에서 멤버 변수에 직접 접근하는 건 캡슐화에 어긋나기 때문에 getter라는 메서드를 이용해 외부에서 멤버 변수의 값을 받을 수 있도록 해야 한다.

타입스크립트에서는 getter를 get 키워드를 이용하여 다음과 같이 사용한다. this는 지금 이 객체를 가리키는 키워드이다.

class Employee {
    constructor(
        private _empName: string, 
        private _age: number, 
        private _empJob: string
    ) {}

    get empName() {
        return this._empName;
    }
}

const emp = new Employee('lee', 20, 'engineer');
console.log(emp.empName); // output: lee

 

setter

외부에서 멤버 변수의 값을 수정하려면 setter을 사용해야 한다.

타입스크립트에서는 setter을 다음과 같이 set 키워드를 사용하여 구현한다.

class Employee {
    constructor(
        private _empName: string, 
        private _age: number, 
        private _empJob: string
    ) {}

    get empName() {
        return this._empName;
    }

    set empName(value: string) {
        this._empName = value;
    }
}

const emp2 = new Employee('lee', 20, 'engineer');
emp2.empName = 'choi';
console.log(emp2.empName); // output: choi

 

 

배운 점

  • 리터럴 타입을 사용하면 코드의 반복을 줄여 가독성이 높아지고, 잘못된 값이 들어오는 것을 방지할 수 있다.
  • 타입 별칭은 사용자가 타입을 정의하여 반복되는 코드를 재사용할 수 있게 해 준다.
  • 유니온 타입을 사용하면 제한된 타입을 동시에 지정할 수 있다.
  • 유니온 타입을 사용하는 경우 다른 변수에 대입하는 과정에서 오류가 발생할 수 있으므로 typeof 연산자를 사용하여 타입 검증을 수행해야 한다.(타입 가드)
  • 타입스크립트의 getter와 setter은 각각 get 키워드와 set 키워드로 구현한다.