코로 넘어져도 헤딩만 하면 그만

Typescript 맛봄 (2) 본문

CODE STATES 44

Typescript 맛봄 (2)

꼬드리 2023. 5. 31. 16:48

 

1. 열거형Enum

2. 인터페이스Interface

3. 타입 별칭Aliases

4. 타입 추론Inference

5. 클래스Class


🚩열거형

흔히 Enum이라고 부르는 열거형에 대해서 알아봅시다. JS에는 없는 기능인데, TS에는 문자형/숫자형 열거형을 지원하고 있습니다. 그래서 열거형이 무엇이냐... 하면 아래와 같은 모습을 하고 있죠.

숫자형 열거형의 예시입니다. 

enum Color {
  Red,
  Green,
  Blue,
}

//기본적으로 0부터 시작해서 1씩 증가, 하지만 다음과 같이 수를 지정할 수도 있다.

enum Color {
  Red = 1,
  Green = 2,
  Blue = 4,
}

//아래처럼 산술 계산도 가능합니다.

enum Color {
  Red = 1,
  Green = 2,
  Blue = 4,
}

let c: Color = Color.Green;
let greenValue: number = Color.Green;
let blueValue: number = Color.Blue;

console.log(c);          // 출력: 2
console.log(greenValue);  // 출력: 2

 

문자형 열거형은 다음과 같은 모습을 취하는데, 주로 외부에서 가져온 값을 다루기 위해 사용됩니다. HTTP 요청 방식도 열거형으로 표현할 수 있습니다. 열거형은 코드의 가독성을 높이고 오타 같은 실수를 방지하기 위해 씁니다. 숫자형보다 명확한 값이 나와 읽기 편합니다.

 

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

let myDirection: Direction = Direction.Up;
console.log(myDirection); // 출력: "UP"

//아래와 같이 HTTP를 가능
enum HttpMethod {
  Get = "GET",
  Post = "POST",
  Put = "PUT",
  Delete = "DELETE",
}

function makeRequest(url: string, method: HttpMethod) {
  // ...
}

makeRequest("/api/data", HttpMethod.Post);

문자열과 숫자열 이넘을 섞어서 사용하는 것은 가능은 하지만, 추천되고 있진 않습니다. 가능하다면 숫자는 숫자만 문자는 문자만 사용하는 걸로 합니다. 

 

 

+역 매핑

숫자형 열거형에만 존재하는 특징입니다. key로 값을 얻을 수 있고, 값으로 key를 얻을 수 있죠. 

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

 

🚩인터페이스

인터페이스는 보통 객체의 구조를 정의하기 위해 사용합니다.

TS의 예약어인 interface를 사용하여 TypeScript 인터페이스를 생성할 수 있다고 해요. 아래처럼 인터페이스를 사용해서 변수를 선언한다면, 인터페이스에 정의를 해준 만큼 써주어야 에러가 나지 않습니다. 순서는 상관 없고요. 

interface User {
	name: string;
	age: number;
}

// 정상적으로 선언됩니다.
const user: User = {
	name: "anna",
	age: 20
}

// 프로퍼티의 순서를 지키지 않아도 정상적으로 선언됩니다.
const user: User = {
	age: 20,
	name: "anna"
}

// 정의된 프로퍼티보다 적게 작성했기 때문에 에러가 납니다.
const user: User = {
	name: "anna"
}
interface User {
	name: string;
	age?: number;
}

// 정상적으로 선언됩니다.
const user: User = {
	name: "anna"
}

//이렇게 ?를 쓰면 들어오지 않아도 에러가 나지 않는다.
interface User {
	name: string;
	age: number;
	job: string;
}

interface Greeting {
	(user: User, greeting: string): string;
}

//함수형 인터페이스와 섞어 쓰는 예시

const greet: Greeting = (user, greeting) => {
	return `${greeting}, ${user.name}! Your job : ${user.job}.`;
}

const user: User = {
	name: "anna",
	age: 30,
	job: "developer"
};

const message = greet(user, "Hi");

console.log(message);

또한 인터페이스는 extends라는 단어를 써서 기존 존재하던 인터페이스를 상속받고, 확장 가능합니다. 훨씬 재사용성을 높이는 방향이 되겠죠.

interface Person {
    name: string;
    age: number;
}

interface Developer extends Person {
    language: string;
}

const person: Developer = {
    language: "TypeScript",
    age: 20,
    name: "Anna",
}
interface FoodStuff {
    name: string;
}

interface FoodAmount {
    amount: number;
}

interface FoodFreshness extends FoodStuff, FoodAmount {
	   isFreshed: boolean;
}
//이런 식으로 여러 인터페이스를 상속 받을 수도 있다.

 

