프론트엔드 개발/Typescript

객체 지향 프로그래밍 - Drag & Drop 프로젝트 02.

하이고니 2023. 3. 22. 01:45

 

본 게시글은 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();
    }
  }

 

 

 

 

객체 지향 프로그래밍을 드디어 제대로 접해본 느낌인데, 이게 진짜 너무 너무 어려웠다. 수차례 복습해야지..