인프런 커뮤니티 질문&답변
27과목 ejs로 todo list 만들기에서 todo를 여러 개 항목 만들었을 때
해결된 질문
작성
·
22
0
제가 따라한 실습 코드에서는
항목 클릭 했을 때 여러 개 todo 항목이 있을 경우
line-through 가 맨 뒤 항목에만 적용되는데
여러 항목을 렌더링 했을 경우 클릭한 doto를 click 시 못 찾아 가는 상황인가요?
답변 2
0
안녕하세요 Edwards님, 올려주신 내용을 확인해보니 어떤 상황인지 바로 이해가 되었습니다. 결론부터 말씀드리면 이는 코드가 틀렸다기보다는, 컴퓨터에게 '누구'를 건드려야 할지 정확히 지시하지 않아 발생하는 자연스러운 현상입니다.
우선 클릭 시 엉뚱하게 맨 뒤 항목만 반응하거나 동작이 꼬이는 현상의 근본적인 원인을 살펴보면, 이는 브라우저가 각 항목을 구별할 수 있는 '고유 식별자(Unique ID)'가 없기 때문입니다. HTML과 웹 표준의 규칙상 하나의 웹 페이지 내에서 id라는 속성값은 주민등록번호처럼 유일해야 합니다. 하지만 현재 작성하신 코드의 흐름을 보면 반복문(forEach)이 돌면서 생성되는 모든 체크박스와 라벨이 똑같은 id 이름을 갖게 되는 구조입니다. 예를 들어 교실에 '철수'라는 학생이 5명 있을 때 선생님이 "철수야!"라고 부르면 누구를 부르는지 알 수 없어 혼란이 생기는 것과 같습니다. 브라우저 또한 마찬가지여서, 사용자가 첫 번째 항목의 라벨을 클릭하더라도 내부적으로는 "이 ID를 가진 요소를 찾아라"라는 명령을 수행할 때 중복된 ID들 사이에서 길을 잃고, 브라우저의 처리 방식에 따라 맨 마지막에 생성된 요소를 선택하거나 첫 번째 요소만 바라보는 등 의도치 않은 동작을 하게 되는 것입니다.
이 문제를 근본적으로 해결하기 위해서는 서버인 app.js에서 데이터가 생성될 때부터 각 할 일(Todo)마다 세상에 하나뿐인 고유한 ID를 부여해주어야 합니다. 기존에는 사용자가 입력한 텍스트(문자열)만 단순히 배열에 담았다면, 이제는 텍스트와 ID, 그리고 완료 여부까지 포함된 '객체(Object)' 형태로 데이터를 관리해야 합니다. 아래 코드는 이러한 로직을 반영하여 수정한 app.js의 전체 내용입니다.
const path = require("path");
const express = require("express");
const app = express();
// 데이터를 저장할 배열입니다.
// 기존에는 문자열만 저장했지만, 이제는 {id, content, isDone} 형태의 객체가 저장됩니다.
let todos = [];
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static("public"));
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
// 메인 페이지 라우트: ID가 포함된 todo 목록을 화면(ejs)으로 전달합니다.
app.get("/", (req, res) => {
res.render("index.ejs", { todos: todos });
});
// 할 일 추가 라우트 (가장 중요한 변경점입니다)
app.post("/", (req, res) => {
const todoContent = req.body.todo;
// 빈 내용이 들어오는 것을 방지하기 위한 유효성 검사입니다.
if (todoContent) {
const newTodo = {
// 1. 고유 ID 생성: Date.now()는 현재 시간을 밀리초 단위 숫자로 반환하므로
// 겹칠 확률이 매우 낮아 간단한 ID로 쓰기에 적합합니다.
id: Date.now(),
// 2. 사용자가 입력한 할 일 내용
content: todoContent,
// 3. 완료 여부 상태 (기본값은 false)
isDone: false
};
todos.push(newTodo);
}
res.redirect("/");
});
// 삭제 기능 추가: ID를 기준으로 특정 항목만 정확히 삭제하는 라우트입니다.
app.post("/delete/:id", (req, res) => {
// URL 파라미터로 넘어온 ID는 문자열이므로 숫자로 변환해줍니다.
const deleteId = parseInt(req.params.id);
// 배열의 filter 함수를 사용해 해당 ID가 아닌 것들만 남깁니다 (즉, 해당 ID 삭제).
todos = todos.filter(todo => todo.id !== deleteId);
res.redirect("/");
});
app.listen(3000, () => {
console.log("Server is Listening on port 3000");
});
서버에서 데이터를 체계적으로 관리할 준비가 되었다면, 이제 사용자에게 보여지는 화면인 views/index.ejs 파일에서도 이 ID를 적극적으로 활용해야 합니다. 단순히 데이터를 뿌려주는 것을 넘어, HTML 태그의 속성에 서버에서 받은 ID를 결합하여 모든 태그가 서로 다른 이름을 갖도록 만들어야 합니다. 아래 코드는 수정된 index.ejs의 내용으로, id 속성과 for 속성에 ID를 동적으로 할당하는 방식에 주목해주시면 됩니다.
<!DOCTYPE html>
<html>
<head>
<title>To-Do List</title>
<style>
/* 완료된 항목에 취소선을 긋는 스타일입니다 */
.completed {
text-decoration: line-through;
color: gray;
}
</style>
</head>
<body>
<h1>My To-Do List</h1>
<form action="/" method="POST">
<input type="text" name="todo" placeholder="할 일을 입력하세요" required>
<button type="submit">추가</button>
</form>
<ul>
<% todos.forEach(function(item) { %>
<li>
<input type="checkbox" id="check-<%= item.id %>">
<label for="check-<%= item.id %>">
<%= item.content %>
</label>
<form action="/delete/<%= item.id %>" method="POST" style="display:inline;">
<button type="submit">삭제</button>
</form>
</li>
<% }); %>
</ul>
</body>
</html>
이렇게 코드를 수정하시면 각 항목이 자신만의 고유한 주소(ID)를 갖게 되어 클릭 시 정확하게 해당 항목만 반응하게 됩니다. 여기서 한 걸음 더 나아가 실제 현업 개발 환경에서는 이 로직을 어떻게 발전시키는지 실무적인 관점도 덧붙여 드리겠습니다.
우선 ID 생성 방식에 있어 학습용으로는 Date.now()가 훌륭하지만, 초당 수천 건의 요청이 들어오는 대규모 서비스에서는 우연히 같은 밀리초에 요청이 들어와 ID가 충돌할 수 있습니다. 그래서 실무에서는 UUID(범용 고유 식별자) 라이브러리를 사용하여 100%에 가깝게 중복을 방지합니다. 또한 현재 방식은 서버 메모리(변수)에 데이터를 저장하기 때문에 서버가 재시작되면 모든 데이터가 사라집니다. 이를 방지하기 위해 MySQL이나 MongoDB 같은 데이터베이스를 연동하여 데이터를 영구적으로 보존하는 것이 필수적입니다. 마지막으로 사용자 경험(UX) 측면에서, 현재는 항목을 지우거나 추가할 때마다 페이지가 새로고침되어 깜빡이는 현상이 있습니다. 실무에서는 AJAX(비동기 통신) 기술이나 React, Vue와 같은 프런트엔드 프레임워크를 도입하여, 페이지 전체를 다시 로드하지 않고 필요한 부분만 부드럽게 업데이트하는 방식을 사용합니다.
지금 겪으신 이 과정은 단순한 오류 해결이 아니라, 웹이 데이터를 어떻게 식별하고 관리하는지에 대한 가장 핵심적인 원리를 이해하는 과정입니다. 안내해 드린 코드를 차근차근 적용해 보시면서 데이터의 흐름을 느껴보시면 실력이 한층 더 성장하실 것입니다. 도움이 되셨기를 바랍니다.
감사합니다!
0
안녕하세요, 인프런 AI 인턴입니다. Edwards님.
ejs를 사용하여 todo list를 만드는 과정에서 여러 개의 항목 중 특정 항목만 클릭한 경우 해당 항목에만 line-through 스타일을 적용하고 싶으신 것 같습니다. 이 문제는 일반적으로 JavaScript의 이벤트 처리를 통해 해결할 수 있습니다.
상황 설명
여러 개의 todo 항목이 있는 경우 특정 항목을 클릭했을 때, 클릭한 해당 항목에만 스타일이 적용되도록 처리해야 합니다. 각 항목에 고유 식별자 혹은 데이터를 부여해 JavaScript에서 이를 활용하여 적절히 스타일을 변경하는 로직을 구현해야 합니다.
해결 방법
HTML 구조에서 각 항목에 식별자를 부여:
각 todo 항목에 고유한id나data-*속성을 부여하여, 클릭 이벤트에서 이를 참조할 수 있도록 합니다.JavaScript 이벤트에서 데이터 핸들링:
클릭 이벤트에서 현재 클릭된 요소의 데이터를 이용해 해당 요소에만line-through클래스를 적용하거나 스타일을 변경합니다.
<!-- EJS template example -->
<ul>
<% todos.forEach(function(todo, index) { %>
<li id="todo-<%= index %>" onclick="toggleComplete('<%= index %>')">
<%= todo.text %>
</li>
<% }); %>
</ul>
<script>
function toggleComplete(index) {
const todoElement = document.getElementById('todo-' + index);
if (todoElement.style.textDecoration === 'line-through') {
todoElement.style.textDecoration = 'none';
} else {
todoElement.style.textDecoration = 'line-through';
}
}
</script>
위 예제 코드에서 각 li 요소에 id 속성을 부여하여 각 항목을 식별하고, JavaScript toggleComplete 함수를 통해 클릭한 항목의 스타일만 변경하도록 구현하였습니다.
추가적으로 이와 관련된 팁이나 예제 코드를 찾고 있다면 다음과 같은 게시물이 도움이 될 수 있습니다:
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.




