[TypeScript] TypeScript 문법2

KIM DA MI·2023년 5월 31일
2

TypeScript

목록 보기
3/3
post-thumbnail

🔵 TypeScript의 열거형(Enum)


  • typeScript의 열거형(Enum)은 특정 값의 집합을 정의할 때 사용된다.
  • JavaScript에서는 기본적으로 열거형을 지원하지 않지만, TypeScript에서는 문자형 열거형과 숫자형 열거형을 지원한다.
  • TypeScript에서 열거형은 다음과 같은 형태로 정의할 수 있다.
    enum Color {
      Red,
      Green,
      Blue,
    }
    • 위 예제에서는 Color라는 열거형을 정의하고 있다.
      열거형의 값은 Red, Green, Blue 세 개이다.

🔹 숫자형 열거형

  • 열거형은 숫자형과 문자열형, 혹은 이 둘의 조합으로 정의될 수 있다.
    디폴트 값으로 숫자형을 사용하며, 각 값은 자동으로 0부터 시작하여 1씩 증가한다.
    그러나 다음과 같이 수동으로 값을 지정할 수도 있다.

    enum Color {
      Red = 1,
      Green = 2,
      Blue = 4,
    }
    • 위 예제에서는 Red1, Green2, Blue4로 정의되었다.
  • 열거형의 값에 대해 산술 연산을 수행할 수도 있다.

    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
    console.log(blueValue);   // 출력: 4
    • 열거형은 일반적으로 상수값을 대신하여 사용되므로, 타입스크립트에서는 열거형이 많이 사용된다.
      열거형은 코드를 더욱 가독성 높게 만들어주고, 오타와 같은 실수를 방지해준다.

🔹 문자형 열거형

  • 문자형 열거형은 앞에서 살펴본 숫자형 열거형과 개념적으로는 거의 비슷하다.
    문자형 열거형은 열거형의 값을 전부 다 특정 문자 또는 다른 열거형 값으로 초기화해야 한다.

    enum Direction {
      Up = "UP",
      Down = "DOWN",
      Left = "LEFT",
      Right = "RIGHT",
    }
    
    let myDirection: Direction = Direction.Up;
    console.log(myDirection); // 출력: "UP"
    • Direction이라는 문자열 기반의 열거형(Enum)을 정의
    • Up, Down, Left, Right 각각에는 문자열 값이 할당
    • myDirection 변수를 Direction.Up으로 초기화
    • 출력 결과 : "UP"
  • 문자형 열거형에는 숫자형 열거형과는 다르게 auto-incrementing이 없어 디버깅을 할 때 숫자형 열거형의 값은 가끔 불명확하게 나올 때가 있지만 문자형 열거형은 항상 명확한 값이 나와 읽기 편하다.
  • 문자열 기반의 열거형은 주로 외부에서 가져온 값을 TypeScript에서 다루기 위해서 사용된다.
    예를 들어, HTTP 요청 방식을 나타내는 열거형을 정의할 수 있다.

    enum HttpMethod {
      Get = "GET",
      Post = "POST",
      Put = "PUT",
      Delete = "DELETE",
    }
    
    function makeRequest(url: string, method: HttpMethod) {
      // ...
    }
    
    makeRequest("/api/data", HttpMethod.Post);
    • HTTP 요청 방식을 나타내는 HttpMethod 열거형을 정의
    • makeRequest 함수는 URL과 HTTP 요청 방식을 인자로 받음.
    • HTTP 요청 방식을 지정할 때 HttpMethod.Post와 같이 열거형 값을 사용
  • 이렇게 열거형을 사용하면 오타와 같은 실수를 방지할 수 있으며, 코드의 가독성과 안정성을 높일 수 있다.

