본문 바로가기

자바스크립트 끄적끄적

타입스크립트 설명

728x90

1. 타입스크립트 개요

 

TypeScript는 JavaScript에 타입을 추가한 슈퍼셋(Superset) 언어입니다. TypeScript는 Microsoft에서 개발하고 관리하며, JavaScript의 기능을 확장하고 개선하여 JavaScript 개발자들이 타입 체크를 수행하고 오류를 더 쉽게 잡을 수 있도록 돕습니다.

TypeScript는 JavaScript와의 호환성이 높으며, TypeScript로 작성된 코드는 JavaScript로 변환할 수 있습니다. 또한 TypeScript는 대부분의 개발 도구에서 지원되며, 코드 에디터에서 타입 체크와 코드 완성 기능을 지원합니다.

TypeScript의 주요 기능은 다음과 같습니다.

 

1-1. 주요 기능

* 타입 시스템(Type System): TypeScript는 JavaScript에 타입을 추가하여 타입 체크를 수행할 수 있습니다. 이를 통해 오류를 빠르게 찾을 수 있으며, 코드의 안정성과 가독성을 높일 수 있습니다.

* 클래스(Class): TypeScript는 JavaScript의 클래스를 확장하고 개선하여, 객체 지향 프로그래밍을 더욱 쉽고 안전하게 수행할 수 있도록 합니다.

* 인터페이스(Interface): TypeScript는 인터페이스를 제공하여, 타입 간의 계약(Contract)을 명시적으로 정의할 수 있습니다. 이를 통해 코드의 가독성과 안정성을 높일 수 있습니다.

* 제네릭(Generic): TypeScript는 제네릭을 지원하여, 타입에 의존적인 코드를 작성할 수 있습니다. 이를 통해 코드의 재사용성과 유연성을 높일 수 있습니다.

* 데코레이터(Decorator): TypeScript는 데코레이터를 지원하여, 클래스, 메서드, 프로퍼티 등에 부가적인 기능을 추가할 수 있습니다.

TypeScript는 JavaScript의 생산성과 안정성을 높이기 위한 언어로, 대규모 프로젝트에서 더욱 유용하게 사용될 수 있습니다.

 


 

주요기능 예시들 들면서 설명해보아요


타입 시스템(Type System)


TypeScript는 JavaScript에 타입을 추가하여 타입 체크를 수행할 수 있습니다. 예를 들어 다음과 같이 함수의 파라미터와 반환값의 타입을 명시할 수 있습니다.

(typescript)

function greet(name: string): string {
  return `Hello, ${name}!`;
}


위 예시에서 name 파라미터와 함수의 반환값의 타입을 각각 string으로 명시하였습니다. 이를 통해 파라미터에 문자열 이외의 값이 들어갔을 때 타입 에러를 발생시킬 수 있습니다.

* 클래스(Class)
TypeScript는 JavaScript의 클래스를 확장하고 개선하여, 객체 지향 프로그래밍을 더욱 쉽고 안전하게 수행할 수 있도록 합니다. 예를 들어 다음과 같이 클래스를 작성할 수 있습니다.


(typescript)

class Person {
  private name: string;
  private age: number;

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

  get fullName(): string {
    return `${this.name} (${this.age})`;
  }

