스크립트 모듈화

제리·2023년 9월 22일
0

마이버추얼트립

목록 보기
3/9
post-thumbnail

목적이나 기능에 따라 파일을 분리해 모듈화를 적용한다.


Review

코드 자체는 크게 달라진 내용은 없지만 기능들을 조각내어 스크립트 모듈화를 적용했다. 처음 모듈화를 시도할 때는 멈칫했다. 보통 자바스크립트 문법 강의들은 간단한 예제를 이용해 설명하다 보니 1개의 파일에서 DOM 조작을 보여주는 설명이 많다. 어느 정도 이해를 한 뒤에 리액트로 넘어갔고, index.html 파일을 크게 신경 쓰지 않았다. 너무 당연하게 쓰느라 써야 하는 이유는 생각해 본 적이 없는 것 같다. 모듈화를 해보면서 "이건 왜 쓰지?", "없으면 무슨 에러가 나지?"로 접근하며 지금까지 익숙했던 것들이 모두 초면이 되는 경험을 했다.

타입스크립트와 클래스를 공부하다가 자바스크립트를 다시 돌아보게 되었던 터라 비교를 해가면서 고민했는데 내용이 짬뽕이 되어 어떤 경우에는 클래스가 좋았다가 또 다른 경우에는 함수가 좋았다가 오락가락했다. 함수형 프로그래밍은 흐름에 따라 전체적으로 그림이 잘 그려져서 구현이 조금 더 수월했는데 클래스의 상속과 타입스크립트의 인터페이스가 그리울 때가 종종 있었다.


Refactoring

  • 기능별 파일 분리
  • 이벤트 버블링을 이용한 수정/삭제 이벤트 제어

Demo

Demo 바로가기


Files

파일들을 분리하면서 리액트와 비슷하게 폴더 구조를 정리했다.


Markup

<main id="wrapper">
    <section>
        <div></div>
        <ul>
            <li></li>
            <li></li>
			...
        </ul>
    </section>
</main>

Check

  • shopping, packing 2개의 카테고리 DOM 요소를 자바스크립트로 생성한다.
  • shopping, packing 파일로 생성되는 Item의 마크업은 서로 다르다.
    • shopping Item에는 체크 기능이 없도록 한다.

Flow

  1. 앱이 실행되면 2개의 카테고리 Section이 생성된다.
  2. Section의 자식으로 메뉴와 리스트도 생성된다.
  3. 입력받은 카테고리에 따라 분기해서 각각의 Item 생성 함수를 호출한다.
  4. 카테고리에 맞는 List에 Item을 넣어준다.

Contents

01. main.js가 로드되면 즉시 실행되는 init 함수

const init = () => {
    const shopping = createSection('shopping', '탐욕 바구니 ✨');
    const packing = createSection('packing', '꼭 챙기기 🧳');
    $wrapper.append(shopping, packing);
};

다음 버전에서는 카테고리 메뉴도 사용자에게 입력받아 생성하는 것을 구현해 본다. 그전에 카테고리를 동적으로 그려보자.

export const createSection = (category, title) => {
    const section = document.createElement('section');
    section.innerHTML = `
        <div class="section__title" id="${category}__title">
        <h3>${title}</h3>
        </div>
    `;

    const sectionTitle = section.querySelector('.section__title');
    const list = createList(category);

    sectionTitle.addEventListener('click', () => {
        if (list.children.length === 0) {
    	    return;
        }
        sectionTitle.classList.toggle('active');
        list.classList.toggle('disabled');
    });

    section.append(list);
    return section;
};

아이템을 추가할 리스트는 카테고리별로 1개씩 가지기 때문에 섹션과 함께 생성된다.

02. 카테고리에 따라 아이템 생성 함수 호출

const addItem = (category, value) => {
    let item = null;
    if (category === 'shopping') {
        item = createShoppingItem(value);
    } else if (category === 'packing') {
        item = createPackingItem(value);
    }
    const list = setActiveState(category);
    list.append(item);
};