🔹 역 매핑 (Reverse mappings)

  • 역 매핑은 숫자형 열거형에만 존재하는 특징이다.
  • 열거형의 키(key)로 값(value)을 얻을 수 있고 값(value)으로 키(key)를 얻을 수도 있다.
    enum Enum {
        A
    }
    let a = Enum.A;
    let nameOfA = Enum[a]; // "A"
    • 위와 같이 열거형의 키로 값을 얻을 수 있지만, 값으로도 열거형의 키를 얻을 수 있다.
      이는 숫자형 열거형에만 존재하며, 문자형 열거형에서는 존재하지 않는 특징이다.



🔵 TypeScript의 인터페이스(Interface)


🔹 변수와 인터페이스

  • TypeScript에서 변수를 선언할 때 인터페이스를 아래와 같이 사용할 수 있다.
    TypeScript에서 인터페이스는 객체(Object)의 구조를 정의하기 위해 주로 사용되는 예약어이다.

    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"
    }
    
    // 정의된 프로퍼티보다 많이 작성했기 때문에 에러가 난다.
    const user: User = {
        name: "anna",
        age: 20,
        job: "developer"
    }
    • 위의 코드는 사용자 정보를 정의하기 위해 interface 예약어를 사용하여 User 인터페이스를 만들었다.

    • 인터페이스를 만들 때 예약어를 작성하고, 인터페이스의 이름을 대문자로 작성한다.

    • 이렇게 인터페이스의 이름을 대문자로 시작하는 것은 네이밍 컨벤션이다.

      네이밍 컨벤션(Naming Convention) :

      • 이름을 짓는 일종의 관례. TypeScript로 개발할 때 대부분의 개발자는 인터페이스의 이름을 대문자로 시작하도록 작성한다. 인터페이스는 객체의 타입을 정의하고, 객체가 대문자로 시작하는 것과 유사하기 때문에 일관성 있는 코드 작성을 위해 이러한 관례를 따른다.
    • 인터페이스 내에는 nameage가 정의되어 있기 때문에, User 인터페이스를 사용하여 변수를 선언할 때는 반드시 정의된 프로퍼티를 전부 작성해야 한다.

    • 또한 interface로 정의된 속성만 지정할 수 있으며, 그 외 프로퍼티를 추가로 작성하고자 해도 인터페이스 내에 정의되어 있지 않기 때문에 추가로 프로퍼티를 더 작성하여 선언할 수 없다.

  • 그러나 인터페이스 안의 모든 프로퍼티가 필요한 것은 아니며, 어떤 조건에서만 존재하거나 아예 없을 수도 있기 때문에 ? 연산자를 사용하여 선택적 프로퍼티를 작성할 수도 있다.

    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);
    • 위의 코드는 User 인터페이스 외에도 Greeting 인터페이스를 추가로 작성하여 함수 타입을 정의했다.

    • Greeting 인터페이스는 User 타입과 문자열 타입을 매개변수로 받아 문자열 타입을 반환한다.

    • greet 함수는 Greeting을 사용하여 구현되었으며, user 객체와 문자열 "Hi"를 전달인자로 전달받아 문자열을 반환한다.

    • Greeting 인터페이스에서 이미 greet의 매개 변수인 usergreeting의 타입과 반환 타입이 작성되어 있기 때문에, greet 함수는 string 타입을 반환한다고 명시하지 않아도 되며, 매개 변수의 타입 또한 작성하지 않아도 된다.


