
Today I Learn📖
- 인터페이스 개요 (강의)
- 인터페이스의 함수, 클래스 (강의)
- 인터페이스의 인덱싱 가능 타입 (강의)
- 인터페이스 확장 (강의)
- 함수의 명시적 this 타입 (강의)
- 함수 오버로딩 (강의)
- 클래스의 접근 제어자 (강의)
- 추상 클래스 (강의)
- 데코레이터 (강의)
: 개체(객체, 배열, 함수, 클래스 등)를 정의하는 타입 (유니온 정의할 땐 잘 사용 안 함)
=> Interface 인터페이스명 {}으로 사용
-> type 별칭과 똑같은 역할
=> 인터페이스, type 별칭 둘 다 ?, readonly 옵션 사용 가능
interface UserI {
name: string
readonly age: number // readonly 붙으면 읽기 전용
isValid?: boolean // 선택 옵션
}
const user: UserI = {
name: 'Heropy',
age: 85
// isValid 없어도 에러 안 남
}
user.age = 22 // 에러 발생 -> 읽기 전용이라 수정 안 됨
: 호출 시그니처(Call signature) 사용
=> (매개변수: 타입): 반환 타입으로 사용
-> 타입의 함수 타입이랑 같은 역할
interface User {
name: string
age?: number
}
interface GetUserNameI {
(u: User) : string // 호출 시그니처
}
const getUserName: GetUserNameI = (user: User) = user.name // 다른 곳에서 반환 타입으로 사용 가능
: 구문 시그니처 (Construct signature) 사용
=> new (매개변수: 타입): 반환타입으로 사용
interface UserI {
name: string
getName(): string // 메서드도 정의 가능
}
interface UserC {
new (n: string): UserI // 구문 시그니처
// 클래스가 실행된 결과는 인스턴스 -> 인스턴스의 타입을 반환값으로 써줘야함
}
class User implements UserI { // TS에서는 `class 클래스명 implements 타입 {}`으로 클래스 사용, implements 뒤에 여러 개 작성 가능 (쉼표로 구분)
public name
constructor (name: string) {
this.name = name
}
getName() {
return this.name
}
}
const user = new User('Heropy') // 생성자 함수를 이용해 인스턴스 생성
user.getName () // 'Heropy'
function hello(userClass: UserC, msg: string) { // 클래스 자체를 매개변수로 받을 때는 구문 시그니처 생성해서 반환값으로 써야함
const user = new userClass('Heropy")
return `Hello ${user.getName()}, ${msg}`
}
hello(User, 'good morning!')
: 인덱스 시그니처 사용
=> [매개변수: 타입]: 반환 타입으로 사용
-> 대괄호 속 key를 통해 value에 접근할 수 있음, key는 아직 정해져있지 않은 이름
// 인터페이스로 배열 타입 정의 가능 -> 인덱스 시그니처 정의하면 인덱싱도 가능
interface Arr {
[key: number]: string // 인덱스 시그니처
}
const arr: Arr = ['A', 'B', 'C']
console.log (arr[1]) // 'B'
// 인터페이스로 객체 타입 정의 가능 -> 인덱스 시그니처 정의하면 인덱싱도 가능
interface Obj {
[key: string]: number // 인덱스 시그니처
}
const obj: Obj = { a: 123, b: 456, c: 789 }
console. log (obj ['b']) // 456
console. log(obj.b) // 456
// 객체에 속성 추가 가능
interface User {
[key: string]: string | number // 인덱스 시그니처, 선택 옵션이라는 의미, key가 string이고 값이 string 또는 number인 속성은 다 추가 가능
name: string
age: number
}
const user: User = {
name: 'Heropy',
age: 85,
email: 'thesecon@gmail.com', // 추가된 속성
isValid: true // bool은 반환 타입에 없기 때문에 에러 발생
}
인터페이스명 extends 타입 으로 사용interface UserA {
name: string
age: number
}
interface UserB extends UserA { // userA 기반으로 확장
isValid: boolean
}
const user: UserB = {
name: 'Heropy',
age: 85,
isValid: true
}
중복된 인터페이스명 사용 => 중복된 내용들은 병합됨interface FullName {
firstName: string
lastName: string
}
interface FullName {
middleName: string
lastName: number // 에러 발생 -> 앞에 선언된 변수와 이름 같으면 타입까지 같아야함
}
const fullName: FullName = {
firstName: 'Tomas',
middleName: 'Sean',
lastName: 'Connery'
}
: this의 타입을 인터페이스를 통해 미리 알려주는 것
interface User {
name: string
}
function greet(this: User, msg: string) { // 명시적 this 타입 사용됨
return `Hello ${this.name}, ${msg}`
}
const heropy = {
name: 'Heropy'
greet
}
heropy.greet('Good morning~') // 'Hello Heropy, Good morning~'
const neo = {
name: 'Neo'
}
greet.call(neo, 'Have a great day!') // 'Hello Neo, Good morning~'
: 한 함수가 n가지 방법으로 호출될 수 있을 때 사용, 유니온 타입은 의도와 다르게 동작 가능하기 때문에 함수 오버로딩 사용함
=> 함수 선언부 -> 함수 구현부로 사용
// 함수 오버로딩
function add(x: string, y: string): string // 선언부 1
function add(x: number, y: number): number // 선언부 2
function add(x: any, y: any): any { // 구현부
console.log(x, y)
return x + y
}
add('Hello', 'World') // 선언부 1에 해당
add(12, 34) // 선언부 2에 해당
add('Hello', 34) // 에러 발생 -> 타입이 안 맞음
// 인터페이스에서의 함수 오버로딩
interface UserBase {
name: string
age: number
}
interface User extends UserBase { // 함수 오버로딩 사용됨
updateInfo(newUser: UserBase): User // updateInfo 형식 1
updateInfo(name: string, age: number): User // updateInfo 형식 2
}
const user: User = {
name: 'Heropy',
age: 85,
updateInfo: function(nameOrUser: UserBase | string, age?: number) { // 객체를 받거나 string & number를 각각 받기
if (typeof nameOrUser === 'string' && age !== undefined) {
this.name = nameOrUser
this.age = age
} else if (typeof nameOrUser === 'object') {
this.name = nameOrUser.name
this.age = nameOrUser.age
}
return this
}
}
=> public (생략 가능), protected, private
class Animal {
private name: string // Animal 클래스 외 다른 곳에서는 접근 불가
protected sound: string // Animal 클래스 + Animal 클래스를 확장한 곳에서만 사용 가능
constructor(name: string, sound: string) {
this.name = name
this.sound = sound
}
}
class Dog extends Animal {
public color: string // public은 굳이 안 써도 됨
constructor(name: string, sound: string, color: string) {
super(name, sound)
this.color = color
}
private speak() {
console.log(this.name) // 에러 발생 -> private이라 접근 불가
return this.sound // Animal 클래스 확장한 곳이니까 접근 가능
}
getColor() {
return this.color
}
}
const dog = new Dog('흰둥이', '멍멍!', 'white')
dog.speak() // 에러 발생 -> private이라 접근 불가
dog.getColor() // 'white'
dog.name // 에러 발생 -> private이라 접근 불가
dog.sound // 에러 발생 -> protected 접근 불가
dog.color // 'white'
=> 접근제어자 뒤에 작성
-> static, readonly 등이 있음
class Animal {
private name: string
public readonly sound: string // readonly니까 읽기 전용
constructor(name: string, sound: string) {
this.name = name
this.sound = sound
}
static speak() { // static이니까 인스턴스에서는 접근 불가 (클래스 자체를 호출해서 사용)
console.log('Animal speak!')
}
}
class Dog extends Animal {
public color: string
constructor(name: string, sound: string, color: string) {
super(name, sound)
this.color = color
this.sound = '야옹!' // 읽기 전용이라 수정 불가
}
getColor() {
return this.color
}
}
const dog = new Dog('흰둥이', '멍멍!', 'white')
Animal.speak() // speak가 static 메서드니까 클래스 이름으로 직접 호출
Dog.speak() // Dog도 Animal을 상속받았으니 가능
dog.speak() // 에러 발생 -> 인스턴스에서는 호출 불가
// 생성자 매개변수 속성 축약 -> 위 코드의 class Animal과 동일
class Animal {
constructor( // 매개변수와 속성의 이름이 같으면 constructor로 넘길 때 축약 가능
private name: string,
public readonly sound: string
) {}
static speak() {
console.log('Animal speak!')
}
}
: 클래스의 구조를 강제하기 위한 것, 직접 인스턴스를 만들 수는 없고 상속을 통해서만 사용 가능
=> 클래스와 메서드에 abstract붙임
-> abstract로 타입만 선언 하거나 메서드 직접 구현도 가능
abstract class AnimalA {
abstract color: string // 추상 속성 -> 자식이 반드시 구현해야함
abstract getColor(): string // 추상 메서드 -> 자식이 반드시 구현해야함
constructor(public sound: string) {} // 모든 자식이 공통으로 갖는 내용이니까 직접 지정
speak() { // 직접 구현한 일반 메서드 -> 모든 자식이 공통으로 갖는 내용이니까
return this.sound
}
}
class Dog extends AnimalA {
constructor(
sound: string,
public color: string // public을 붙이면 자동 속성 생성 + 할당됨 (안 붙이면 그냥 매개변수)
) {
super(sound) // 상속 받았으니 super 호출 필요
}
getColor() { // 자식이 추상 메서드 구현함
return this.color
}
}
const dog = new Dog('멍멍!', 'white')
dog.speak() // '멍멍!'
dog.getColor() // 'white'
| 특징 | 추상클래스 | 인터페이스 |
|---|---|---|
| 사용법 | abstract class 클래스명 {} | interface 인터페이스명 {} |
| 상속 | extends (1개) | implements (여러, 개, 가능) |
| 메서드 구현 | 가능 (구현부 포함) | 불가 (타입만 정의) |
| 부모 호출 | super() 필수 | 상속 아니니까 필요없음 |
| 상황 | 부모 클래스의 공통 속성을 규칙으로 만들고 싶을 때 | 타입만 정하고 구현은 자유롭게 할 때 |
// 추상클래스와 인터페이스 동시 사용 가능
interface AnimalI1 { ... }
interface AnimalI2 { ... }
abstract class CatA { ... }
class Cat extends CatA implements AnimalI1, AnimalI2 { // 동시 사용 가능, extends가 implements보다 먼저 와야함
constructor(
) {
super() // 추상클래스로 extends 했으니까 super() 호출 필요
}
: 클래스나 클래스 속 메서드, 속성 등에 기능을 확장하거나 변경, 제어하기 위한 문법
=>tsconfig.json의 "compilerOptions":에 "experimentalDecorators": true 추가 후 사용
-> 사용 위치: 클래스, 클래스의 속성, 클래스의 메서드, 메서드의 매개변수
-> 사용 위치 앞 또는 위에 @데코레이터명을 붙임
// deco.ts
export function deco(target: any) {
if (target.name !== 'User') { // 생성자 함수로 클래스 선언될 때 단 1번만 실행됨
throw new Error('클래스의 이름이 User가 아닙니다!')
}
console.log('정상적인 클래스의 이름입니다!')
return class extends target { // return 속 내용은 생성된 클래스가 사용될 때마다 실행됨, 원본 클래스 복사 + 새로운 내용
constructor(...args: any[]) { // 클래스 속 데이터가 어떤 것일지 모르니까 ...args로 전부 받기
super(...args) // 전부 받은 걸로 부모 원본 클래스 호출
fetch('서버주소', {
//
})
console.log('완료!')
}
} as any
}
// main.ts
import { deco } from './deco'
@deco // deco 실행, 클래스에 적용할 때는 클래스명 위에 붙임
class User {
constructor(public name: string) {}
hello(msg: string) {
return `Hello ${this.name}, ${msg}`
}
}
const heropy = new User('Heropy') // deco 속 return 뒷부분 실행
const neo = new User('Neo') // deco 속 return 뒷부분 실행
console.log(heropy)
console.log(neo)
// deco에서 타입 지정할 때 제네릭 사용하는 방법
export function deco<T extends { new (...args: any[]): any }>(target: T): any {
...
} // as any는 삭제
인터페이스를 공부하며 자바스크립트의 클래스도 다시 한번 복습했다. 또, 타입 작성하는 건 많이 안 것 같은데 함수 오버로딩, 오바라이딩, 수식어 등 새롭게 배우는 개념들이 있어서 이해하는데 시간이 좀 더 걸렸다 🥹
타입으로 선언하는 것과 인터페이스 선언의 차이점이 궁금했는데 선언 방식의 차이고 의미는 같다는 걸 알게됐다 !