본문 바로가기
TYPESCRIPT

모듈과 네임스페이스

by 일태찡 2023. 4. 6.

 

네임스페이스 작업하기

 

drag-drop-interfaces.ts

namespace App {
  export interface Draggable {
    dragStartHandler(event: DragEvent): void;
    dragEndHandler(event: DragEvent): void;
  }

  export interface DragTarget {
    dragOverHandler(event: DragEvent): void;
    dropHandler(event: DragEvent): void;
    dragLeaveHandler(event: DragEvent): void;
  }
}

 

기존에 프로젝트에서 app.ts에 모두 몰려있던 코드를 나누는 작업입니다.

일단 드래그&드롭 인터페이스만 따로 빼서 파일을 만들고 네임스페이스 안에 넣습니다.

이 네임스페이스에는 클래스, 상수 등 원하는 모든 것을 넣을 수 있습니다.

인터페이스 앞에 export 키워드를 추가하여 이 인터페이스들이 이 네임스페이스 내부 또한 파일 외부에서도 이용가능하게 합니다.

 

app.ts

/// <reference path="drag-drop-interfaces.ts" />

namespace App {

  나머지 기존 코드

}

 

기존 프로젝트를 작성한 app.ts에 import 하는 코드를 작성합니다.

이는 슬래시 3개로 시작하는 특수 구문이고 그다음으로 열고 닫는 XML 태그를 만들고 다음과 같이 입력하면 됩니다.

임포트 된 파일로부터 무언가를 사용하고자 한다면 동일한 네임스페이스에 집어넣어야 하기에 네임스페이스 이름을 통일해 줍니다.

 

project-model.ts

namespace App {
  export enum ProjectStatus {
    Active,
    Finished,
  }

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

 

이렇게 하나 더 분리시키고 app.ts에 연결한 다음 프로젝트를 테스트하면 다음과 같은 에러가 나옵니다.

 

 

이는 각 파일이 독자적으로 컴파일이 되었기 때문이며 기본적으로 타입스크립트에서의 연결이 자바스크립트로 컴파일되면 무너지게 됩니다.

따라서 이 비연결을 tsconfig.json에서 해결해야 합니다.

 

"module": "amd",

...

"outFile": "./dist/bundle.js",                       /* Concatenate and emit output to single file. */

 

이 outFile 설정은 타입스크립트와 네임스페이스가 연결되도록 합니다.

그래서 여러 개 자바스크립트 파일을 컴파일하는 대신에 컴파일 중에 연결되는 참조들을 하나의 자바스크립트 파일로 연결합니다.

그리고 module 설정도 바꿔야 합니다.

이것들은 여러 이유 때문에 서로 다른 목적의 파일들을 로드하거나 하나로 묶는 방식들, 그리고 어떻게 개발되는지에 관한 것들이며 자세하게 알고 싶다면 아래 링크에서 볼 수 있습니다.

 

https://medium.com/computed-comparisons/commonjs-vs-amd-vs-requirejs-vs-es6-modules-2e814b114a0b

 

- — — — — CommonJS vs AMD vs RequireJS vs ES6 Modules — — — — -

Before i step into the modular section, kindly check out my unique comparison, Garbage Collection vs Automatic Reference Counting.

medium.com

 

<script src="dist/bundle.js" defer></script>

 

마지막으로 index.html에서 src 속성도 다음과 같이 변경해야 합니다.

 

 

ES 모듈 사용하기

 

네임스페이스는 컴파일할 때 에러를 잡아주지 못하는 부분이 많습니다.

어느 파일에서 무엇을 임포트 하는지 확실하게 명시하는 임포트 및 엑스포트를 갖추는 것이 좋습니다.

 

<script type="module" src="dist/app.js"></script>
"module": "es2015",

...

//"outFile":

 

html 파일과 tsconfig를 다음과 같이 바꾸고 시작합니다.

 

drag-drop.ts

export interface Draggable {
  dragStartHandler(event: DragEvent): void;
  dragEndHandler(event: DragEvent): void;
}

export interface DragTarget {
  dragOverHandler(event: DragEvent): void;
  dropHandler(event: DragEvent): void;
  dragLeaveHandler(event: DragEvent): void;
}

 

위와 같이 namespace를 뺀 나머지를 그대로 내버려 둡니다.

 

project-item.ts

import { Draggable } from "../models/drag-drop.js"; // 여기
import { Component } from "./base-component.js";
import { autobind } from "../decorators/autobind.js";
import { Project } from "../models/project.js";

export class ProjectItem
  extends Component<HTMLUListElement, HTMLLIElement>
  implements Draggable
{
  private project: Project;

  get persons() {
    if (this.project.people === 1) {
      return "1 person";
    } else {
      return `${this.project.people} persons`;
    }
  }

  constructor(hostId: string, project: Project) {
    super("single-project", hostId, false, project.id);
    this.project = project;

    this.configure();
    this.renderContent();
  }

  @autobind
  dragStartHandler(event: DragEvent) {
    event.dataTransfer!.setData("text/plain", this.project.id);
    event.dataTransfer!.effectAllowed = "move";
  }

  dragEndHandler(_: DragEvent) {
    console.log("DragEnd");
  }

  configure() {
    this.element.addEventListener("dragstart", this.dragStartHandler);
    this.element.addEventListener("dragend", this.dragEndHandler);
  }

  renderContent() {
    this.element.querySelector("h2")!.textContent = this.project.title;
    this.element.querySelector("h3")!.textContent = this.persons + " assigned";
    this.element.querySelector("p")!.textContent = this.project.description;
  }
}

 

그리고 다음과 같이 필요한 부분만 임포트 합니다. 기존에 이 방법만 이용하다 보니 별 다른 점은 없습니다.

다만 자바스크립트로 컴파일된 것을 임포트 해야 하기 때문에 자바스크립트 파일에서 임포트 합니다.

 

 

여러 가져오기 및 내보내기 구문

 

project-input.ts

import Cmp from "./base-component.js"; // 3
import * as Validation from "../util/validation.js"; // 1
import { autobind as Autobind } from "../decorators/autobind.js"; // 2
import { projectState } from "../state/project-state.js";