🔹 클래스와 인터페이스

  • 클래스에서도 인터페이스를 사용할 수 있다. 클래스에서는 인터페이스를 아래와 같이 사용한다.

    interface Calculator {
        add(x: number, y: number): number;
        substract(x: number, y: number): number;
    }
    
    class SimpleCalculator implements Calculator {
        add(x: number, y:number) {
            return x + y;
        }
    
        substract(x: number, y: number) {
            return x - y;
        }
    }
    
    const caculator = new SimpleCalculator();
    
    console.log(caculator.add(4, 9)); //13
    console.log(caculator.substract(10, 5)); //5
    • 위의 코드에서 Calculator 인터페이스는 addsubstract 메서드를 정의하고 있고, SimpleCaculator 클래스는 Calculator 인터페이스를 사용하여 작성되었다.

    • Caculator 인터페이스를 사용하고 있기 때문에 SimpleCaculator 클래스 내에는 Calculator 인터페이스 내에 정의된 두 메서드를 반드시 작성해야 한다.

    • 또 클래스를 구현할 때 인터페이스에서 정의된 함수나 메서드의 매개변수 타입과 반환 값과 일치하도록 구현해야 하므로, 클래스 내부에서 해당 메서드의 매개변수 타입을 다시 한번 더 명시해 주지 않으면 컴파일 에러가 발생하게 된다.



🔵 TypeScript의 타입 별칭(Type Aliases)


  • 타입 별칭(Type Aliases)은 타입의 새로운 이름을 만드는 것이다.
    이는 새로운 이름으로 기존의 타입을 참조하는 것을 의미한다.
  • 타입 별칭을 이용하여 타입의 새로운 이름을 만들 때 키워드 type을 사용하여 작성한다.

    type MyString = string;
    
    let str1: string = 'hello!';
    
    // string 타입처럼 사용할 수 있다.
    let str2: MyString = 'hello world!';
    • 타입을 정의할 수 있는 모든 곳에는 타입 별칭을 쓸 수 있다.
  • 이런 방식으로 타입 별칭을 사용하면 코드를 더 간결하고 가독성 좋게 만들 수 있다.
    또한 복잡한 타입을 간략하게 표현하고, 타입 정의를 재사용하는 등 가독성을 높일 수 있다.

    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",
        },
    }
    
    // Commentary 인터페이스 내부에 content 프로퍼티가 존재하기 때문에 
    // content 프로퍼티를 작성하지 않으면 컴파일 에러가 난다.
    let kimcoding: Commentary = {
        id: 1,
        user: {
            id: 1,
            name: "김코딩",
            email: "kimcoding@codestates.com",
        },
    };
    
    // Person 타입 내부에 isDeveloper 프로퍼티가 존재하지 않기 때문에 
    // isDeveloper 프로퍼티를 작성할 시 컴파일 에러가 난다.
    let kimcoding: Commentary = {
        id: 1,
        content: "뭐예요?",
        user: {
            id: 1,
            name: "김코딩",
            email: "kimcoding@codestates.com",
            isDeveloper: true,
        },
    };
    • 이처럼 인터페이스나 다른 변수를 정의할 때 타입 별칭으로 정의한 타입을 참조하게 됨으로써 코드를 더 간결하고 가독성 좋게 만들 수 있다.
    • 타입 별칭으로 만들어진 타입을 참조할 시에는 인터페이스와 마찬가지로 내부에 정의된 프로퍼티를 전부 참조해야만 한다.

🔹 인터페이스 vs 타입 별칭

  • 타입 별칭 또한 인터페이스와 같은 특징이 있기 때문에, 인터페이스의 역할을 타입 별칭이 수행할 수도 있다. 그러나 인터페이스와 타입 별칭에는 미묘한 차이점이 있다.

    type Person = {
        name: string;
        age: number;
    }
    
    interface User {
        name: string;
        age: number;
    }
    
    let kimcoding: Person = {
        name: '김코딩',
        age: 30,
    }
    
    let coding: User = {
        name: '김코딩',
        age: 30,
    }
    • VSCode로 작성 시, kimcoding 객체가 참조하고 있는 Person에 마우스를 올리면 Person 내부에 어떤 프로퍼티들이 정의되어 있는지 보인다.
    • 그러나 coding 객체가 참조하고 있는 User는 내부에 어떤 프로퍼티들이 정의되어 있는지 보이지 않는다.

  • 또 다음과 같은 typeinterface 차이점이 있다.

    type Person = {
        name: string;
        age: number;
    }
    
    interface User {
        name: string;
        age: number;
    }
    
    // 에러가 발생한다.
    type Students extends Person {
        className: string;  
    }
    
    // 정상적으로 동작한다.
    interface Students extends User {
         className: string;   
    }
    
    // 정상적으로 동작한다.
    interface Students extends Person {
        className: string;  
    }
    • 타입 별칭은 말 그대로 타입에 새로운 이름을 부여하는 것에서 그치기 때문에 확장이 되지 않는다.
    • 그러나 인터페이스는 기존의 인터페이스 및 타입 별칭으로 만들어진 타입 둘 다 상속할 수 있기 때문에, 유연한 코드 작성을 위해서는 인터페이스로 만들어서 필요할 때마다 확장할 수 있다.



