본문 바로가기
TYPESCRIPT

연습 프로젝트 2

by 일태찡 2023. 3. 31.

 

렌더링 프로젝트 목록

 

class ProjectList {
  templateElement: HTMLTemplateElement;
  hostElement: HTMLDivElement;
  element: HTMLElement; // 1

  //                                 2
  constructor(private type: 'active' | 'finished') {
    this.templateElement = document.getElementById(
      "project-list"
    )! as HTMLTemplateElement;
    this.hostElement = document.getElementById("app")! as HTMLDivElement;

    const importedNode = document.importNode(
      this.templateElement.content,
      true
    );
    this.element = importedNode.firstElementChild as HTMLElement;
    this.element.id = `${this.type}-projects`; // 3
    this.attach();
    this.renderContent();
  }

  private renderContent() {
    const listId = `${this.type}-projects-list`; // 3
    this.element.querySelector('ul')!.id = listId;
    this.element.querySelector('h2')!.textContent = this.type.toUpperCase() + ' PROJECTS';
  }

  private attach() {
    this.hostElement.insertAdjacentElement("beforeend", this.element); // 4
  }
}

...

const prjInput = new ProjectInput();
const activePrjList = new ProjectList('active');
const finishedPrjList = new ProjectList('finished');

 

프로젝트 리스트 목록을 렌더링 할 새로운 클래스를 작성합니다.

전 블로그에서 했던 Input 클래스 렌더링과 크게 다르지 않습니다.

 

  1. section 태그이지만 특화된 유형이 없기에 일반적인 HTMLElement로 설정해 줍니다.
  2. 매개변수로 유니언 타입을 받아 활성화된 리스트와 종료된 리스트를 보여줄 것입니다.
  3. 템플릿 리터럴을 이용해 id를 부여합니다.
  4. 이번엔 종료 태그 앞에 생성해 보겠습니다.

 

 

 

싱글톤으로 애플리케이션 상태 관리하기

 

class ProjectState {
  private listeners: any[] = [];
  private projects: any[] = [];
  private static instance: ProjectState;

  private constructor() {

  }

  // 3
  static getInstance() {
    if (this.instance) {
      return this.instance;
    }
    this.instance = new ProjectState();
    return this.instance;
  }

  // 5
  addListener(listenerFn: Function) {
    this.listeners.push(listenerFn);
  }

  // 1
  addProject(title: string, description: string, numOfPeople: number) {
    const newProject = {
      id: Math.random().toString(),
      title: title,
      description: description,
      people: numOfPeople
    }
    this.projects.push(newProject)
    // 5
    for (const listenerFn of this.listeners) {
      listenerFn(this.projects.slice())
    }
  }
}

// 2
const projectState = ProjectState.getInstance();

...

class ProjectList {
  templateElement: HTMLTemplateElement;
  hostElement: HTMLDivElement;
  element: HTMLElement;
  assignedProjects: any[];

  constructor(private type: "active" | "finished") {
    this.templateElement = document.getElementById(
      "project-list"
    )! as HTMLTemplateElement;
    this.hostElement = document.getElementById("app")! as HTMLDivElement;
    this.assignedProjects = []; // 6

    const importedNode = document.importNode(
      this.templateElement.content,
      true
    );
    this.element = importedNode.firstElementChild as HTMLElement;
    this.element.id = `${this.type}-projects`;

      // 6
      projectState.addListener((projects: any[]) => {
        this.assignedProjects = projects;
        this.renderProjects(); // 7
      });

    this.attach();
    this.renderContent();
  }

  // 7
  private renderProjects() {
    const listEl = document.getElementById(`${this.type}-projects-list`)! as HTMLUListElement;
    for (const prjItem of this.assignedProjects) {
      const listItem = document.createElement('li');
      listItem.textContent = prjItem.title;
      listEl.appendChild(listItem);
    }
  }

  ...

}

class ProjectInput {

  ...
  
  @autobind
  private submitHandler(event: Event) {
    event.preventDefault();
    const userInput = this.gatherUserInput();
    if (Array.isArray(userInput)) {
      const [title, desc, people] = userInput;
      projectState.addProject(title, desc, people); // 4
      this.clearInputs();
    }
  }

  ...
  
}

 

