Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

동그란 도그린

[타입스크립트] 한 입 크기로 잘라먹는 타입스크립트 정리 (6. 클래스 ~ 10. 유틸리티 타입) 본문

FrontEnd

[타입스크립트] 한 입 크기로 잘라먹는 타입스크립트 정리 (6. 클래스 ~ 10. 유틸리티 타입)

도그rin 2023. 10. 29. 01:58

6️⃣ 클래스

클래스란?

  • 동일한 모양의 객체를 더 쉽게 생성하도록 도와주는 문법
  • 붕어빵 = 객체, 붕어빵 기계 = 클래스
  • 클래스를 이용하여 새로운 객체를 생성할 때 new 클래스이름 형태로 클래스의 생성자 함수 호출
class Student {
	name;
	age;
	grade;

	// 생성자
	constructor(name, age, grade) {
		this.name = name;
		this.age = age;
		this.grade = grade;
	}

	// 메서드
	study() {
		console.log("열심히 공부함");
	}
}

// 클래스 호출하여 객체 생성
const studentA = new Student("홍길동", 27, "A+");

 

✨ 상속

  • 앞서 만든 클래스를 기반으로 추가적인 필드와 메서드를 갖는 클래스를 선언하고 싶다면 상속 이용
  • 상속할 클래스의 생성자를 함께 호출해줘야 함 ⇒ 호출하지 않으면 이전 클래스에서 정의한 필드가 제대로 설정되지 않음
// Student 클래스 상속
class StudentDeveloper extends Student {
	// 필드
	favoriteSkill;

	// 생성자
	constructor(name, age, grade, favoriteSkill) {
		this.favoriteSkill = favoriteSkill;
	}

	// 메서드
	programming() {
		console.log(`${this.favoriteSkill}로 프로그래밍함`);
	}
}

 

✨ 타입스크립트의 클래스

  • 클래스의 필드를 선언할 때 타입 주석으로 타입을 함께 정의해야 함 (정의하지 않으면 암시적 any 타입으로 추론되는데 strict 옵션이 true일 때는 오류가 발생)
  • 생성자에서 각 필드의 값을 초기화하지 않을 경우, 초기값도 함께 명시해줘야 함
  • 생성자 함수에서 필드의 값들을 잘 초기화해준다면 필드 선언 시의 초기값은 생략해도 됨
  • 클래스가 생성하는 객체의 특정 프로퍼티를 선택적 프로퍼티를 만들고 싶다면 필드 이름 뒤에 물음표 붙이면 됨
class Employee {
	// 필드
	name: string = "";
	age: number = 0;
	position?: string = "";   **// ✨ 선택적 프로퍼티 (물음표 붙임)**

	// 생성자
	constructor(name: string, age: number, position: string) {
		this.name = name;
		this.age = age;
		this.position = position;
	}

	// 메서드
	work() {
		console.log("일함");
	}
}
  • 타입스크립트의 클래스는 타입으로도 사용 가능
class Employee {
	(...)
}

const employeeC: Employee = {    **// ✨ 타입으로 사용**
	name: "",
	age: 0,
	position: "",
	work() {},
};

 

✨ 타입스크립트의 상속

  • 파생 클래스에서 생성자를 정의했다면 반드시 super 메서드를 호출하여 슈퍼 클래스의 생성자를 호출해야 함
  • 슈퍼 클래스의 생성자 호출 위치는 생성자의 최상단이어야 함
class ExecutiveOfficer extends Employee {
	officeNumber: number;
	
	constructor(
		name: string,
		age: number,
		position: string,
		officeNumber: number
	) {
		super(name, age, position);    **// ✨ 슈퍼 클래스의 생성자 호출**
		this.officeNumber = officeNumber;
	}
}

 

✨ 접근 제어자

  • 타입스크립트에서만 제공되는 기능
  • 클래스의 특정 필드나 메서드를 접근할 수 있는 범위를 설정하는 기능
  • 3개의 접근 제어자 사용 가능
    1. public : 모든 범위에서 접근 가능
    2. private : 클래스 내부에서만 접근 가능
    3. protected : 클래스 내부 또는 파생 클래스 내부에서만 접근 가능

 

1. public

  • 필드의 접근 제어자를 지정하지 않으면 기본적으로 public 접근 제어자를 갖게 됨
