프론트JSP
6

JSP/jQuery 화면에서 이벤트 중심 코드를 페이지 단위로 정리하기

페이지 하나를 기준으로 역할별로 정리

JSP/jQuery 화면에서 이벤트 중심 코드를 페이지 단위로 정리하기

JSP/jQuery 화면에서 이벤트 중심 코드를 페이지 단위로 정리하기

JSP와 jQuery 기반 화면을 개발하다 보면 처음에는 단순한 클릭 이벤트 하나로 시작한다.

예를 들어 버튼을 클릭하면 값을 가져오고, input에 값이 입력되면 유효성 검사를 하고, 저장 버튼을 누르면 서버로 데이터를 전송하는 식이다.

처음에는 이벤트가 몇 개 없기 때문에 큰 문제가 없어 보인다. 하지만 화면 기능이 늘어나면 클릭, input, change, Ajax, 유효성 검사, CSS 변경, 저장 로직이 한 파일 안에 섞이기 시작한다.

이때 코드를 이벤트 단위로만 작성하면 어느 시점에 어떤 값이 바뀌는지 파악하기 어려워진다.


1. 기존에 작성하던 이벤트 단위 방식

기존에는 보통 이벤트가 필요할 때마다 바로 코드를 작성했다.

$(function () {
  $("#recommendCode").on("input", function () {
    const code = $("#recommendCode").val();

    $("#recommendMsg")
      .text("")
      .removeClass("success error is-visible");
  });

  $("#checkBtn").on("click", function () {
    const code = $("#recommendCode").val();

    if (!/^[A-Za-z0-9]{6}$/.test(code)) {
      $("#recommendMsg")
        .text("추천인 코드는 영문/숫자 6자리입니다.")
        .removeClass("success")
        .addClass("error is-visible");
      return;
    }

    $.ajax({
      url: "/recommend/check",
      type: "POST",
      data: JSON.stringify({ recommendCode: code }),
      contentType: "application/json",
      success: function (result) {
        if (result === "VALID") {
          $("#recommendMsg")
            .text("사용 가능한 추천인 코드입니다.")
            .removeClass("error")
            .addClass("success is-visible");
        } else {
          $("#recommendMsg")
            .text("사용할 수 없는 추천인 코드입니다.")
            .removeClass("success")
            .addClass("error is-visible");
        }
      },
    });
  });

  $("#saveBtn").on("click", function (e) {
    e.preventDefault();

    // 저장 처리
  });
});

이 방식은 빠르게 구현하기에는 편하다.

하지만 이벤트가 많아지면 다음과 같은 문제가 생긴다.

  • 이벤트 안에 너무 많은 로직이 들어간다.
  • 유효성 검사, Ajax, CSS 변경 코드가 섞인다.
  • 같은 DOM 제어 코드가 여러 곳에 반복된다.
  • Ajax 응답이 오기 전에 다음 로직이 실행될 수 있다.
  • 저장 시점에 어떤 값이 최종 상태인지 헷갈린다.

즉, 코드는 동작하지만 유지보수하기 어려운 구조가 된다.


2. 이벤트 단위와 페이지 단위의 차이

이벤트 단위는 각각의 사용자 행동을 기준으로 코드를 작성하는 방식이다.

input 입력 이벤트
버튼 클릭 이벤트
체크박스 change 이벤트
저장 버튼 클릭 이벤트

반면 페이지 단위는 하나의 화면을 기준으로 JS 코드를 정리하는 방식이다.

회원정보 수정 페이지
 ├─ 상태값 관리
 ├─ 초기 데이터 조회
 ├─ 이벤트 등록
 ├─ 유효성 검사
 ├─ Ajax 요청
 ├─ 화면 변경
 └─ 저장 처리

즉, 이벤트 하나하나가 아니라 이 화면이 어떤 기능을 가지고 있고, 어떤 흐름으로 동작하는지를 기준으로 코드를 구성한다.


3. 페이지 단위로 정리한 방식

같은 추천인 확인 기능을 페이지 단위로 정리하면 다음과 같다.