                                 // 3
export class ProjectInput extends Cmp<HTMLDivElement, HTMLFormElement> {
  titleInputElement: HTMLInputElement;
  descriptionInputElement: HTMLInputElement;
  peopleInputElement: HTMLInputElement;

  constructor() {
    super("project-input", "app", true, "user-input");
    this.titleInputElement = this.element.querySelector(
      "#title"
    ) as HTMLInputElement;
    this.descriptionInputElement = this.element.querySelector(
      "#description"
    ) as HTMLInputElement;
    this.peopleInputElement = this.element.querySelector(
      "#people"
    ) as HTMLInputElement;

    this.configure();
  }

  configure() {
    this.element.addEventListener("submit", this.submitHandler);
  }

  renderContent() {}

  private gatherUserInput(): [string, string, number] | undefined {
    const enteredTitle = this.titleInputElement.value;
    const enteredDescription = this.descriptionInputElement.value;
    const enteredPeople = this.peopleInputElement.value;

    const titleValidatable: Validation.Validatable = { // 1
      value: enteredTitle,
      required: true,
    };

    const descriptionValidatable: Validation.Validatable = {
      value: enteredDescription,
      required: true,
      minLength: 5,
    };

    const peopleValidatable: Validation.Validatable = {
      value: +enteredPeople,
      required: true,
      min: 1,
      max: 5,
    };

    if (
      // 1
      !Validation.validate(titleValidatable) ||
      !Validation.validate(descriptionValidatable) ||
      !Validation.validate(peopleValidatable)
    ) {
      alert("Invalid input, please try again!");
      return;
    } else {
      return [enteredTitle, enteredDescription, +enteredPeople];
    }
  }

  private clearInputs() {
    this.titleInputElement.value = "";
    this.descriptionInputElement.value = "";
    this.peopleInputElement.value = "";
  }

  // 2
  @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);
      this.clearInputs();
    }
  }
}

 

base-component.ts

       // 3
export default abstract class Component<T extends HTMLElement, U extends HTMLElement> {
  templateElement: HTMLTemplateElement;
  hostElement: T;
  element: U;

  constructor(
    templateId: string,
    hostElementId: string,
    insertAtStart: boolean,
    newElementId?: string
  ) {
    this.templateElement = document.getElementById(
      templateId
    )! as HTMLTemplateElement;
    this.hostElement = document.getElementById(hostElementId)! as T;

    const importedNode = document.importNode(
      this.templateElement.content,
      true
    );
    this.element = importedNode.firstElementChild as U;
    if (newElementId) {
      this.element.id = newElementId;
    }

    this.attach(insertAtStart);
  }

  private attach(insertAtBeginning: boolean) {
    this.hostElement.insertAdjacentElement(
      insertAtBeginning ? "afterbegin" : "beforeend",
      this.element
    );
  }

  abstract configure(): void;
  abstract renderContent(): void;
}

 

  1. 모든 것을 임포트해와 별칭을 넣고 객체처럼(dot notation) 사용할 수 있습니다.
  2. 특정 임포트 대상만 별칭을 넣을 수 있습니다.
  3. 디폴트 엑스포트를 설정해 아무 이름이나 사용하여 임포트 할 수 있습니다. 
    1. 파일당 하나의 디폴트 엑스포트만 사용할 수 있습니다.
    2. 디폴트가 아닌 엑스포트랑은 혼용이 가능합니다.

 

++ 임포트가 여러 파일에서 되었더라도 그 파일은 다른 어떤 파일에 의해 처음으로 임포트 되었을 때 1회만 실행됩니다.

 

'TYPESCRIPT' 카테고리의 다른 글

장소 선택 및 공유 구글 API 연습  (5) 2023.04.10
웹팩(Webpack) 사용하기  (6) 2023.04.07
연습 프로젝트 4  (6) 2023.04.04
연습 프로젝트 3  (6) 2023.04.03
연습 프로젝트 2  (13) 2023.03.31