class Employee {
  // 필드
  public name: string;    **// ✨ public 접근 제어자를 직접 명시도 가능**
  public age: number;
  public position: string;

  ...
}

const employee = new Employee("이정환", 27, "devloper");

employee.name = "홍길동";
employee.age = 30;
employee.position = "디자이너";

 

2. private

class Employee {
  // 필드
  private name: string; **// ✨ private 접근 제어자 설정**
  public age: number;
  public position: string;

  ...

  // 메서드
  work() {
    console.log(`${this.name}이 일함`); // 여기서는 접근 가능
  }
}

const employee = new Employee("이정환", 27, "devloper");

employee.name = "홍길동"; **// ❌ 오류 (클래스 내부에서만 접근 가능)**
employee.age = 30;
employee.position = "디자이너";

 

3. protected

class Employee {
  // 필드
  private name: string; 
  protected age: number;     **// ✨ protected 접근 제어자 설정**
  public position: string;

  ...

  // 메서드
  work() {
    console.log(`${this.name}이 일함`);
  }
}

class ExecutiveOfficer extends Employee {
 // 메서드
  func() {
    this.name;
    this.age; **// ✅ 가능 (파생 클래스에서 접근 가능)**
  }
}

const employee = new Employee("이정환", 27, "devloper");

employee.name = "홍길동";
employee.age = 30; **// ❌ 오류 (클래스 내부나 파생 클래스에서 접근 가능)**
employee.position = "디자이너";

 

✨ 접근 제어자 사용 ⇒ 필드 생략

  • 생성자에 접근 제어자를 설정하면 동일한 이름의 필드를 선언하지 못하게 됨 (접근 제어자가 설정되면 자동으로 필드도 함께 선언되기 때문) ⇒ 중복된 필드 선언을 모두 제거
  • 접근 제어자가 설정된 매개변수들은 this.필드 = 매개변수가 자동으로 수행됨 ⇒ 아래와 같은 생성자 내부 코드 제거 가능
  • 💡 클래스 사용 시 보통 생성자 매개변수에 접근 제어자를 설정하여 필드 선언과 생성자 내부 코드를 생략하는 것이 훨씬 간결하고 빠르게 코드를 작성할 수 있어 좋음
class Employee {
  // 필드
  private name: string;    **// ❌ ⇒ 중복 제거**
  protected age: number;   **// ❌ ⇒ 중복 제거**
  public position: string; **// ❌ ⇒ 중복 제거**

  // 생성자
  constructor(
    private name: string,
    protected age: number,
    public position: string
  ) {
    this.name = name;          **// 제거 가능**
    this.age = age;            **// 제거 가능**
    this.position = position;  **// 제거 가능**
  }

  // 메서드
  work() {
    console.log(`${this.name} 일함`);
  }
}

 

✨ 인터페이스를 구현하는 클래스

  • 인터페이스를 이용해 클래스에 어떤 필드/메서드들이 존재하는지 정의 가능
**interface** CharacterInterface {
  name: string;
  moveSpeed: number;
  move(): void;
}

class Character **implements CharacterInterface** {
  constructor(
    public name: string,
    public moveSpeed: number,
    private extra: string
  ) {}

  move(): void {
    console.log(`${this.moveSpeed} 속도로 이동!`);
  }
}

 

7️⃣ 제네릭

✨ 제네릭 함수란?

  • 모든 타입의 값을 다 적용할 수 있는 범용적인 함수
  • 타입 변수 T에 어떤 타입이 할당될지는 함수가 호출될 때 결정됨
  • 제네릭 함수 호출 시 타입 변수에 할당할 타입을 직접 명시할 수도 있음
  • 타입 변수가 2개 필요하다면 T, U와 같이 2개의 타입 변수 사용 가능
function func<T>(value: T): T {
	return value;
}

let num = func(10);

let arr = func<[number, number, number]>([1, 2, 3]);  **// ✨ 할당할 타입 직접 명시도 가능**

 

✨ 제네릭 함수 사용 사례

function swap<T, U>(a: T, b: U) {
	return [b, a];
}

const [a, b] = swap("1", 2);  **// T는 String 타입, U는 Number 타입으로 추론됨**
// 다양한 배열 타입을 인수로 받는 제네릭 함수
function returnFirstValue<T>(data: T[]) {
	return data[0];
}