shopping과 packing 아이템을 생성하는 함수를 가진 파일은 전체적인 구조는 동일하지만, 마크업 부분이 다르기 때문에 카테고리에 따라 함수를 다르게 호출한다.

03. 수정/삭제 버튼에 이벤트 버블링 적용

item.addEventListener('click', (e) => {
    if (e.target.className === 'item__button--edit') {
        editItem(item);
    } else if (e.target.className === 'item__button--delete') {
        const title = item.parentNode.previousElementSibling;
        item.previousElementSibling ? '' : title.classList.remove('active');
        item.remove();
    }
});

수정/삭제 버튼을 가지고 있는 item에 이벤트를 등록하고 className을 이용해 분기했다.


Note

✳︎ 모듈(module)

script 태그 안에 모든 코드를 넣게 되면 코드 양이 많아질수록 관리가 어려워진다. 그럼 관련이 있는 기능끼리 스크립트를 분리하면 된다. 그런데 그냥 가져와 사용하면 분리를 했음에도 1개의 파일인 것처럼 동작한다. 전역 스코프를 갖기 때문이다.

모듈화를 하게 되면 각 파일들마다 별도의 모듈 스코프를 갖는다. 자바스크립트 자체는 모듈을 지원하지 않기 때문에 스크립트가 실행되는 환경이 모듈 기능을 지원하면 이를 활용할 수 있다.

  • require과 module.exports : Node.js / 서버 사이드 / CommonJS
  • import와 export : 브라우저 / 클라이언트 사이드 / ES Module

ES Module은 ES6부터 지원했기 때문에 이전까지는 Node.js가 제공하는 모듈 기능을 활용하였고, 이를 브라우저가 해석할 수 있도록 바벨과 같은 컴파일러가 필요했다. 그리고 Node.js 13.2 버전부터 ES Module을 지원하게 되면서 별도 컴파일 없이 HTML에서 바로 적용할 수 있게 됐다.

모듈화 시 주의할 점


모듈을 적용하면 모듈 스코프를 갖는다고 했다. 그렇기 때문에 여러 개의 모듈화 된 스크립트를 가져와도 그 안에 작성된 데이터를 외부에서 활용할 수 없다. 스크립트에서 내보내고(export) 가져와서(import) 사용해야 한다.

HTML에 1개의 스크립트를 적용하는 방법


a. html script 내부에 import 하기
b. entry point가 되는 1개의 파일 작성하기
모듈화 된 코드끼리 서로 상호작용하면서 최종적으로 프로그램이 화면에 그려지고 동작할 수 있도록 하는, entry point 역할의 파일을 만든다.


✳︎ 리액트의 텅 빈 html이 떠오른다.

비어있는 body 태그와 한 줄의 script를 보는데 문득 리액트의 <div id="root"></div>가 생각났다. 게다가 어디에도 <script> 태그가 없다. 간단하게 리액트처럼 entry point를 만들어보자.

리액트 index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);

클래스로 만들어 본 MyReactDom

class MyReactDom {
  constructor(){}
  
  createRoot(domNode){
    return {
      render: function(children){
        domNode.append(children)
      }
    }
  }
}
export default new MyReactDom()

다양한 메서드들이 있겠지만 render만 고려했다. createRoot 메서드는 root를 받아와(domNode) render 함수를 반환한다. (해당 코드는 실제 node만을 받도록 분기 처리를 하지 않았다.)

적용해보기

import App from './App.js';
import MyReactDom from './client.js'

const root = MyReactDom.createRoot(document.querySelector('#root'))
root.render(App)

render에 최종 App 컴포넌트를 넘겨주면서 리액트와 비슷한 구조를 만들었다. 동일하게 잘 실행된다.

profile
DOM과 친해지기

0개의 댓글

관련 채용 정보