| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- component
- emit
- SFC
- TODO
- goroutine
- Refactoring
- todo-list
- Server
- reactivity
- Matrix
- Vue.js
- graceful shutdown
- method
- map
- PROPS
- toggle
- cli
- container
- App.vue
- 행렬
- Vue
- localStorage
- Dictionary
- CDN
- golang
- URL
- goroutines
- go
- websocket
- channel
Archives
- Today
- Total
ksundev 님의 블로그
[TODO App][Refactoring] App 컴포넌트를 컨테이너로 만들기 3 본문
할일 완료 상태 토글 기능 구현하기
이번 포스트에서는 할 일의 완료 상태를 토글하는 기능을 구현해보겠습니다. 특히 Vue.js의 반응형 시스템과 관련된 중요한 개념인 RMT 패턴에 대해서도 함께 알아보겠습니다.
1. 현재 상황과 해결해야 할 문제
현재 TodoList 컴포넌트의 상태
현재 TodoList.vue는 프레젠테이션 컴포넌트로 변경되어 데이터 관리 기능이 App 컴포넌트로 이동했습니다. 하지만 완료 상태 토글 기능은 아직 구현되지 않았습니다.
해결해야 할 문제
- 체크 아이콘 클릭 시 완료 상태 변경
- 완료된 할 일의 시각적 표시 (취소선, 색상 변경)
- localStorage에 변경된 상태 저장
2. App 컴포넌트에 토글 메서드 추가하기
App.vue에 toggleComplete 메서드 추가
<template>
<div id="app">
<TodoHeader />
<TodoInput @addTodo="addOneItem" />
<TodoList :todoItems="todoItems" @removeItem="removeOneItem" @toggleComplete="toggleComplete" />
<TodoFooter />
</div>
</template>
<script>
import TodoHeader from "./components/TodoHeader.vue";
import TodoInput from "./components/TodoInput.vue";
import TodoList from "./components/TodoList.vue";
import TodoFooter from "./components/TodoFooter.vue";
export default {
data() {
return {
todoItems: [],
};
},
created() {
if (localStorage.length > 0) {
for (var i = 0; i < localStorage.length; i++) {
if (localStorage.key(i) !== "loglevel:webpack-dev-server") {
this.todoItems.push(
JSON.parse(localStorage.getItem(localStorage.key(i)))
);
}
}
}
},
methods: {
addOneItem(todoItem) {
var obj = { completed: false, item: todoItem };
localStorage.setItem(todoItem, JSON.stringify(obj));
this.todoItems.push(obj);
},
removeOneItem(todoItem, index) {
localStorage.removeItem(todoItem.item);
this.todoItems.splice(index, 1);
},
// 완료 상태 토글 메서드
toggleComplete(todoItem, index) {
this.todoItems[index].completed = !this.todoItems[index].completed;
},
},
components: {
TodoHeader,
TodoInput,
TodoList,
TodoFooter,
},
};
</script>
toggleComplete 메서드 분석
toggleComplete(todoItem, index) {
this.todoItems[index].completed = !this.todoItems[index].completed;
},
3. RMT 패턴과 접근 방식의 차이점
방법 1: Props 객체 직접 변경 (문제가 될 수 있음)
// TodoList.vue에서 props로 받은 객체 직접 변경
methods: {
toggleComplete(todoItem, index) {
todoItem.completed = !todoItem.completed; // 문제: Vue가 감지하지 못할 수 있음
}
}
방법 2: 반응형 데이터 직접 접근 (권장)
// App.vue에서 반응형 데이터 직접 변경
methods: {
toggleComplete(todoItem, index) {
this.todoItems[index].completed = !this.todoItems[index].completed; // 권장: Vue가 확실히 감지
}
}
4. 왜 방법 2가 더 좋은가?
RMT 패턴의 원칙
- R(React): 반응형 데이터에 직접 접근
- M(Mutation): 데이터 변경
- T(Trigger): Vue의 반응형 시스템이 변경을 감지
방법 1의 문제점
// Props로 받은 객체는 Vue의 반응형 시스템 밖에 있을 수 있음
props: {
todoItems: {
type: Array,
required: true,
},
},
methods: {
toggleComplete(todoItem, index) {
// todoItem은 props의 참조일 수 있어서 Vue가 감지하지 못할 수 있음
todoItem.completed = !todoItem.completed;
}
}
방법 2의 장점
// this.todoItems는 확실히 반응형 데이터
data() {
return {
todoItems: [], // Vue가 완전히 추적하는 반응형 데이터
};
},
methods: {
toggleComplete(todoItem, index) {
// Vue의 반응형 시스템이 확실히 감지
this.todoItems[index].completed = !this.todoItems[index].completed;
}
}
5. 실제 동작 과정 비교
방법 1: Props 접근 시
// 1. Props로 객체 참조 받음
const todoItem = this.todoItems[0]; // 부모에서 온 참조
// 2. 객체 속성 변경
todoItem.completed = true; // 객체는 변경되지만
// 3. Vue의 반응: "이건 props에서 온 거라서 내가 추적하지 않을게"
// 결과: UI 업데이트 안됨
방법 2: 직접 접근 시
// 1. 내가 관리하는 배열에서 직접 접근
const targetItem = this.todoItems[0]; // 내가 관리하는 데이터
// 2. 객체 속성 변경
this.todoItems[0].completed = true; // 내 데이터 변경
// 3. Vue의 반응: "아! 내가 관리하는 데이터가 변경됐구나!"
// 결과: UI 업데이트됨
6. TodoList 컴포넌트에서 이벤트 발생
TodoList.vue 수정
<template>
<div>
<ul>
<li v-for="(todoItem, index) in todoItems" :key="index" class="shadow">
<i
class="checkBtn fa-solid fa-check"
:class="{ checkBtnCompleted: todoItem.completed }"
@click="$emit('toggleComplete', todoItem, index)"
></i>
<span :class="{ textCompleted: todoItem.completed }">{{
todoItem.item
}}</span>
<span class="removeBtn" @click="$emit('removeItem', todoItem, index)">
<i class="fa-solid fa-trash-can"></i>
</span>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
todoItems: {
type: Array,
required: true,
},
},
};
</script>
이벤트 발생 구조
<!-- 체크 아이콘 클릭 시 부모에게 이벤트 발생 -->
@click="$emit('toggleComplete', todoItem, index)"
7. 완성된 토글 기능의 동작
데이터 흐름
- 사용자 클릭: 체크 아이콘 클릭
- 이벤트 발생: TodoList에서
toggleComplete이벤트 발생 - 메서드 호출: App 컴포넌트의
toggleComplete메서드 실행 - 데이터 변경:
this.todoItems[index].completed값 토글 - UI 업데이트: Vue의 반응형 시스템이 자동으로 화면 업데이트
시각적 피드백
- 완료되지 않은 할 일: 체크 아이콘 파란색, 텍스트 검정색
- 완료된 할 일: 체크 아이콘 회색, 텍스트 취소선과 회색
8. RMT 패턴의 핵심 교훈
같은 객체, 다른 접근 방식
// 같은 객체를 참조하지만
const todoItem = this.todoItems[0]; // props 참조
this.todoItems[0]; // 직접 접근
// Vue의 반응형 시스템에서는 다른 의미
todoItem.completed = true; // Props 간접 접근 (Vue 추적 안함)
this.todoItems[0].completed = true; // 직접 접근 (Vue 확실히 추적)
결론
RMT 패턴을 고려하면 반응형 데이터에 직접 접근하는 방법 2가 더 안전하고 확실합니다. 이 방법은 Vue의 반응형 시스템과 완벽하게 호환되어 모든 상황에서 안정적으로 작동하며, UI 업데이트가 보장됩니다.
다음 포스트에서는 전체 초기화 기능을 추가하여 Todo 앱을 완전히 동작하는 상태로 만들어보겠습니다!
'[개발] Vue.js > 중급' 카테고리의 다른 글
| [Vue][퀴즈] 10문제 (0) | 2025.07.09 |
|---|---|
| [TODO App][Refactoring] App 컴포넌트를 컨테이너로 만들기 4 (5) | 2025.07.09 |
| [TODO App][Refactoring] App 컴포넌트를 컨테이너로 만들기 2 (0) | 2025.07.07 |
| [TODO App][Refactoring] App 컴포넌트를 컨테이너로 만들기 1 (0) | 2025.07.07 |
| [TODO App][Refactoring] 현재 앱의 문제점 (1) | 2025.07.07 |