const memberModifyPage = {
  state: {
    recommendCode: "",
    recommendChecked: false,
  },

  init() {
    this.bindEvents();
  },

  bindEvents() {
    $("#recommendCode").on("input", this.handleRecommendInput.bind(this));
    $("#checkBtn").on("click", this.handleRecommendCheck.bind(this));
    $("#saveBtn").on("click", this.handleSave.bind(this));
  },

  handleRecommendInput(e) {
    const value = e.target.value;

    this.state.recommendCode = value;
    this.state.recommendChecked = false;

    this.hideRecommendMessage();
  },

  async handleRecommendCheck() {
    const code = this.state.recommendCode;

    if (!this.validateRecommendCode(code)) {
      this.showRecommendError("추천인 코드는 영문/숫자 6자리입니다.");
      return;
    }

    const result = await this.checkRecommendCode(code);

    if (result === "VALID") {
      this.state.recommendChecked = true;
      this.showRecommendSuccess("사용 가능한 추천인 코드입니다.");
      return;
    }

    this.state.recommendChecked = false;
    this.showRecommendError("사용할 수 없는 추천인 코드입니다.");
  },

  async handleSave(e) {
    e.preventDefault();

    if (!this.state.recommendChecked) {
      this.showRecommendError("추천인 확인을 먼저 해주세요.");
      return;
    }

    const param = this.makeSaveParam();

    await this.saveData(param);

    alert("저장되었습니다.");
  },

  validateRecommendCode(code) {
    return /^[A-Za-z0-9]{6}$/.test(code);
  },

  makeSaveParam() {
    return {
      recommendCode: this.state.recommendCode,
    };
  },

  checkRecommendCode(code) {
    return $.ajax({
      url: "/recommend/check",
      type: "POST",
      data: JSON.stringify({ recommendCode: code }),
      contentType: "application/json",
    });
  },

  saveData(param) {
    return $.ajax({
      url: "/member/save",
      type: "POST",
      data: JSON.stringify(param),
      contentType: "application/json",
    });
  },

  showRecommendSuccess(message) {
    $("#recommendMsg")
      .text(message)
      .removeClass("error")
      .addClass("success is-visible");
  },

  showRecommendError(message) {
    $("#recommendMsg")
      .text(message)
      .removeClass("success")
      .addClass("error is-visible");
  },

  hideRecommendMessage() {
    $("#recommendMsg")
      .text("")
      .removeClass("success error is-visible");
  },
};

$(function () {
  memberModifyPage.init();
});

4. 구조를 나누면 어떤 점이 좋아질까?

1. 이벤트 등록 위치가 명확해진다

기존에는 이벤트 코드가 여기저기 흩어질 수 있었다.

$("#checkBtn").on("click", function () {});
$("#saveBtn").on("click", function () {});
$("#recommendCode").on("input", function () {});

페이지 단위로 정리하면 이벤트 등록은 bindEvents()에서만 관리한다.

bindEvents() {
  $("#recommendCode").on("input", this.handleRecommendInput.bind(this));
  $("#checkBtn").on("click", this.handleRecommendCheck.bind(this));
  $("#saveBtn").on("click", this.handleSave.bind(this));
}

이렇게 하면 이 화면에서 어떤 이벤트를 사용하고 있는지 한눈에 볼 수 있다.


2. 화면 상태를 state에서 관리할 수 있다

기존 방식에서는 값을 전역 변수로 만들거나, 필요할 때마다 DOM에서 다시 꺼내 쓰기 쉽다.

let recommendChecked = false;

페이지 단위에서는 화면에서 관리해야 하는 값을 state에 모아둔다.

state: {
  recommendCode: "",
  recommendChecked: false,
}

이렇게 하면 저장 시점에 어떤 값이 필요한지 명확해진다.


3. 이벤트 함수는 흐름만 담당한다

기존에는 클릭 이벤트 안에 유효성 검사, Ajax, CSS 변경, 저장 로직이 모두 들어갔다.

페이지 단위에서는 이벤트 함수가 전체 흐름만 담당한다.