리액트 리덕스와 비슷한 패턴을 위한 앱 상태를 관리하는 클래스를 구축합니다.

 

  1. 버튼 입력 시 그 내용을 리스트에 추가합니다.
  2. 전역 상수가 되어 파일 어디에서나 사용될 수 있게 합니다.
  3. 항상 전체 앱에서 한 가지 유형의 객체(하나의 상태 관리)를 갖게 합니다.
  4. Input 클래스와 연결합니다.
  5. 상태가 변경될 때마다 함수 목록을 호출하고 복사본을 얻습니다.
  6. 리스트에 연결해 갱신된 리스트를 받아옵니다.
  7. 받아 온 리스트를 렌더링 합니다.

 

 

지금까지의 과정으로 입력이 화면에 보이게 됩니다.

하지만 분류되지 않았고 전의 입력한 데이터가 중첩됩니다.

이제 이 문제들을 해결해야 합니다.

 

 

더 많은 클래스 및 사용자 정의 타입

 

enum ProjectStatus {
  Active,
  Finished,
}

class Project {
  constructor(
    public id: string,
    public title: string,
    public description: string,
    public people: number,
    public status: ProjectStatus
  ) {}
}

class ProjectState {
  ...
  private projects: Project[] = [];
  ...
  addProject(title: string, description: string, numOfPeople: number) {
    const newProject = new Project(Math.random().toString(), title, description, numOfPeople, ProjectStatus.Active); //
    this.projects.push(newProject);
    for (const listenerFn of this.listeners) {
      listenerFn(this.projects.slice());
    }
  }
}

 

다음과 같이 Project 클래스를 정의하여 코드를 개선하였습니다.

 

type Listener = (items: Project[]) => void;

class ProjectState = {
  private listeners: Listener[] = [];
  ...
  addListener(listenerFn: Listener) {
    this.listeners.push(listenerFn);
  }
  ...
}

...

class ProjectList = {
  ...
  constructor(private type: "active" | "finished") {
	...
    projectState.addListener((projects: Project[]) => {
      this.assignedProjects = projects;
      this.renderProjects();
    });
	...
  }
  ...
}

Listener 타입을 정의하여 코드를 개선하였습니다.

 

 

열거형으로 프로젝트 필터링하기

 

class ProjectList {
  templateElement: HTMLTemplateElement;
  hostElement: HTMLDivElement;
  element: HTMLElement;
  assignedProjects: Project[];

  constructor(private type: "active" | "finished") {
    this.templateElement = document.getElementById(
      "project-list"
    )! as HTMLTemplateElement;
    this.hostElement = document.getElementById("app")! as HTMLDivElement;
    this.assignedProjects = [];

    const importedNode = document.importNode(
      this.templateElement.content,
      true
    );
    this.element = importedNode.firstElementChild as HTMLElement;
    this.element.id = `${this.type}-projects`;

    // 1
    projectState.addListener((projects: Project[]) => {
      const relevantProjects = projects.filter(prj => {
        if(this.type === 'active') {
          return prj.status === ProjectStatus.Active;
        }
        return prj.status === ProjectStatus.Finished;
      })
      this.assignedProjects = relevantProjects;
      this.renderProjects();
    });

    this.attach();
    this.renderContent();
  }

  private renderProjects() {
    const listEl = document.getElementById(
      `${this.type}-projects-list`
    )! as HTMLUListElement;
    listEl.innerHTML = ''; // 2
    for (const prjItem of this.assignedProjects) {
      const listItem = document.createElement("li");
      listItem.textContent = prjItem.title;
      listEl.appendChild(listItem);
    }
  }

  private renderContent() {
    const listId = `${this.type}-projects-list`;
    this.element.querySelector("ul")!.id = listId;
    this.element.querySelector("h2")!.textContent =
      this.type.toUpperCase() + " PROJECTS";
  }

  private attach() {
    this.hostElement.insertAdjacentElement("beforeend", this.element);
  }
}

 

  1. 필터링을 통해 리스트를 분류합니다.
  2. 초기화를 통해 전에 남아있던 리스트를 없애 중복을 방지합니다.

 

 

'TYPESCRIPT' 카테고리의 다른 글

연습 프로젝트 4  (6) 2023.04.04
연습 프로젝트 3  (6) 2023.04.03
연습 프로젝트 1  (6) 2023.03.30
데코레이터  (8) 2023.03.28
제네릭  (6) 2023.03.27