https://vuejs.org/examples/#todomvc
Examples 의 Practical 의 가장 마지막 단계를 참고하였다.
Todo 도 어떻게 보면 기본적인 CRUD 기능과 그 외 Filter 와 CSS 기능이라고 볼 수 있다.
이번 포스팅은 작은 단위의 상태 state 들부터 설명하며 커지는 기능들에 대해 코드와 함께 설명을 하고자 한다. CSS 기능은 @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; 단지 import 하였기 때문에 큰 설명은 없을 계획이다.
import { ref, computed, watchEffect } from "vue";
const STORAGE_KEY = "vue-todomvc";
//state
const todos = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"));
const visibility = ref("all");
const editedTodo = ref();
const filters = {
all: (todos) => todos,
active: (todos) => todos.filter((todo) => !todo.completed), //완료되지 않음
completed: (todos) => todos.filter((todo) => todo.completed), //완료됨
};
//persist state
watchEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos.value));
});
//derived state
const filteredTodos = computed(() => filters[visibility.value](todos.value));
const remaining = computed(() => filters.active(todos.value).length); //현재 화면에 표시되는 할 일 목록에서 아직 완료되지 않은 할 일의 수를 동적으로 추적
todos : 먼저 기본적인 state 로는 todos 로 해당 로컬 스토리지에 저장하고 값을 불러온다. 이는 외부 저장소에 상태를 저장함으로써 지속 상태를 구현할 수 있다.
지속 상태(persist state)란?
어떤 상태가 시스템이나 애플리케이션을 종료하고 다시 시작해도 유지되는 상태를 의미한다. 일반적으로 로컬 스토리지, 세션 스토리지, 또는 데이터베이스와 같은 외부 저장소에 상태를 저장함으로써 지속 상태를 구현할 수 있다. 지속 상태는 사용자가 애플리케이션을 종료되어도 데이터를 유지하고 다시 로드할 수 있고 즉, 이전 상태를 복원하고 유지하려는 목적으로 사용된다.
따라서, watchEffect 함수를 이용해 todos 라는 반응형 상태의 변경을 감시하고 해당 상태가 변경될 때마다 로컬 스토리지에 저장하도록 한다.
filters : todos 리스트들을 각각 체크 박스를 통해 완료유무를 판별하는데 이를 active, completed, all 세 가지 상태로 분류하여 filters 라는 객체로 표현한다.
filteredTodos : filters 객체 안에서 visibility.value(all, active, completed 중 하나)를 적용한 할 일 목록을 계산하고 반환한다.
remaining : filters 객체 안에서 visibility.value 가 active 인 즉, 완료되지 않은 할 일의 수를 동적으로 추적한다.
파생 상태(Derived State)란?
주어진 데이터나 상태로부터 계산되거나 유도된 상태를 의미한다. Vue 에서는 computed 개념을 사용하여 파생 상태를 쉽게 생성할 수 있다.원본 상태의 변화에 따라 자동으로 업데이트된다.

<input
class="new-todo"
autofocus
placeholder="What nedds to be done?"
@keyup.enter="addTodo" />
input 에 keypress event 를 걸어줘서 enter 가 눌렀을 때를 캐치하여 addTodo 함수를 연결해준다.
function addTodo(e) {
const value = e.target.value.trim();
if (value) {
todos.value.push({
id: Date.now(),
title: value,
completed: false,
});
e.target.value = "";
}
}
addTodo 함수는 input의 value 값을 가져와 todos 상태에 객체로 추가해준다.
먼저 코드를 보여주고 설명을 해보겠다.
<section class="main" v-show="todos.length">
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
class="todo"
:key="todo.id"
:class="{ completed: todo.completed, editing: todo === editedTodo }">
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed" />
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input
v-if="todo === editedTodo"
class="edit"
type="text"
v-model="todo.title"
@vue:mounted="({ el }) => el.focus()"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)"
/>
</li>
</ul>
</section>
section - v-show : 가장 큰 틀인 section 태그에서 v-show 바인딩은 todos 배열의 길이가 0보다 큰 경우 , 즉 존재할 경우에만 요소들을 보여주고, 그렇지 않을 때는 숨기는 조건부 렌더링을 위한 디렉티브이다. 이렇게 함으로써 할 일 목록이 비어있는지 여부에 따라 해당 요소를 동적으로 보이거나 숨길 수 있다.filteredTodos : computed 를 이용해 현재 선택된 필터 함수를에 따라 걸려저 동적으로 추적된 배열안에서 리스트를 나열할 수 있도록 한다. 각각의 할 일의 completed 와 편집 상태에 따라 class 가 동적으로 변경되고 그에 따라 style 도 변경된다. Read 기능에서 코드를 살펴보면 key event 에 연결된 함수를 4가지를 확인할 수 있다. 이 중 update 와 관련된 함수는 3가지로 editTodo(todo), doneEdit(todo), cancelEdit(todo) 를 설명해보겠다.
let beforeEditCache = "";
function editTodo(todo) {
beforeEditCache = todo.title;
editedTodo.value = todo;
//옮겨놓기
}
function doneEdit(todo) {
if (editedTodo.value) {
//옮겨놓은 todo 다시 옮기기
editedTodo.value = null;
todo.title = todo.title.trim();
if (!todo.title) removeTodo(todo);
//값이 없다면 삭제
}
}
function cancelEdit(todo) {
editedTodo.value = null;
todo.title = beforeEditCache;
}
editTodo(todo) : @dblclick 더블클릭 key event 일 경우, 해당 할 일 todo 를 편집을 하도록 한다. beforeEditCache 변수를 이용해 기존의 할 일 todo.title 을 저장하고, 편집할 값 editedTodo.value 를 매개변수로 전달한 todo 로 저장한다.
doneEdit(todo) : 편집을 완료하고 엔터 키를 눌렀을 경우 작동되는 함수로, 이전 editTodo 함수에서 저장해놓았던 editedTodo.value 값이 존재할 경우 다시 null로 갱신하고 진짜 todo 값에 새롭게 저장한다. 만약, 아무 값을 입력하지 않을 채로 엔터키를 눌렀다면 그 todo 는 삭제되도록 한다.
cancelEdit(todo) : 더블 클릭 후 편집 기능으로 들어갔지만, 사용자 입장에서는 편집을 도중에 취소할 수도 있다. 이 경우는 사용자가 keyup 이벤트 중 벗어났을 경우에 실행되는 함수로, doneEdit 과 비슷하게 되돌리는 방식이다.