let str = retrunFirstValue([1, "hello", "mynameis"]);
let num = returnFirstValue([0, 1, 2]);
**// 반환값의 타입을 배열의 첫번째 요소의 타입이 되도록 하려면 튜플 타입과 나머지 파라미터 이용**
function returnFirstValue<T>(data: [T, ...unknown[]]) {
	return data[0];
}

let str = returnFirstValue([1, "hello", "mynameis"]);   **// number 타입으로 추론됨**

 

제네릭 함수 이용 ⇒ Map 메서드 타입 정의

  • map 메서드는 모든 타입의 배열에 적용할 수 있으므로 arr 타입은 unknown[]
  • callback의 타입은 배열 요소 하나를 매개변수로 받아 특정 값을 반환하는 함수로 정의
  • unknown 타입을 타입 변수 T로 대체
// 1단계
function map(arr: unknown[], callback: (item: unknown) => unknown): unknown[] {}

// 2단계
function map<T>(arr: T[], callback: (item: T) => T): T[] {}

// 3단계 (map 메서드는 원본 배열 타입과 다른 타입의 배열로도 변환 가능해야 함)
function map<T, U>(arr: T[], callback: (item: T) => U): U[] {}

 

✨ 제네릭 인터페이스

  • 제네릭 인터페이스는 제네릭 함수와 달리 변수의 타입으로 정의할 때 반드시 꺽쇠와 함께 타입 변수에 할당할 타입을 명시해야 함 (제네릭 함수는 매개변수 값의 타입을 기준으로 타입 변수의 타입을 추론할 수 있지만, 인터페이스는 마땅히 추론할 수 있는 값이 없기 때문)
**interface** KeyPair<K, V> {
	key: K;
	value: V;
}

let keyPair: KeyPair<string, number> = {
	key: "key",
	value: 0,
};
// 인덱스 시그니쳐와 함께 사용 => 더 유연한 객체 타입 정의 가능
**interface** Map<V> {
  [key: string]: V;
}

let stringMap: Map<string> = {
  key: "value",
};

let booleanMap: Map<boolean> = {
  key: true,
};

 

✨ 제네릭 타입 별칭

**type** Map2<V> = {
  [key: string]: V;
};

let stringMap2: Map2<string> = {
  key: "string",
};

 

✨ 제네릭 클래스

class List<T> {
  constructor(private list: T[]) {}

  push(data: T) {
    this.list.push(data);
  }

  pop() {
    return this.list.pop();
  }

  print() {
    console.log(this.list);
  }
}

const numberList = new List([1, 2, 3]);
const stringList = new List(["1", "2"]);

 

✨ Promise 사용

  • Promise는 제네릭 클래스로 구현되어 있음
  • 새로운 Promise를 생성할 때 타입 변수에 할당할 타입을 직접 설정하면 해당 타입이 resolve 결과값의 타입이 됨

 

8️⃣ 타입 조작하기

✨ 타입 조작이란?

  • 기본 타입이나 별칭 또는 인터페이스로 만든 원래 존재하던 타입들을 상황에 따라 유동적으로 다른 타입으로 변환하는 것
  • 타입스크립트에서는 제네릭 이외에도 다양한 타입 조작 기능을 제공

 

1. 인덱스드 액세스 타입

  • 객체 프로퍼티의 타입 추출하기
interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number; **// 추가**
  };
}

function printAuthorInfo(author: { id: number; name: string, age: number }) {
	**// age 프로퍼티도 추가 => 프로퍼티 타입이 수정되면 매개변수 타입도 계속 수정해줘야 하는 불편함**
  console.log(`${author.id} - ${author.name}`);
}
// 위와 같은 불편함을 해결하기 위해 Post 타입으로부터 author 프로퍼티 타입을 추출
interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number; // 추가
  };
}

function printAuthorInfo(author: Post["author"]) {  **// author 프로퍼티 타입을 추출**
  console.log(`${author.id} - ${author.name}`);
}
  • 배열 요소의 타입 추출하기
// 배열 타입에서 하나의 요소의 타입만 추출할 수 있음
const post: PostList[number] = {
  title: "게시글 제목",
  content: "게시글 본문",
  author: {
    id: 1,
    name: "이정환",
    age: 27,
  },
};
  • 튜플 요소 타입 추출하기

 