🔵 TypeScript의 타입 추론(Type Inference)


🔹 타입 추론의 기본

  • 타입 추론(Type Inference)은 변수나 함수의 타입을 선언하지 않아도 TypeScript가 자동으로 유추하는 기능이다.
    let isNumber = 123;
    • 이 코드에서는 변수 isNumber를 선언하고, 숫자 123을 할당했다.
      이 경우, 타입스크립트는 isNumber의 타입을 자동으로 숫자(Number)로 추론한다.

🔹 최적 공통 타입 (Best common type)

  • TypeScript는 여러 표현식에서 타입 추론이 발생할 때, 해당 표현식의 타입을 사용하여 "최적 공통 타입"을 계산한다.
    let x = [0, 1, null];
    • 예제의 x 타입을 추론하려면 각 배열 요소의 타입을 고려해야 힌다.
    • 최적 공통 타입 알고리즘은 각 후보의 타입을 고려하여, 모든 후보의 타입을 포함할 수 있는 타입을 선택한다.

🔹 문맥상의 타이핑(Contextual Typing)

  • 타입스크립트에서 타입을 추론하는 또 하나의 방식은 바로 문맥상으로 타입을 결정하는 것이다.
  • 이 문맥상의 타이핑(타입 결정)은 코드의 위치(문맥)를 기준으로 일어난다.
    function add(a: number, b: number) {
      return a + b;
    }
    • add 함수는 두 개의 매개변수를 받아 더한 값을 반환한다.
    • 만약 매개변수 ab가 모두 숫자(Number) 타입이라면, add 함수의 반환 값도 숫자(Number) 타입으로 추론된다.

🔹 타입 추론의 장점

  • 코드의 가독성 향상: 타입 추론을 사용하면 코드의 가독성이 향상된다.
    명시적으로 타입을 지정하지 않아도 코드에서 변수의 타입을 알 수 있기 때문이다.
  • 개발 생산성 향상: 타입 추론을 사용하면 코드 작성 시간을 단축할 수 있다.
    명시적으로 타입을 지정하지 않아도 TypeScript가 자동으로 타입을 추론하기 때문이다.
  • 오류 발견 용이성: 타입 추론을 사용하면 코드의 오류를 발견하는 것이 쉬워진다.
    TypeScript는 변수나 함수의 타입을 추론하여 타입 검사를 수행하기 때문이다.

🔹 타입 추론의 단점

  • 타입 추론이 잘못될 경우 코드 오류 발생: 타입 추론은 TypeScript가 자동으로 수행하는 것이기 때문에, 추론이 잘못될 경우 코드 오류가 발생할 수 있다.
  • 명시적인 타입 지정이 필요한 경우가 있음: 타입 추론만으로는 부족한 경우가 있다.
    특히, 복잡한 함수나 객체의 경우에는 명시적인 타입 지정이 필요할 수 있다.
    따라서, 타입 추론은 TypeScript의 장점 중 하나이지만, 때에 따라 명시적인 타입 지정이 필요한 경우도 있다.



🔵 TypeScript의 클래스(Class)


