![]()
실전 예제로 구현하는 JavaScript 좋아요 버튼 컴포넌트
앞선 시리즈에서 DOM 조작, 이벤트 처리, fetch 사용법, 모듈 구조까지 단계별로 살펴보았다. 이제 이 내용을 실제 UI 컴포넌트에 적용해보자. 대상은 가장 단순하면서도 실전성이 높은 좋아요 버튼이다. 상태 토글과 카운트 증가, 서버 반영까지 포함하면 프런트와 백엔드의 연결 구조를 한 번에 이해할 수 있다.
1. 기본 마크업 설계
좋아요 버튼은 크게 세 가지 정보가 필요하다. 어떤 대상에 대한 좋아요인지 식별하기 위한 id, 현재 좋아요 상태, 그리고 좋아요 수다. 이 정보는 dataset 속성에 담아두면 처리하기 편하다.
- data-id: 게시물 또는 대상의 고유 번호
- data-liked: 현재 좋아요 여부(0 또는 1)
- .like-count: 화면에 표시되는 좋아요 수
2. DOM과 이벤트 연결
버튼을 모두 선택한 뒤, 각 버튼에 클릭 이벤트를 연결한다. 이벤트 안에서는 데이터 상태를 읽고 다음 상태를 계산한 후 UI와 서버 요청을 처리한다.
document.querySelectorAll('.like-btn').forEach(btn => {
btn.addEventListener('click', () => {
handleLike(btn);
});
});
실제 로직은 handleLike 함수 내부에서 관리하면 코드가 더 깔끔해진다.
3. 좋아요 상태 전환 로직
좋아요 버튼의 핵심은 상태 전환이다. 현재 liked 값에 따라 다음 상태를 계산하고, 카운트를 증감시키며, UI를 업데이트한다.
function handleLike(btn) {
const id = btn.dataset.id;
const liked = btn.dataset.liked === '1';
const countEl = btn.querySelector('.like-count');
const iconEl = btn.querySelector('.like-icon');
let count = parseInt(countEl.innerText, 10) || 0;
const nextLiked = liked ? 0 : 1;
const nextCount = liked ? count - 1 : count + 1;
// UI를 먼저 반영
btn.dataset.liked = String(nextLiked);
countEl.innerText = nextCount;
btn.classList.toggle('active', nextLiked === 1);
iconEl.innerText = nextLiked === 1 ? '♥' : '♡';
// 서버에 비동기 저장
sendLikeRequest(id, nextLiked);
}
이 패턴은 사용자가 빠르게 반응을 느끼도록 UI를 먼저 업데이트하고, 이후 서버에 비동기 요청을 보내는 방식이다.
4. fetch를 이용한 서버 연동
서버에는 좋아요 상태를 저장하거나 로그로 남길 수 있다. 간단한 예제로는 id와 liked 값을 전달하는 방식이 가장 일반적이다.
function sendLikeRequest(id, liked) {
fetch('/api/like_toggle.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, liked })
})
.then(res => res.text())
.then(result => {
if (result !== 'OK') {
console.error('LIKE SAVE ERROR:', result);
}
})
.catch(err => {
console.error('LIKE FETCH ERROR:', err);
});
}
실제 운영 환경에서는 오류 발생 시 UI를 원상복구할지 여부를 비즈니스 규칙에 따라 결정하면 된다.
5. PHP에서의 처리 예시
백엔드(PHP)에서는 JSON으로 전달된 데이터를 받아 저장하거나 카운트를 업데이트한다. 아래는 단순화된 예시다.
<?php
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$id = isset($data['id']) ? (int)$data['id'] : 0;
$liked = isset($data['liked']) ? (int)$data['liked'] : 0;
if ($id <= 0) {
echo 'NG';
exit;
}
// 예시: 좋아요 테이블에 상태 저장 또는 카운트 업데이트
// update tbl_like set liked = {$liked} where id = {$id}
echo 'OK';
실제 프로젝트에서는 트랜잭션 관리, 사용자 식별, 중복 방지 등 추가 로직이 필요하지만 UI 흐름을 이해하기에는 위와 같은 구조만으로 충분하다.
6. 모듈 구조로 분리하기
프로젝트가 커질수록 좋아요 기능 역시 별도 모듈로 분리하는 것이 좋다. 앞서 다룬 모듈 구조를 적용하면 다음과 같은 형태가 될 수 있다.
// likeButton.js
export function initLikeButtons() {
document.querySelectorAll('.like-btn').forEach(btn => {
btn.addEventListener('click', () => handleLike(btn));
});
}
function handleLike(btn) {
// 앞서 정의한 로직과 동일
}
async function sendLikeRequest(id, liked) {
// fetch 요청 처리
}
그리고 init 파일에서 필요한 모듈만 불러와 초기화한다.
// init.js
import { initLikeButtons } from './likeButton.js';
window.addEventListener('DOMContentLoaded', () => {
initLikeButtons();
});
실전 컴포넌트 구현을 통해 얻는 것
좋아요 버튼은 단순한 예제처럼 보이지만, 실제로는 DOM 조작, 이벤트 처리, 상태 관리, 서버 통신, 모듈 구조까지 모두 포함하는 학습 소재다. 한 번 완성해 두면 이후 북마크, 즐겨찾기, 구독 버튼 등 다양한 기능에 쉽게 확장 적용할 수 있다.
이로써 JavaScript 실전 시리즈는 DOM, 이벤트, fetch, 모듈, 컴포넌트 구현까지 전체 흐름을 한 번에 정리하게 된다. 이후에는 각 프로젝트에 맞추어 패턴을 변형하고 확장해 나가는 것이 다음 단계다.