async handleRecommendCheck() {
  const code = this.state.recommendCode;

  if (!this.validateRecommendCode(code)) {
    this.showRecommendError("추천인 코드는 영문/숫자 6자리입니다.");
    return;
  }

  const result = await this.checkRecommendCode(code);

  if (result === "VALID") {
    this.state.recommendChecked = true;
    this.showRecommendSuccess("사용 가능한 추천인 코드입니다.");
    return;
  }

  this.state.recommendChecked = false;
  this.showRecommendError("사용할 수 없는 추천인 코드입니다.");
}

이렇게 작성하면 코드를 읽을 때 흐름이 명확하다.

값 확인
↓
유효성 검사
↓
서버 확인
↓
결과에 따라 상태 변경
↓
화면 메시지 변경

4. Ajax 비동기 흐름을 제어하기 쉬워진다

Ajax는 서버 응답을 기다리지 않고 다음 코드가 먼저 실행될 수 있다.

그래서 다음과 같은 코드는 문제가 될 수 있다.

let isChecked = false;

checkRecommendCode();

if (isChecked) {
  saveData();
}

추천인 확인 결과가 오기 전에 if문이 먼저 실행될 수 있기 때문이다.

페이지 단위 구조에서는 Ajax 함수를 분리하고, 필요한 곳에서 await를 사용해 순서를 보장한다.

const result = await this.checkRecommendCode(code);

이렇게 하면 서버 응답을 받은 뒤 다음 로직을 실행할 수 있다.


5. 화면 변경 코드가 반복되지 않는다

기존 방식에서는 성공 메시지, 실패 메시지, 숨김 처리를 이벤트 안에서 매번 작성하기 쉽다.

$("#recommendMsg")
  .text("사용 가능한 코드입니다.")
  .removeClass("error")
  .addClass("success is-visible");

이 코드를 여러 곳에서 반복하면 나중에 CSS 클래스명이 바뀌었을 때 수정해야 할 곳이 많아진다.

그래서 화면 변경 로직은 함수로 분리하는 것이 좋다.

showRecommendSuccess(message) {
  $("#recommendMsg")
    .text(message)
    .removeClass("error")
    .addClass("success is-visible");
}

showRecommendError(message) {
  $("#recommendMsg")
    .text(message)
    .removeClass("success")
    .addClass("error is-visible");
}

hideRecommendMessage() {
  $("#recommendMsg")
    .text("")
    .removeClass("success error is-visible");
}

5. 정리

기존에 작성하던 방식은 이벤트가 발생할 때마다 바로 로직을 작성하는 이벤트 단위 방식이었다.

이 방식은 빠르게 구현하기에는 좋지만, 기능이 많아질수록 코드가 복잡해진다.

반면 페이지 단위 방식은 하나의 화면을 기준으로 상태, 이벤트, 유효성 검사, Ajax, 화면 변경, 저장 로직을 역할별로 나누는 방식이다.

이벤트 단위 = 사용자 행동 하나하나를 기준으로 작성
페이지 단위 = 하나의 화면 기능 흐름을 기준으로 작성

JSP와 jQuery 환경에서는 React나 Vue처럼 상태가 바뀌면 화면이 자동으로 갱신되지 않는다. 따라서 상태 관리와 DOM 변경을 개발자가 직접 신경 써야 한다.

그래서 화면이 복잡해질수록 다음 기준을 잡고 작성하는 것이 좋다.

  • 이벤트 등록은 bindEvents()에 모은다.
  • 화면에서 관리할 값은 state에 모은다.
  • 이벤트 함수는 전체 흐름만 담당하게 한다.
  • 유효성 검사는 validate 함수로 분리한다.
  • Ajax 요청은 별도 함수로 분리한다.
  • 서버 응답 순서가 중요하면 await를 사용한다.
  • CSS와 메시지 변경은 UI 함수로 분리한다.

결국 중요한 것은 JS 코드를 무조건 짧게 만드는 것이 아니라, 각 코드가 어떤 역할을 하는지 명확하게 나누는 것이다.

댓글

(0)
JSP/jQuery 화면에서 이벤트 중심 코드를 페이지 단위로 정리하기 | 강민석의 개발블로그