🔹 JavaScript에서의 클래스

  • JavaScript에서 클래스는 ES6(ECMAScript 2015)에서 처음 도입되었다.
    클래스를 사용하면 객체를 생성하고 객체의 속성과 메서드를 정의할 수 있다.

    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    
      greet() {
        console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${this.age}살 입니다.`);
      }
    }
    • 위 코드에서 Person 클래스는 nameage 속성을 가지고 greet() 메서드를 정의한다.
      이 클래스를 사용하여 객체를 생성하면 다음과 같다.
      const person = new Person('Alice', 30);
      person.greet(); // "안녕하세요, 제 이름은 Alice이고, 30살 입니다."

🔹 TypeScript에서의 클래스

  • TypeScript의 클래스는 JavaScript의 클래스와 비슷하지만 몇 가지 추가된 기능이 있다.
  • 예를 들어, TypeScript에서는 클래스의 속성과 메서드에 대한 타입을 명시할 수 있다.

    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}살 입니다.`);
      }
    }
    • 위 코드에서 name 속성과 age 속성은 문자열과 숫자 타입으로 정의되어 있다.
    • 이때 TypeScript에서는 클래스를 정의할 때, constructor를 이용하여 초기화하는 멤버들은 전부 상단에서 정의를 해줘야 한다.
    • 또한 contructor 내 인자로 받을 때도 정확히 타입을 명시해 줘야 한다.
  • TypeScript를 사용하여 객체를 생성할 때도 아래와 같이 JavaScript와 비슷한 방식으로 할 수 있다.
    const person = new Person('Alice', 30);
    person.greet(); // "안녕하세요, 제 이름은 Alice이고, 30살 입니다."

🔹 클래스와 상속(Inheritance)

  • TypeScript의 클래스(class)는 인터페이스(interface)와 마찬가지로 기존에 존재하던 클래스를 상속받아 확장하여 새로운 클래스를 만들 수 있다.
    이때도 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();
    • 위의 코드에서 Animal이라는 클래스를 Dog라는 클래스가 상속받고 있다.
    • Dog 클래스는 Animal 클래스로부터 프로퍼티와 메서드를 상속받으며,
      Dog 클래스는 파생 클래스라고도 불리며, 하위클래스(subclasses)라고도 불린다.
      여기서 Animal 클래스는 기초 클래스, 상위클래스(superclasses)라고 불린다.

🔹 public, private 키워드

  • 기본적으로 클래스 내에 선언된 멤버는 외부로 공개되는 것이 디폴트 값이다.
    그러나 공개된다고 명시적으로도 표시해 줄 수 있는데, 바로 public 키워드를 사용하면 된다.

    class Person {
      public name: string;
      public age: number;
    
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    
      greet(): void {
        console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${this.age}살 입니다.`);
      }
    }
  • 혹은 외부에 드러내지 않을 멤버가 있다면 private 키워드로 명시해 주면 된다.

    class Person {
      public name: string;
      private age: number;
    
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    
      greet(): void {
        console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${this.age}살 입니다.`);
      }
    }

🔹 readonly 키워드

  • readonly 키워드를 사용하여 프로퍼티를 읽기 전용으로 만들 수 있다.
    읽기 전용 프로퍼티들은 선언 또는 생성자에서 초기화해야 한다.
    class Mydog {
        readonly name: string;
        constructor(theName: string) {
            this.name = theName;
        }
    }
    let spooky = new Mydog("스푸키");
    spooky.name = "멋진 스푸키"; // 에러
    • 위의 코드는 namereadonly로 명시되어 있기 때문에, 값을 변경할 수 없다.
      이런 식으로 변경되면 안 될 값을 readonly로 명시하여 보호할 수 있다.

1개의 댓글

comment-user-thumbnail
2023년 6월 7일

그 어렵다는 타스 정리 폼 미쳐따 ㄷ ㄷ 멋쪄요 !!!

답글 달기