객체 지향 프로그래밍 - Drag & Drop 프로젝트 02.
본 게시글은 udemy 강좌(Typescript :기초부터 실전형 프로젝트까지 with React + NodeJS)를 수강한 내용을 바탕으로 합니다.
프로젝트 관리 앱.
이 앱에서는 사용자가 프로젝트를 생성할 수 있고, 상태(active / finished) 별 프로젝트를 볼 수 있다.
- Project는 프로젝트 개체를 나타내며 id, 제목, 설명, 인원 수 및 상태(active / finished)를 가진다.
- ProjectItem은 프로젝트 목록의 단일 항목을 나타내며 프로젝트 제목, 인원 수 및 설명을 표시한다.
- State 클래스는 리스너가 상태 업데이트를 구독할 수 있도록 한다.
- ProjectState는 State의 하위 클래스로서 프로젝트를 위한 애플리케이션 상태를 관리한다. 이는 싱글톤 인스턴스를 제공하고, 프로젝트를 추가하는 메서드를 제공한다.
- Validatable 인터페이스와 validate 함수를 통해 사용자 입력의 유효성을 검사한다.
- autobind 데코레이터를 정의해서 클래스 메서드를 클래스 인스턴스에 바인딩한다.
- UI 구성 요소를 만들기 위한 기본 클래스 인 Component. 이는 templateId, hostElementId, insertAtStart 및 newElementId를 취하는 생성자를 정의하며 템플릿을 가져와 새 요소를 생성하고 host element에 삽입한다. 또한 추상 메서드 renderContent를 정의하며 하위 클래스에서 이를 구현해야 한다.
- ProjectList 클래스는 Component를 확장한 것으로, 프로젝트 목록을 표시한다. 프로젝트의 상태(active / finished)를 지정하는 type 인자를 취한다. 이는 프로젝트 상태가 변경되거나 프로젝트가 추가될 때 할당된 프로젝트 목록을 업데이트하기 위해 프로젝트 상태 변경 사항을 체크한다.
ACTIVE PROJECTS에 있는 항목을 FINISHED PROJECTS로,
FINISHED PROJECTS에 있는 항목을 ACTIVE PROJECTS로
드래그해서 옮길 수 있는 기능을 구현하려고 한다.
// Drag & Drop Interfaces
interface Draggable {
dragStartHandler(event: DragEvent): void;
dragEndHandler(event: DragEvent): void;
}
interface DragTarget {
// 드래그 앤 드롭 실행중. 드래그가 타겟이 유효한지 알려주기 위함.
dragOverHandler(event: DragEvent): void;
// 드롭 시 벌어지는 일들.
dropHandler(event: DragEvent): void;
// 드롭이 일어나지 않고 취소되거나 하는 경우?
dragLeaveHandler(event: DragEvent): void;
}
두 개의 인터페이스를 정의한다.
Draggable
: 드래그 할 요소. ProjectItem 클래스
DragTarget
: 드래그가 도착하는 곳. ProjectList 클래스
// ProjectItem Class
class ProjectItem extends Component<HTMLUListElement, HTMLLIElement>
implements Draggable {
private project: Project;
...
constructor(hostId: string, project: Project) {
...
}
@autobind
dragStartHandler(event: DragEvent) {
console.log(event);
}
dragEndHandler(_: DragEvent) {
console.log('DragEnd');
}
configure() {
this.element.addEventListener('dragstart', this.dragStartHandler);
this.element.addEventListener('dragend', this.dragEndHandler);
}
...
}
Draggable은 dragStartHandler와 dragEndHandler를 가진다.
addEventListener의 'dragstart', 'dragend' 이벤트 발생 시 해당 메서드가 실행된다.
// ProjectList Class
class ProjectList extends Component<HTMLDivElement, HTMLElement>
implements DragTarget {
assignedProjects: Project[];
constructor(private type: 'active' | 'finished') {
...
}
@autobind
dragOverHandler(_: DragEvent) {
const listEl = this.element.querySelector('ul')!;
// drop 할 수 있는 곳인지 css 상으로 표현하기 위해 class 추가
listEl.classList.add('droppable');
}
dropHandler(_: DragEvent) {}
@autobind
dragLeaveHandler(_: DragEvent) {
const listEl = this.element.querySelector('ul')!;
// 마우스 벗어나면 class 제거
listEl.classList.remove('droppable');
}
configure() {
this.element.addEventListener('dragover', this.dragOverHandler);
this.element.addEventListener('dragleave', this.dragLeaveHandler);
this.element.addEventListener('drop', this.dropHandler);
projectState.addListener((projects: Project[]) => {
...
});
}
...
}
// ProjectItem
@autobind
dragStartHandler(event: DragEvent) {
// 모든 드래그가 데이터를 전송할 수 있는 건 아님. 그래서 null 일 수도 있다.
// 데이터 포맷, 전송할 데이터
event.dataTransfer!.setData('text/plain', this.project.id);
event.dataTransfer!.effectAllowed = 'move';
}
옮기고 싶은 데이터 전체를 전송하는 것이 아니다. 나중에 id를 통해서 나머지 정보를 다 불러올 수 있기 때문에 최소한의 데이터만 전송한다.
// ProjectList
@autobind
dragOverHandler(event: DragEvent) {
if (event.dataTransfer && event.dataTransfer.types[0] === 'text/plain') {
event.preventDefault();
const listEl = this.element.querySelector('ul')!;
listEl.classList.add('droppable');
}
}
@autobind
dropHandler(event: DragEvent) {
// 데이터 추출
const prjId = event.dataTransfer!.getData('text/plain');
projectState.moveProject(
prjId,
this.type === 'active' ? ProjectStatus.Active : ProjectStatus.Finished
);
}
@autobind
dragLeaveHandler(_: DragEvent) {
const listEl = this.element.querySelector('ul')!;
listEl.classList.remove('droppable');
}
// ProjectState
moveProject(projectId: string, newStatus: ProjectStatus) {
const project = this.projects.find(prj => prj.id === projectId);
// 드롭할 곳의 project의 상태가 이전과 달라야 상태를 변경시켜준다.
if (project && project.status !== newStatus) {
project.status = newStatus;
this.updateListeners();
}
}
객체 지향 프로그래밍을 드디어 제대로 접해본 느낌인데, 이게 진짜 너무 너무 어려웠다. 수차례 복습해야지..