🚩타입 별칭

타입 별칭(Type Aliases)은 새로운 이름으로 기존의 타입을 참조하는 것을 말하며 type을 사용해서 작성합니다.

type Person = {
  id: number;
  name: string;
  email: string;
}

//Commentary 인터페이스에서 Person 타입을 참조하고 있습니다.
interface Commentary {
  id: number;
  content: string;
  user: Person;
}

//객체에서 Commentary 인터페이스를 참조하고 있습니다.
let comment1: Commentary = {
    id: 1,
    content: "뭐예요?",
    user: {
        id: 1,
        name: "김코딩",
        email: "kimcoding@codestates.com",
    },
}

코드를 더 간결하게 만드는 데에 type이 도움을 줍니다. 이렇게 만들어진 타입은 내부에 정의된 프로퍼티를 전부 참조해야 하고, 안 그러면 컴파일 에러가 난답니다.

타입 별칭이 인터페이스와 유사한 기능을 수행할 수는 있지만 둘 사이에는 차이점이 있습니다.

 

 

🎈타입 별칭 vs 인터페이스?
1.사용목적
인터페이스: 객체의 구조 정의할 때
타입 별칭: 다양한 타입을 이름(닉네임)참조하려고 

2. 활용성
인터페이스: 객체를 구성하는 프로퍼티나 메서드 명시하고 유지하는데 유용
타입 별칭: 복잡한 타입 단순화. 공통적으로 사용되는 타입 중복 없이 정의

3. ★ 확장 가능성
인터페이스: extends로 상속,확장 가능
타입: type는 extends가 불가능. 정의하는 순간 끝남.
 

 

🚩타입 추론

TypeScript는 타입 추론(Type Inference)이라는 기능을 통해 코드 작성을 도와줍니다. 즉, 변수나 함수의 타입을 선언하지 않아도 자동으로 타입 스크립트가 유추를 해줍니다. 

let isNum = 333;
//타입 스크립트는 number타입이라고 추론한다.

이러한 추론을 통해 타입 스크립트는 추론해야 하는 대상의 모든 후보 타입을 포함할 수 있는 '최적 공통 타입'을 계산하고, 그것을 선택합니다. 또한 a와 b의 타입이 모두 number일 때 a+b라는 반환값도 number일 것이라고 추론합니다.

이처럼 자동으로 타입을 추론해주기 때문에 가독성이 향상되며, 생산성과 오류 발견도 용이해집니다. 다만 추론이 잘못될 경우 오류가 발생할 수 있고 명시적으로 타입 지정이 필요한 경우도 있으므로 잘 살펴보아야 합니다. 

 

🎈타입 단언 : 개발자 측에서 직접 단언해주어 검사를 막는다.
const food : FoodFreshness = {}  이러면 에러가 뜬다. 
const food = {} as FoodFreshness ☞ 타입 단언(이 부분은 개발자가 직접 넘겨주니 컴파일러가 타입 검사를 안 해도 됨)
const food = <FoodFreshness> {};처럼 제네틱으로도 표현 가능 

 

 

🚩클래스

JS에서 지원하는 것과 같이, TS에서도 클래스를 사용해서 객체 생성이 가능합니다. 그러나 TS의 클래스에는 몇 가지 추가된 기능이 존재합니다.

//기존까지 JS로 작성한 class
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${this.age}살 입니다.`);
  }
}

//TS로 작성한 class
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): void {
    console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${this.age}살 입니다.`);
  }
}

TS로 작성한 class가 갖는 차이점은, 우선 클래스를 정의할 때, constructor을 사용해서 초기화하는 것들을 전부 상단에 정의해줘야 한다는 부분입니다. 또 constructor에서 인자로 받을 때에도 타입을 명시해주어야 합니다.

 

클래스는 인터페이스와 마찬가지로, extends로 존재하던 클래스를 상속받아 확장하며 새 클래스를 만들 수 있습니다.

class Animal {
    move(distanceInMeters: number): void {
        console.log(`${distanceInMeters}m 이동했습니다.`);
    }
}

class Dog extends Animal {
    speak(): void {
        console.log("멍멍!");
    }
}

const dog = new Dog();
dog.move(10);
dog.speak();

 

+ 앞에 readonly를 붙여 명시하면, 이 값은 아예 변경할 수 없게 됩니다.

class Mydog {
    readonly name: string;
    constructor(theName: string) {
        this.name = theName;
    }
}
let spooky = new Mydog("Honey");
spooky.name = "HoneyMustard"; // 에러
Comments