2. Keyof 연산자

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

function getPropertyKey(person: Person, key: "name" | "age") {  // Person 타입에 새로운 프로퍼티가 추가되거나 수정될 때마다 key 타입 수정해줘야 함
  return person[key];
}

const person: Person = {
  name: "이정환",
  age: 27,
};
interface Person {
  name: string;
  age: number;
  location: string; // 추가
}

function getPropertyKey(person: Person, key: keyof Person) {  // keyof 연산자 이용
  return person[key];
}

const person: Person = {
  name: "이정환",
  age: 27,
};
  • Typeof와 Keyof 연산자 함께 사용하기
function getPropertyKey(person: Person, key: keyof typeof person) {  // typeof와 keyof 함께 사용
  return person[key];
}

const person: Person = {
  name: "이정환",
  age: 27,
};

 

3.  Mapped(맵드) 타입

 

4. 템플릿 리터럴 타입

 

9️⃣ 조건부 타입

조건부 타입이란?

  • extends와 삼항 연산자를 이용해 조건에 따라 각각 다른 타입을 정의하도록 돕는 문법
type A = number extends string ? number : string;
type ObjA = {
  a: number;
};

type ObjB = {
  a: number;
  b: number;
};

type B = ObjB extends ObjA ? number : string;  // ObjB는 ObjA의 서브타입이므로 참이 되어 B는 number 타입이 됨

 

분산적인 조건부 타입

  1. 타입 변수에 할당한 Union 타입 내부의 모든 타입이 분리됨
  2. 이후 분산된 각 타입의 결과를 모아 다시 Union 타입으로 묶음
type StringNumberSwitch<T> = T extends number ? string : number;

(...)

let c: StringNumberSwitch<number | string>;
// c변수는 string | number 타입으로 정의됨
// 조건부 타입의 타입 변수에 Union 타입을 할당하면 분산적인 조건부 타입으로 조건부 타입이 업그레이드 되기 때문

 

Exclude 조건부 타입 구현하기

  1. Union 타입이 분리됨
    • Exclude<number, string>
    • Exclude<string, string>
    • Exclude<boolean, string>
  2. 분리된 타입 모두 계산
  3. 계산된 타입들을 모두 Union으로 묶음
type Exclude<T, U> = T extends U ? never : T;

type A = Exclude<number | string | boolean, string>;

 

infer

  • 조건부 타입 내에서 특정 타입을 추론하는 문법
  • 특정 함수 타입에서 반환값의 타입만 추출하는 특수한 조건부 타입인 ReturnType을 만들 때 이용 가능
type ReturnType<T> = T extends () => infer R ? R : never;

type FuncA = () => string;

type FuncB = () => number;

type A = ReturnType<FuncA>;
// string

type B = ReturnType<FuncB>;
// number

 

🔟 유틸리티 타입

✨ 유틸리티 타입이란?

  • 제네릭, 맵드 타입, 조건부 타입 등의 타입 조작 기능을 이용해 실무에서 자주 사용되는 유용한 타입들을 모아 놓은 것

 

✨ Partial<T>

  • 특정 객체 타입의 모든 프로퍼티를 선택적 프로퍼티로 변환
  • 기존 객체 타입에 정의된 프로퍼티들 중 일부분만 사용할 수 있도록 도와주는 타입

✨ Required<T>

  • 특정 객체 타입의 모든 프로퍼티를 필수(선택적이지 않은) 프로퍼티로 변환

✨ Readonly

  • 특정 객체 타입의 모든 프로퍼티를 읽기 전용 프로퍼티로 변환

✨ Pick<T, K>

  • 특정 객체 타입으로부터 특정 프로퍼티만 골라내는 타입

✨ Omit<T, K>

  • 특정 객체 타입으로부터 특정 프로퍼티만 제거하는 타입

✨ Exclude<T, K>

  • T로부터 K를 제거하는 타입

✨ Extract<T, K>

  • T로부터 K를 추출하는 타입

✨ ReturnType<T>

  • 타입변수 T에 할당된 함수 타입의 반환값 타입을 추출하는 타입

 


'한 입 크기로 잘라먹는 타입스크립트' 핸드북을 정리한 내용입니다.

 

Comments