TS
Enum(열거형)
특정 값의 집합을 정의할때 사용. 숫자형과 문자형을 지원한다.
enum Color {
Red,
Green,
Blue,
}
숫자형 Enum
열거형(Enum)은 디폴트 값으로 숫자형을 사용함. 각 값은 자동으로 0부터 시작해 1씩 증가한다.
또는 수동으로 값을 지정할수도 있다.
enum Color {
Red = 1,
Green = 2,
Blue = 5,
}
열거형의 값에 대해 산술 연산도 가능하다.
열거형은 일반적으로 상수값을 대신하여 사용되므로, TS에서는 열거형이 많이 사용된다.
사용시 코드의 가독성이 더욱 높아지고 오타 등의 실수를 방지할 수 있다.
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
역 매핑(Reverse mappings)
숫자형 열거형에만 존재하는 특징. 열거형의 키(key)로 값(value)을 얻을 수 있고, 값(value)로 키(key)를 얻을 수도 있다.
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
문자형 Enum
문자형 Enum의 경우 숫자형과 개념적으로는 비슷하지만, 열거형의 값을 모두 특정 문자 또는 다른 열거형 값으로 초기화 해야한다.
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
let myDirection: Direction = Direction.Up;
console.log(myDirection); // 출력: "UP"
주로 외부에서 가져온 값을 TS에서 다루기 위해 사용한다.
예) HTTP 요청 방식을 나타내는 열거형
enum HttpMethod {
Get = "GET",
Post = "POST",
Put = "PUT",
Delete = "DELETE",
}
function makeRequest(url: string, method: HttpMethod) {
// ...
}
makeRequest("/api/data", HttpMethod.Post);
Interface
타입 체크를 위해 사용하고, 주로 객체(Object)의 구조를 정의하기 위해 사용된다. 변수, 함수, 클래스에 사용할 수 있다.
선언된 프로퍼티 또는 메서드의 구현을 강제하여 일관성을 유지하도록 한다.
interface를 사용해 생성할 수 있고, 대부분 대문자로 시작한다.
변수와 인터페이스
인터페이스를 사용하여 변수를 선언할때는 반드시 정의된 프로퍼티를 모두 작성해야한다.
또 정의된 속성만 지정이 가능하고 추가로 프로퍼티를 작성하여 선언할수는 없다.
선택적 프로퍼티를 사용할 경우에는 ?를 사용한다.
interface User {
name: string;
age: number;
}
// OK
const user: User = {
name: "anna",
age: 20
}
// 순서가 달라도 OK
const user: User = {
age: 20,
name: "anna"
}
// 정의된 프로퍼티보다 작으므로 ERROR
const user: User = {
name: "anna"
}
// 정의된 프로퍼티보다 많이 작성해서 ERROR
const user: User = {
name: "anna",
age: 20,
job: "developer"
}
함수와 인터페이스
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);
클래스와 인터페이스
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
클래스를 구현할 때 인터페이스에서 정의된 함수나 메서드의 매개변수 타입과 반환 값과 일치하도록 구현해야 하므로, 클래스 내부에서 해당 메서드의 매개변수 타입을 다시 한번 더 명시해 주지 않으면 컴파일 에러가 발생하게 됨
인터페이스와 상속
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;
}
const food = {} as FoodFreshness;
food.name = "egg";
food.amount = 2;
food.isFreshed = true;
타입 별칭(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,
},
};
인터페이스나 다른 변수를 정의할 때 타입 별칭으로 정의한 타입을 참조하게 됨으로써 코드를 더 간결하고 가독성 좋게 만들 수 있다.
타입 별칭으로 만들어진 타입을 참조할 시에는 인터페이스와 마찬가지로 내부에 정의된 프로퍼티를 전부 참조해야만 한다.
또한 타입 별칭으로 만들어진 타입 내부에 정의된 프로퍼티 외에 다른 프로퍼티를 더 작성하게 되면 그 또한 컴파일 에러가 난다.
Interface vs Type Aliases
vs code 사용시 Type Aliases는 마우스를 올려놓으면 내부에 정의된 프로퍼티들이 보이지만 interface는 보이지 않는다.
또한 Type Aliases는 타입에 새로운 이름을 부여하는 것이므로 확장은 되지 않는다.
하지만 interface는 확장이 가능하고, 기존의 인터페이스 및 타입 별칭으로 만들어진 타입 둘다 상속이 가능하다.
타입 추론(Type Inference)
변수나 함수의 타입을 선언하지 않아도 TS가 자동으로 유추하는 기능.
let isNumber = 123; // 타입을 선언하지 않아도 자동으로 number로 추론
최적 공통 타입(Best common type)
TS는 여러 표현식에서 타입 추론이 발생할 때 해당 표현식의 타입을 사용하여 최적 공통 타입을 계산한다.
let x = [0, 1, null];
// x의 타입을 추론하기 위해서는 각 배열 요소의 타입을 고려해야함
// 타입 후보로는 number, null이 있음
// 최적 공통 타입 알고리즘은 각 후보의 타입을 고려해 모든 후보의 타입을
// 포함할 수 있는 타입을 선택한다.
문맥상의 타이핑(Contextual Typing)
코드의 위치(문맥)을 기준으로 타입을 결정하는 것
function add(a, b) {
return a + b;
}
// 매개변수의 타입이 명시되어 있지 않지만 a, b의 값을 자동으로 추론한다
// 만약 a, b가 모두 number라면 return 값도 number로 추론한다
타입추론의 장단점
장점
- 코드의 가독성 향상
- 개발 생산성 향상
- 오류 발견 용이성
단점
- 타입추론이 잘못될 경우 코드 오류 발생
- 명시적인 타입 지정이 필요한 경우가 있음
클래스(Class)
JS의 클래스
ES6에서 처음 도입됨. 객체를 생성하고 속성과 메서드를 정의할 수 있음.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${this.age}살 입니다.`);
}
}
const person = new Person('Alice', 30);
person.greet(); // "안녕하세요, 제 이름은 Alice이고, 30살 입니다."
TS의 클래스
JS의 클래스와 비슷하지만 몇가지 추가된 기능이 있음.
클래스의 속성과 메서드의 타입을 명시할 수 있음.
단 TS에서 클래스를 정의할때는 constructor를 이용해 초기화하는 멤버들은 모두 상단에서 정의를 해줘야 하고, constructor 내 인자로 받을때도 정확히 타입을 명시해줘야 한다.
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}살 입니다.`);
}
}
const person = new Person('Alice', 30);
person.greet(); // "안녕하세요, 제 이름은 Alice이고, 30살 입니다."
클래스와 상속(Inheritance)
인터페이스와 마찬가지로 기존에 존재하던 클래스를 상속받아 확장하여 새로운 클래스를 만들 수 있음. extens 키워드를 사용한다.
// 기초 클래스, 상위 클래스(superclasses)
class Animal {
move(distanceInMeters: number): void {
console.log(`${distanceInMeters}m 이동했습니다.`);
}
}
// Animal 클래스 상속
// 파생클래스, 하위클래스(subclasses)
class Dog extends Animal {
speak(): void {
console.log("멍멍!");
}
}
const dog = new Dog();
dog.move(10);
dog.speak();
public과 private
기본적으로 클래스 내에 선언된 멤버는 외부로 공개되는 것이 기본값이다.
공개하는것을 명시적으로 표시해줄 경우 public 키워드를 사용하고, 외부에 드러내지 않으려면 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
프로퍼티를 읽기 전용으로 만드는 키워드. 선언 또는 생성자에서 초기화해야한다.
읽기 전용 값은 값을 변경할 수 없기때문에 변경이 되지 않아야 하는 값들을 명시하여 보호할 수 있다.
class Mydog {
readonly name: string;
constructor(theName: string) {
this.name = theName;
}
}
let spooky = new Mydog("스푸키");
spooky.name = "멋진 스푸키"; // 에러
제네릭(Generic)
코드 재사용성을 높이고 타입 안정성을 보장하는 기능.
제네릭 사용시 함수, 클래스를 작성할때 사용될 데이터의 타입을 미리 지정하지 않고 이후에 함수나 클래스를 호출할때 인자로 전달된 데이터의 타입에 따라 자동으로 타입을 추론하게 한다.
제네릭의 필요성
function printLog(text) {
return text;
}
// 1. 특정 타입을 주어 작성
function printLog(text: string): string {
return text;
}
printLog('hello'); // 정상
printLog(123); //에러
// string 외의 다른 타입은 에러가 발생한다
// 2. 함수를 여러개 선언
function printLog(text: string): string {
return text;
}
function printLogNumber(text: number): number {
return text;
}
printLog('hello'); // 정상
printLogNumber(123); //정상
// 가독성과 유지보수성이 나빠진다
// 3. 유니온 타입으로 선언
function printLog(text: string | number) {
return text;
}
printLog('hello'); // 정상
printLogNumber(123); //정상
// string과 number 이외에는 타입이 추론되지 않음으로 사용할수없음
// 4. any 타입을 사용
function printLog(text: any): any {
return text;
}
// 어떤 타입도 사용할수있지만 타입을 추론할 수 없다
// => 제네릭을 사용하자!
제네릭 사용시
function printLog<T>(text: T): T {
return text;
}
// T(타입변수):유저가 준 파라미터의 타입을 캡쳐하고 이 정보를 나중에 사용할 수 있게 한다
const str = printLog<string>('hello');
const str = printLog('hello'); // 타입 추론 기능 활용
// ㄴ 타입이 복잡해져 컴파일러가 유추할 수 없는경우에는 사용할수없다
인터페이스와 제네릭
interface Item<T> {
name: T; // 어떤 타입이 들어갈지만 작성해주면 인터페이스 하나로 재사용 가능
stock: number;
selected: boolean;
}
const obj: Item<string> = {
name: "T-shirts",
stock: 2,
selected: false
};
const obj: Item<number> = {
name: 2044512,
stock: 2,
selected: false
};
클래스와 제네릭
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
// number 대신 string 등 여러가지를 사용할수있다!
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
제네릭 타입 변수
제네릭을 사용하기 시작하면 제네릭 함수를 만들때 컴파일러가 함수 본문에 제네릭 타입화 된 매개변수를 쓰도록 강요한다.
// 예시
function printLog<T>(text: T): T {
console.log(text.length); // 에러 => string이 아닌 타입을 보낼수도 있음
return text;
}
// 해결
function printLog<T>(text: T[]): T[] {
console.log(text.length); // 인자값으로 배열 형태의 T를 받음. = .length를 허용
return text;
}
function printLog<T>(text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
제네릭 제약 조건
제네릭 타입 변수 외에 제네릭 함수에 어느정도 어떤 타입이 들어올 것인지 힌트를 줄 수 있다.
// 예시
function printLog<T>(text: T): T {
console.log(text.length); // T가 어떤타입일지 모르니까 에러
return text;
}
// 타입 정의 없이 length 정도를 허용하려면
interface TextLength {
length: number;
}
// extends를 통해 강제는 아니지만 length에 대해서만 동작하는 인자를 받을수있음
function printLog<T extends TextLength>(text: T): T {
console.log(text.length);
return text;
}
// 또는 keyof 사용
interface Item<T> {
name: T;
stock: number;
selected: boolean;
}
function printLog<T extends keyof Item>(text: T): T {
return text;
}
printLog('name'); //정상
pirntLog('key'); //에러
// <T extends keyof Item> 부분에서 첫 번째 인자로 받는 객체에 없는 속성들은
// 접근할 수 없게끔 제한 가능
'study > TIL' 카테고리의 다른 글
다시 시작 (0) | 2024.03.20 |
---|---|
230628 TIL (0) | 2023.06.28 |
타입스크립트 기본 복습 정리 (0) | 2023.06.16 |
4월 19일 TIL (0) | 2023.04.20 |
기본 프로젝트 세팅과 git (1) | 2023.04.12 |