추상 클래스는 인터페이스의 역할도 하면서 클래스의 기능도 가지고 돌연변이 같은 클래스입니다
일반 클래스와의 차이는 선언할 때 코드 말머리에 abstract
를 추가한다는 것인데요,
이렇게 선언한 클래스는 인스턴스 생성구문(new
)를 사용할 수 없습니다
그리고 용어 정의적으로는 하나 이상의 추상 메서드를 가진 클래스를 추상 클래스라고 합니다
// 추상 클래스 생성
abstract Class App
// 추상 메서드 생성
abstract fc(): void;
추상 클래스에서 선언한 추상 메서드들은 이를 상속받은 자식 클래스에서 반드시 구체화되어야 합니다
이와 같이 실제로 발동될 코드에 조건을 걸어준 다는 점에서 추상 클래스는 인터페이스와 유사한 면이 있습니다
추상 클래스는 일종의 미완성 설계도이자 클래스 생성의 중간 단계 정도의 역할을 합니다
공통적으로 사용될 일반 메서드도 가질 수 있으면서, 거기서 선언된 추상 메서드는 하위 클래스에서 반드시 구현하도록 강제합니다
abstract class Component {
...
abstract setup(): void;
abstract template(): string;
render(): void {
this.target.innerHTML = this.template();
}
}
// setup()과 template()는 Component를 상속받은 하위 클래스에서 구체화
반면 인터페이스의 역할은 체크리스트와 유사합니다
간단한 예제 코드를 살펴보겠습니다
interface IAnimal {
name: string;
makeSound(): void;
}
이 인터페이스는 name
이라는 속성과 makeSound()
라는 메서드를 정의하고 있습니다
만약 IAnimal
인터페이스를 구현하는 클래스가 있다면, 해당 클래스는 인터페이스가 정의한 속성과 메서드를 모두 구현해야 합니다
마치 체크리스트에서 모든 항목을 체크해야 하는 것과 같습니다
또 인터페이스는 클래스와 클래스 간의 상호 작용을 가능하게 하기 위해서도 사용됩니다
그리고 인터페이스는 추상 클래스와 달리 다중 상속을 지원하기 때문에, 하나의 클래스는 여러 인터페이스를 동시에 구현할 수도 있습니다
interface IList {
id: number;
userid: string;
content: string;
register: Date;
}
interface IUser {
userid: string;
username: string;
}
interface IState {
user: IUser;
list: IList[]; // [{}, {}, {}...]
}
// HTMLElement와 제네릭을 사용한 이유는 상속받을 자식 클래스에서 엘리먼트와 타입을 구체화하기 위해 (확장성 ↑)
abstract class Component<T> {
target: HTMLElement;
state!: T;
constructor(_target: HTMLElement) {
this.target = _target;
this.setup();
this.render();
}
abstract setup(): void;
abstract template(): string;
render(): void {
this.target.innerHTML = this.template();
}
}
// 타입을 구체화합니다 (IState -> this.state)
class App extends Component<IState> {
setup(): void {
this.state = {
list: [
{ id: 1, userid: "web7722", content: "hello1", register: new Date("2023-01-09") },
{ id: 2, userid: "web7722", content: "hello2", register: new Date("2023-01-09") },
{ id: 3, userid: "web7722", content: "hello3", register: new Date("2023-01-09") },
],
user: {
userid: "web7722",
username: "testname",
},
};
}
template(): string {
const { list } = this.state;
return `
<div id='comment-list'>
${list
.map((comment) => {
return `<ul class="comment-row" data-index="${comment.id}">
<li class="comment-id">${comment.userid}</li>
<li class="comment-content">${comment.content}</li>
<li class="comment-date">${comment.register}</li>
</ul>`;
})
.join("")}
</div>
<button id='btn'>버튼!</button>
`;
}
}
const div = document.createElement("div");
div.id = "app";
const app = new App(div);
// App.tsx
import { TestComponent } from "./common/Test"
const App = () => {
return (
<div className="App">
<TestComponent userid="web7722" username="testname" />
</div>
);
}
export default App;
// Test.tex
import { useState, useEffect } from "react"
// 내려받는 쪽에서 프로퍼티에 대한 타입 정의가 필요
interface IUser {
userid: string;
username: string;
}
interface IList {
id: number;
userid: string;
content: string;
register: string;
}
export const TestComponent= ({ userid, username }: IUser) => {
// 전달받은 프로퍼티의 타입과 상태값의 타입을 명시해야 합니다 (IUser, IList[])
const [ commentList, setCommentList ] = useState<IList[]>([])
const comments: IList[] = [
{ id: 1, userid: "web7722", content: "hello1", register: "2023-01-09" },
{ id: 2, userid: "web7722", content: "hello2", register: "2023-01-09" },
{ id: 3, userid: "web7722", content: "hello3", register: "2023-01-09" },
]
useEffect(() => {
setCommentList(comments)
}, [])
return (
<div>
<div>userid: {userid}</div>
<div>username: {username}</div>
<form>input</form>
<div id='comment-list'>
{commentList?.map(comment =>
<ul data-index={comment.id}>
<li>{comment.userid}</li>
<li>{comment.content}</li>
<li>{comment.register}</li>
</ul>)}
</div>
<button id='btn'>버튼!</button>
</div>
)
}