  sayHello(): void {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

 



위 예시에서 Person 클래스를 정의하였습니다. 클래스에는 name, age 프로퍼티와 constructor 메서드, fullName 게터(getter) 메서드, sayHello 메서드가 있습니다.

* 인터페이스(Interface)
TypeScript는 인터페이스를 제공하여, 타입 간의 계약(Contract)을 명시적으로 정의할 수 있습니다. 예를 들어 다음과 같이 인터페이스를 작성할 수 있습니다.

(typescript)

interface Person {
  name: string;
  age: number;
  sayHello(): void;
}


위 예시에서 Person 인터페이스를 정의하였습니다. 인터페이스에는 name, age 프로퍼티와 sayHello 메서드가 있습니다.

* 제네릭(Generic)
TypeScript는 제네릭을 지원하여, 타입에 의존적인 코드를 작성할 수 있습니다. 예를 들어 다음과 같이 제네릭 함수를 작성할 수 있습니다.

(typescript)


Copy code
function identity<T>(arg: T): T {
  return arg;
}


위 예시에서 identity 함수는 파라미터와 반환값의 타입이 동일한 제네릭 타입 T를 사용하고 있습니다. 이를 통해 함수가 받은 인자의 타입과 반환값의 타입이 동일한지를 타입 체크하여 안정성을 높일 수 있습니다.

* 데코레이터(Decorator)

데코레이터(Decorator)는 TypeScript에서 클래스, 메서드, 프로퍼티 등에 추가적인 기능을 부여하기 위한 문법입니다. 데코레이터는 @ 기호를 사용하여 표시하며, 클래스, 메서드, 프로퍼티 앞에 위치합니다.

데코레이터는 다양한 용도로 사용될 수 있습니다. 예를 들어, 다음과 같이 클래스 데코레이터를 사용하여 클래스를 확장할 수 있습니다.

(typescript)


Copy code
function addAuthor(target: any) {
  target.author = 'John Doe';
}

@addAuthor
class Book {
  title: string;

  constructor(title: string) {
    this.title = title;
  }
}

console.log(Book.author); // 출력값: "John Doe"


위 예시에서 addAuthor 함수는 클래스 Book에 author 프로퍼티를 추가하는 데코레이터입니다. @addAuthor 구문을 사용하여 Book 클래스에 addAuthor 데코레이터를 적용하였습니다. 이를 통해 Book 클래스에 author 프로퍼티가 추가되어 출력값이 "John Doe"가 됩니다.

데코레이터는 다양한 용도로 사용되며, 코드의 가독성과 재사용성을 높이는 데 큰 역할을 합니다. 또한 Angular 등의 프레임워크에서도 데코레이터를 활용하여 다양한 기능을 제공합니다.


* 의문 => 데코레이터에서 addAuthor는 그냥 함수인데 어떻게 John Doe가 저장되는거야?


addAuthor 함수는 Book 클래스의 생성자 함수를 인자로 받고, Book 클래스의 author 프로퍼티에 "John Doe" 값을 할당하고 있습니다. 이 때 target 인자는 데코레이터가 적용된 클래스 생성자 함수 자체를 나타내며, 해당 클래스 생성자 함수를 이용하여 클래스에 추가적인 프로퍼티나 메서드를 추가할 수 있습니다.

즉, 데코레이터 함수는 적용된 대상에 대한 정보를 가진 객체(target)를 인자로 받아, 해당 대상에 추가적인 기능을 부여하는 역할을 합니다. 따라서 addAuthor 데코레이터가 적용된 Book 클래스를 생성하면, Book 클래스의 생성자 함수가 addAuthor 함수의 인자로 전달되고, addAuthor 함수 내부에서 target.author를 통해 Book 클래스의 author 프로퍼티에 "John Doe" 값을 할당하는 것입니다.





* 의문2

function addAuthor(target: any) {
  target.author = 'John Doe';
}

를 데코레이터를 이용해서 추가하지말고 정석적인 방법으로 추가하면 어떤 모양의 코드가 되지?

 


데코레이터 없이 클래스에 author 프로퍼티를 추가하는 방법은 다음과 같다

typescript

class Book {
  title: string;
  author: string;

  constructor(title: string) {
    this.title = title;
    this.author = 'John Doe';
  }
}

console.log(new Book('Typescript 101').author); // 출력값: "John Doe"


위 코드에서 Book 클래스의 생성자 함수에서 author 프로퍼티를 초기화하여 "John Doe" 값을 할당하는 것으로, 클래스 인스턴스를 생성할 때마다 author 프로퍼티에 기본값으로 "John Doe"를 설정할 수 있습니다. 클래스 인스턴스의 author 프로퍼티 값을 출력하면 "John Doe"가 나타납니다.






* 의문 constructor는 정확히 머지?


constructor는 클래스의 생성자 함수를 정의하는 특별한 메서드입니다. 클래스 인스턴스를 만들 때 자동으로 실행되며, 클래스 인스턴스를 초기화하기 위한 작업을 수행합니다.

constructor 메서드는 클래스의 인스턴스 프로퍼티를 초기화하거나 인스턴스 메서드를 정의하는 등의 작업을 할 수 있습니다. 이 메서드는 클래스 내부에 하나만 존재할 수 있으며, 반드시 constructor라는 이름으로 정의되어야 합니다.

예를 들어, 다음과 같이 Person 클래스에서 name과 age 프로퍼티를 초기화하고, greet 메서드를 정의하는 constructor 메서드를 작성할 수 있습니다.

typescript

class Person {
  name: string;
  age: number;

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

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const person = new Person('John Doe', 30);


person.greet(); // 출력값: "Hello, my name is John Doe and I am 30 years old."
위 예시에서 Person 클래스의 constructor 메서드는 name과 age 인자를 받아서 클래스 인스턴스의 name과 age 프로퍼티를 초기화하는 역할을 합니다. greet 메서드는 name과 age 프로퍼티를 사용하여 인사말을 출력하는 역할을 합니다. person 객체를 생성하고, greet 메서드를 호출하여 결과를 출력합니다.



* 의문 자바의 constructor와 같다고 생각하면 될까?


TypeScript의 constructor는 Java의 constructor와 비슷한 개념입니다. Java에서도 클래스를 생성할 때 new 연산자와 함께 constructor를 호출하여 객체를 초기화하고 반환하는 방식으로 사용합니다.

TypeScript에서 constructor는 클래스의 생성자 함수를 정의하는 역할을 하며, Java에서와 마찬가지로 클래스 인스턴스를 생성할 때 자동으로 호출되어 인스턴스 초기화 작업을 수행합니다. 따라서 constructor는 클래스의 인스턴스 생성 시점에 중요한 역할을 합니다.

하지만 TypeScript는 JavaScript의 확장된 언어이기 때문에 Java와는 다른 문법과 기능이 있습니다. 따라서 Java와 TypeScript의 constructor가 완전히 동일하다고는 볼 수 없습니다. 그러나 클래스 인스턴스의 초기화를 수행하는 역할에서는 유사한 개념으로 생각할 수 있습니다.


* 인터페이스(Interface) 를 쓰는 예시 코드좀?


TypeScript에서 인터페이스(Interface)는 데이터 타입을 정의하는데 사용됩니다. 인터페이스는 클래스, 함수, 객체 등의 타입을 지정할 수 있습니다.

다음은 인터페이스를 사용하여 객체의 타입을 정의하는 예시 코드입니다.

typescript

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

function printPersonInfo(person: Person) {
  console.log(`Name: ${person.name}, Age: ${person.age}`);
}

const john: Person = { name: 'John Doe', age: 30 };


printPersonInfo(john); // 출력값: "Name: John Doe, Age: 30"


위 코드에서 Person 인터페이스는 name과 age 프로퍼티를 가진 객체의 타입을 정의합니다. printPersonInfo 함수는 Person 타입의 인자를 받아서 이름과 나이를 출력합니다. john 객체는 Person 타입을 준수하는 객체로 생성되었으며, printPersonInfo 함수에 전달하여 출력합니다.

인터페이스를 사용하면 코드의 가독성과 유지보수성이 향상됩니다. 인터페이스를 사용하면 함수와 객체의 타입을 명확히 정의할 수 있으며, 개발자가 의도하지 않은 타입 오류를 방지할 수 있습니다.


 


제네릭(Generic) 코드 예시 좀


제네릭(Generic)은 TypeScript에서 함수, 클래스, 인터페이스 등에서 타입 안정성을 유지하는 방법입니다. 제네릭을 사용하면 여러 종류의 데이터 타입에 대해 동일한 코드를 작성할 수 있으며, 코드 재사용성과 유연성을 높일 수 있습니다.

다음은 제네릭을 사용하여 배열에서 최대값을 찾는 함수를 구현한 예시 코드입니다.

typescript

function findMax<T>(arr: T[]): T | undefined {
  if (arr.length === 0) return undefined;
  let max = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
      max = arr[i];
    }
  }
  return max;
}

const numbers = [1, 2, 3, 4, 5];


const maxNumber = findMax(numbers); // 타입 추론에 의해 maxNumber의 타입은 number | undefined가 됩니다.

const strings = ['apple', 'banana', 'orange'];
const maxString = findMax(strings); // 타입 추론에 의해 maxString의 타입은 string | undefined가 됩니다.
위 코드에서 findMax 함수는 T라는 제네릭 타입 변수를 사용합니다. 함수의 인자로 배열을 받아서 최대값을 찾아 반환합니다. 제네릭 타입 변수 T는 배열의 타입을 결정합니다. numbers 배열의 경우 T는 number로 결정되며, strings 배열의 경우 T는 string으로 결정됩니다. 타입 추론에 의해 maxNumber와 maxString의 타입은 각각 number | undefined와 string | undefined가 됩니다.

위 예시 코드처럼 제네릭을 사용하면 여러 종류의 데이터 타입에 대해 동일한 로직을 적용할 수 있습니다. 제네릭은 TypeScript에서 매우 유용한 기능 중 하나입니다.




<T> 무슨 의미?


<T>는 TypeScript에서 제네릭 타입 매개변수를 나타냅니다. 제네릭 타입 매개변수는 함수, 클래스, 인터페이스 등에서 사용되며, 타입 안정성을 유지하기 위한 목적으로 사용됩니다.

<T>는 제네릭 타입 매개변수의 시작을 나타내는 구문입니다. <T>에서 T는 제네릭 타입 매개변수의 이름으로, 보통 알파벳 대문자 한 글자로 지정됩니다. <T>를 <U> 또는 <K> 등으로 바꿀 수도 있습니다.

제네릭 타입 매개변수는 함수나 클래스 내부에서 타입으로 사용됩니다. 예를 들어, function identity<T>(arg: T): T와 같은 함수에서 T는 함수 인자와 반환 값의 타입으로 사용됩니다. 함수가 호출될 때 T는 실제로 사용되는 데이터의 타입으로 대체됩니다.

제네릭은 TypeScript에서 매우 유용한 기능 중 하나이며, 코드의 재사용성과 타입 안정성을 높일 수 있습니다.




* 제네릭은 타입을 추론하게 해주는 기능인거야?


제네릭은 타입 추론(Type Inference)을 돕는 기능입니다. TypeScript에서 제네릭을 사용하면 함수나 클래스를 여러 종류의 타입에서 사용할 수 있으며, 이를 통해 타입 안정성과 코드 재사용성을 높일 수 있습니다.

제네릭을 사용하면 함수나 클래스에서 사용하는 타입을 미리 지정하지 않고, 함수를 호출하거나 클래스를 생성할 때 인자의 타입에 따라 자동으로 타입을 추론합니다. 이를 통해 코드 작성 시점에서 모든 가능한 타입에 대해 미리 지정하지 않아도 되므로 코드 작성이 더욱 간단해집니다.

예를 들어, 다음과 같은 함수가 있다고 가정해봅시다.

typescript

function identity<T>(arg: T): T {
  return arg;
}


이 함수는 제네릭 타입 매개변수 T를 받아서, arg와 같은 타입의 값을 반환합니다. 이 함수를 호출할 때 인자로 전달하는 값의 타입에 따라서 T가 결정됩니다. 예를 들어, 다음과 같이 함수를 호출할 수 있습니다.

typescript

let result = identity('Hello');  // result의 타입은 string
let value = identity(123);       // value의 타입은 number


위 코드에서 첫 번째 호출에서는 T가 string으로 결정되고, 두 번째 호출에서는 T가 number로 결정됩니다. 이렇게 인자를 통해 타입이 결정되므로, 타입 추론이 가능해집니다.



 


* 만약 string이 들어왔는데 리턴으로 불린이 나오게 하면 어떻게 되지?


만약 identity 함수가 받은 인자의 타입에 관계 없이 항상 불린 값을 반환하도록 변경하려면, 다음과 같이 함수 구현부를 수정할 수 있습니다.

typescript

function identity<T>(arg: T): boolean {
  return true;
}


위 코드에서는 함수의 반환 타입을 T에서 boolean으로 변경하였습니다. 이제 identity 함수는 인자로 받은 값에 상관없이 항상 true를 반환하게 됩니다.

하지만 이렇게 제네릭 타입 매개변수와 반환 타입이 서로 관련성이 없는 경우에는, 제네릭 함수의 의미가 상실되고 타입 안정성을 보장할 수 없게 됩니다. 따라서 일반적으로는 제네릭 함수의 반환 타입을 T와 연관성이 있는 타입으로 설정하는 것이 좋습니다.





* 이해가 안가는게  타입추론을 해준다고 하는데, 자바스크립트에서 알아서 타입추론을 하지 않나? 내부적으로 왜 굳이 코드를 첨부해서 제네릭을 쓰는거지?


JavaScript는 동적 타입 언어이기 때문에, 코드를 실행하기 전까지는 변수나 함수의 타입을 추론하지 않습니다. 실행 시점에서 타입 에러가 발생할 수 있습니다.

TypeScript에서 제네릭을 사용하면, 컴파일러가 타입 추론을 수행하여 코드를 실행하기 전에 타입 에러를 예방할 수 있습니다. 예를 들어, 다음과 같은 코드를 실행할 때 문제가 발생할 수 있습니다.

javascript
Copy code
function add(a, b) {
  return a + b;
}

add("1", "2"); // "12"
위 코드에서 add 함수는 두 개의 인자를 받아서 더한 후 결과를 반환합니다. 하지만 인자로 문자열 "1"과 "2"를 전달한 경우, 이들을 단순히 문자열 결합(concatenation)하여 "12"라는 잘못된 결과가 반환됩니다. 이 문제를 TypeScript에서는 다음과 같이 제네릭을 이용하여 예방할 수 있습니다.

typescript

function add<T extends number>(a: T, b: T): T {
  return a + b;
}

add(1, 2); // 3


add("1", "2"); // Error: Argument of type '"1"' is not assignable to parameter of type 'number'.
위 코드에서는 T extends number 구문을 이용하여, 함수 add가 받는 인자의 타입이 number의 서브타입(number 자체 또는 number를 상속한 타입)이어야 함을 명시합니다. 따라서 add 함수에 문자열 "1"과 "2"를 인자로 전달하면 컴파일 타임에 타입 에러가 발생하여 문제를 미리 예방할 수 있습니다.

즉, 제네릭을 사용하면 코드의 타입 안정성을 보장하면서도 타입 추론을 수행하여 타입 에러를 예방할 수 있습니다.





* extends number은 무슨 뜻이지?


extends number 구문은 TypeScript에서 제네릭 타입 매개변수 T가 number 타입의 서브타입이어야 한다는 것을 명시하는 구문입니다. 즉, T는 number 자체 또는 number를 상속한 타입(예를 들어, number[]나 number | null 등)이어야 합니다.

이 구문을 사용하면 함수나 클래스의 타입 매개변수를 number와 관련된 타입으로 제한할 수 있습니다. 이렇게 타입 매개변수를 제한하면 함수나 클래스에서 타입 안정성을 보다 강화할 수 있습니다. 예를 들어, 다음과 같은 함수를 생각해보세요.

typescript

function add<T>(a: T, b: T): T {
  return a + b;
}

add(1, 2); // 3
add("1", "2"); // "12"


위 코드에서는 타입 매개변수 T를 제한하지 않았기 때문에, add 함수에 어떤 타입이든지 인자로 전달할 수 있습니다. 그 결과, add("1", "2")와 같이 문자열을 인자로 전달하는 경우, 문자열 결합(concatenation)이 수행되어 "12"가 반환됩니다.

하지만, T extends number 구문을 이용하여 타입 매개변수 T가 number의 서브타입이어야 한다는 제한을 두면, 다음과 같이 문자열이 인자로 전달되는 경우 컴파일 타임에 타입 에러가 발생하여 문제를 미리 예방할 수 있습니다.

typescript

function add<T extends number>(a: T, b: T): T {
  return a + b;
}

add(1, 2); // 3


add("1", "2"); // Error: Argument of type '"1"' is not assignable to parameter of type 'number'.
즉, extends number 구문을 사용하면 타입 매개변수를 특정 타입으로 제한하여 코드의 타입 안정성을 강화할 수 있습니다.






* add <T: number> 이렇게 쓸 수 도 있나?


네, add<T: number>와 같이 제네릭 타입 매개변수 T에 제한 조건을 추가할 수 있습니다. 이 경우 T는 반드시 숫자 타입이어야 합니다. 따라서 T는 숫자 타입과 그 하위 타입만 사용할 수 있습니다.





* 이경우도 add("1", "2");일때 같은 에러를 반환하나?


add<T: number>와 같이 제네릭 타입 매개변수 T에 숫자 타입을 제한한 경우, add("1", "2")와 같은 문자열을 인자로 전달하면 타입 에러가 발생합니다. 이는 타입스크립트가 인자로 전달된 값의 타입을 추론하고 add 함수의 타입 매개변수 T가 숫자 타입이어야 한다는 제약 조건을 만족하지 못하기 때문입니다. 따라서 add("1", "2")와 같은 코드는 타입 에러가 발생합니다.

 


후기

 

질&답 형식의 타입스크립트 개념 정리

728x90