Frontend

순수 JS로 모듈 컴포넌트 만들기

비비빅B 2023. 6. 12. 00:45

중복되는 컴포넌트

 

컴포넌트 중복이 가장 많은 곳은 프론트영역이다. 달력, 입력박스, 안내창 등은 대부분의 화면에서 공통적인 기능으로 사용된다. 스타일은 물론 로직(이벤트처리)이 같은 컴포넌트도 많을 것이다.

프론트에서 공통 컴포넌트를 개발하고 관리하는 것이 중요하지만, 현재 진행 중인 프로젝트에서는 그러지 못했다. 서버에서 관리하는 컴포넌트의 경우 jsp tag 라이브러리를 이용해 어느정도 관리가 됐지만, 화면에서 관리해야 하는 컴포넌트(Clinent Side Rendering)의 경우 전혀 관리되지 못했다.

 

똑같은 기능을 하는 컴포넌트들이 각자 개발되다보니, 작은 변경 하나도 모든 화면을 수정해야 했다. 유지보수 지옥이나 다름없었다.😭

사실 이렇게까지 된 이유는 별다른 방법이 없다고 생각했기 때문이다. React, Vue와 같은 라이브러리 없이 컴포넌트를 공통으로 관리하는 것은 힘들다고 생각했다. 또한 현 프로젝트에서는 IE11 버전 지원, 모듈 스크립트 작성 불가능(솔루션 라이브러리 사용 조건)으로 인해 `import`, `export` 구문을 사용 못하다 보니, 별다른 방안이 생각나지 않았다.

 

그러다 최근에 재미로 지원했던 프로그래머스 프론트 과제 테스트에서 그 해답을 찾아 글로 정리해보려 한다.


컴포넌트 모듈 만들기

개별 컴포넌트

 

직원들의 이메일을 입력받는 입력박스가 있어야 한다고 가정해 보자. 입력이 끝나면 간단하게 이메일형식을 확인해 알려주는 기능도 필요하다. 개별적으로 개발한다면 대부분 아래와 같이 개발될 것이다.

 

<html>
<head>
  <title>bbbicb</title>
  <meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
  <style>
    .input__non_valid {
      background-color: red;
    }
  </style>
</head>
<body>
  <main class="App">
    <form>
      <input type="email" id="emailBox" name="email">
    </form>
  </main>
</body>
</html>

<script>
  document.getElementById("emailBox")?.addEventListener("input", (e) => {
    console.count("email input event");
    const emailRegexPattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
    const nonValidClazz = "input__non_valid"

    const inputNode = e.target
    const isValid = inputNode.value.match(emailRegexPattern);
    if (!isValid) {
      inputNode.className = nonValidClazz;
    } else {
      inputNode.classList.remove(nonValidClazz);
    }
  })
</script>

이메일 형식 검사까지 잘 되는 모습

 

현재 기능은 제대로 동작하더라도, 유지보수의 관점에서 보면 상당히 골치 아픈 상황이다.

위와 같은 코드가 모든 화면에 있다고 생각하면, 작은 수정에도 전체 화면을 수정하고 테스트해야 하는 상황이 돼버린다.

 

아래의 요구사항이 추가로 생겼다고 가정해 보자.

  1. 빈값이면 "이메일을 입력해 주세요"라는 문구를 넣어주세요.
  2. 이메일 도메인 셀렉트박스를 옆에 보여주고, "직접입력"을 기본으로 해주세요.

 

위와 같이 개발했다면 이런 요구사항을 반영하기 위해서, 모든 화면을 하나하나 수정해야 해서 유지보수 지옥이나 다름없다.


공통 컴포넌트

기존기능 구현

 

우선 이전에 개별적으로 구현했던 기능을 공통 컴포넌트로 변경했다. 

// EmailInput.js

export default function EmailInput({target, initialState}) {
  const EMAIL_REGEX_PATTERN = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;

  this.elelemt = document.createElement("form");
  target.appendChild(this.elelemt);

  this.state = initialState;
  this.setState = (nextState) => {
    this.state = {...this.state, ...nextState};
    this.render();
  }

  this.render = () => {
    this.elelemt.innerHTML = `
      <input type="email" class="${this.state.clazz}" id="${this.state.id}" name="${this.state.name}" value="${this.state.value ?? ""}">
    `;
  }

  this.render();
  this.elelemt.addEventListener("input", e => {
    console.count("email input event");
    const inputNode = e.target
    const isValid = inputNode.value.match(EMAIL_REGEX_PATTERN);
    inputNode.className = isValid ? "" : "input__non_valid";
  });
}

 

이제 다른 화면에서 아래와 같이 모듈을 생성하기만 하면 동일하게 동작한다.

 

...
<body>
  <main class="App"></main>
</body>
</html>

<script type="module" >
  import EmailInput from './EmailInput.js'
  new EmailInput({
    target: document.querySelector(".App"),
    initialState: {
      id: "emailBox",
      name: "email"
    }});
</script>

요구사항 반영

 

공통 컴포넌트로 구현해 놓았기 때문에, 모든 화면을 수정할 필요 없이 `EmailInput` 컴포넌트만 수정하면 된다.

 

// EmailInput.js
  this.render = () => {
    this.elelemt.innerHTML = `
      <input type="email" class="${this.state.clazz}" id="${this.state.id}" name="${this.state.name}" value="${this.state.value ?? ""}"
        placeholder="이메일을 입력해주세요.">
      <select>
        <option value="">직접입력</option>
        <option value="@gmail.com">구글</option>
        <option value="@naver.com">네이버</option>
      </select>
    `;
  }

  this.render();
  this.elelemt.addEventListener("input", e => {
    console.count("email input event");
    const inputNode = this.elelemt.querySelector("input");
    const selectNode = this.elelemt.querySelector("select");
    const isValid = (inputNode.value + selectNode[selectNode.selectedIndex].value).match(EMAIL_REGEX_PATTERN);
    inputNode.className = isValid ? "" : "input__non_valid";
  });

 

이메일 형식 검사를 셀렉트박스에 맞게 하는 모습


기본기의 중요성

 

코드 스타일이 리액트 라이브러리와 비슷하다는 것에서 볼 수 있듯이, 리액트에서 제공하는 상태기반 컴포넌트를 바닐라 자바스크립트로 구현한 것이다. 모듈 방식의 자바스크립트 지식이 부족하다 보니, 리액트를 사용하지 않고는 컴포넌트를 관리하는 게 어렵다고 생각했다.

현재 진행하는 프로젝트에서 초반에 이걸 알고 정리했다면, 좀 더 좋은 유지보수 환경을 만들 수 있었을 것 같은데 아쉽다.

 

흔히 리액트 배우기 전에 자바스크립트로 먼저 해보라는 말을 강의에서 듣곤 하는데, 이번에 그 이유를 확실히 알 수 있었다.

'Frontend' 카테고리의 다른 글

Redux test를 위한 export, default export 구분  (0) 2020.11.08
CSS 세로로 글쓰기  (0) 2020.04.02
Grid template minmax, auto-fill, auto-fit  (0) 2020.03.23