위 예제는 Vue 공부하기 에서 Swift 공부하기 로 변경하는 것으로,
더블 클릭을 해서 editTodo(todo)를 실행하는데 이때 매개변수인 todo는 Vue 공부하기 로 beforeEditCache 값으로 저장이 되고, editedTodo.value 에는 큰 todo 값이 저장된다.
Swift 공부하기 로 text를 변경 후 엔터키를 누르면, doneEdit(todo) 가 실행되고 이때의 매개변수 todo는 Swift 공부하기 로 editTodo.value 는 null이 되고 진짜 값인 todo 에 Swift 공부하기가 저장된다.
예제 끝에 Swift 공부하기 에서 더블 클릭 후 편집 상태로 들어가지만 바깥 화면을 클릭하므로써 escape 가 되면서 자동으로 편집 상태가 취소되는 cancelEdit(todo) 가 실행되었다.
마찬가지로 Read 기능 중 button 태그에서 click 이벤트로 연결된 removeTodo(todo) 함수가 존재했다.
removeTodo(todo) : update 기능과는 반대로 간단하게 todos 배열에서 해당 todo 를 삭제시키면되는 로직으로 이는 splice 메서드를 이용하여 todos 배열 중 해당 위치를 찾아 1개만 삭제시킨다.function removeTodo(todo) {
todos.value.splice(todos.value.indexOf(todo), 1);
//splice 함수 이용해 잘라내기
}


위와 같이 todos 리스트를 나열할 때 하단에 아이템의 갯수와 아이템을 필터링할 수 있는 필터가 존재한다. state 를 설명할 때 filters 객체 안에는 all, active, completed 로 세 가지 filter 종류가 존재한다고 했다.
selected 선택되는 값에 따라 class 가 정해지고, 데이터를 필터링된 리스트로 내려준다.
<footer class="footer" v-show="todos.length">
<span class="todo-count">
<strong>{{ remaining }}</strong>
<span>{{ remaining === 1 ? " item" : " items" }} left</span>
</span>
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: visibility === 'all' }">All</a>
</li>
<li>
<a href="#/active" :class="{ selected: visibility === 'active' }"
>Active</a
>
</li>
<li>
<a
href="#/completed"
:class="{ selected: visibility === 'completed' }"
>Completed</a
>
</li>
</ul>
<button
class="clear-completed"
@click="removeCompleted"
v-show="todos.length > remaining"
>
Clear completed
</button>
</footer>
</section>

위와 같이 세 가지 아이템 중 하나이상 선택되면 Clear completed 라는 버튼이 생긴다. 이는 v-show 에서 할 일이 남은 상태를 동적으로 추적하는 remaining 값보다 전체 todos 갯수가 많을 경우 보여지게 된다.
function removeCompleted() {
todos.value = filters.active(todos.value);
}
removeCompleted : 이를 클릭하면 completed 된 todo 를 없애고, todos.value 배열에서 완료되지 않은 active 필터함수를 이용해 completed 속성이 false 인 항목만을 반환하여 다시 할당시킨다. 즉, todos.value 에는 완료된 항목이 제거되고 새로운 할 일 목록이 저장된다.
css 작업을 내가 하지 않고 import 해서 편리한 감도 있지만, 조금 더 살펴봐도 좋을 것 같다. class 를 상태에 따라 binding 하는 부분이 많아서 동적으로 상태들이 바뀌어도 쏙쏙 적용되는 부분이 재밌기도 하다.
crud 기능은 이제 익숙하지만, filter 부분이 filters 객체에서 세 가지 상태로 이미 나누어서 적용시키는 부분이 재밌게 느껴졌다. 정리를 하고 포스팅을 하면서
derived state 와 persist state 에 대해 더 명확히 알 수 있게 되었다.
그리고, 처음 TodoMVC 의 코드를 보고 복잡하게 느껴졌지만 하나하나 코드를 이해하고 정리를 하는 과정 자체도 개념을 이해하고 머릿속에 정리되는 느낌이 앞으로 알아가는 게 더 많을 거라고 생각하니 아찔하기도 하지만 기대된다.