본문 바로가기
Programming/Vue

[실습] 뷰(Vue)로 투두 리스트(Todo list) 구현하기 - 최상위 컴포넌트 기능 구현하기

by 돌방로그 2022. 12. 23.

제목의 글을 설명하기에 앞서 정보전달의 목적도 있지만, 

제가 잊지않기 위해서 공부 및 정리하며 쓰는 글이라는 사실을 미리 고지합니다.

 

혹시라도 오입력된 정보가 있다면, 댓글 남겨주세요!


오늘의 Vue.js 실습 목표는 "투두 리스트 (Todo list) 만들어보기!" 입니다.

본 게시글에서 다루는 사항은 투두리스트(Todo List)의 최상위 컴포넌트 기능을 구현하는 과정입니다.

 


투두 리스트(Todo List) 구현하기

사전 준비

아래 두가지에 대해서 사전 준비가 완료되지 않으신 분들은 아래 링크를 참조하여 사전 준비를 진행합니다.

  1. 투두 리스트 레이아웃(Todo List Layout) 구현하기: https://logs-jejustone.tistory.com/37

 

혹시라도 위에서 언급한 개념이 잘 기억나지 않으시는 분들은 아래 링크를 참조하시기 바랍니다.

 

결과 이미지

구현하기에 앞서 본 글을 따라하시면 아래와 같이 투두 리스트의 결과를 콘텐츠 영역에서 확인하실 수 있습니다.

 

기본

UI

DB / Console


Todo Item 추가(Add) 기능

UI

DB / Console


Todo Item 삭제(Delete) 기능

UI

DB / Console


Todo Item 완료 처리(Done, Checked) 기능

UI


Todo Item 전체 삭제(Delete All) 기능

UI

 

DB / Console


 

주의 사항

Vue 실습 프로젝트로 Todo List가 정석처럼 여겨지고 있어서 구글링해보면 정말 많은 게시글이 확인되지만 대체로 비슷비슷한 소스코드로 구현되어 있습니다.

TodoList를 구현할 때 Local Storage를 DB로 사용하는데 Local Storage에 데이터를 저장하고 있으나 실질적으로 화면에 표시하는 데이터로는 사용하지 않습니다. 

따라서 최상위 컴포넌트에서 화면에 표시할 데이터를 Local Storage와 동일한 형식을 갖추도록 구현해야 합니다.

 

잘 못 된 코드

1) Local Storage은 {key, value} 구조로 입력받은 텍스트를 key와 value에 동일하게 입력하는 경우

동일한 데이터를 N번 입력하는 경우 화면에는 N번 표시되나 Local Storage에는 1번만 저장됩니다. 

따라서 이 현상으로 인해 Local Storage에는 Key 중복으로 인해 데이터가 정상적으로 저장되지 않는 문제가 발생합니다.

let newTodoText = "사용자가 입력한 텍스트";
localStorage.setItem(newTodoText, newTodoText);

 

2) 화면에 표시하기 위해 저장하는 변수인 todoItems를 단순 배열(Array)로만 지정하는 경우

Todo List에서 개별 아이템 삭제시 todoItems 내 화면에 동일하게 표시되는 데이터가 있는 경우 todoItems은 선택된 Todo Item이 아닌 중복된 데이터 중 하나가 삭제될 수 있습니다. 

즉, 사용자가 선택한 데이터가 아닌 데이터가 삭제되고 Local Storage와 todoItems가 상이한 데이터를 지니는 문제가 발생합니다.

data: function() {
	return {
    	todoItems: []
    }
}
methods: {
    pushItem: function(newTodoText) {
    	this.todoItems.push(newTodoText);
    }
}

 

해결 방법 (정상 동작 코드)

1) Local Storage은 {key, value} 구조로 입력받은 텍스트를 key와 value에 동일하게 입력하는 경우

Local Storage의 {key, value} 구조에 맞게 key는 unique id/value를 지정하고, value에는 사용자에게 입력받은 텍스트를 사용하여 Local Storage에서 발생하는 중복 Key 문제를 해결할 수 있습니다.

let todoId = Date.now(); // for unique id/value
let newTodoText = "사용자가 입력한 텍스트";
localStorage.setItem(todoId, newTodoText);

 

2) 화면에 표시하기 위해 저장하는 변수인 todoItems를 단순 배열(Array)로만 지정하는 경우

Local Storage와 동일한 구조를 지닌 배열(Object를 지닌 Array)을 사용하여 각 Todo Item에 대한 key와 value, 그리고 화면 요소에서 표시되는 인덱스를 통해 상이한 데이터가 관리되거나 선택되지 않은 데이터가 삭제되는 문제를 해결할 수 있습니다. 

data: function() {
	return {
    	todoItems: []
    }
}
methods: {
    pushItem: function(newTodoText) {
        let todoItem = {
            key: todoId,
            value: newTodoText
        };
    
    	this.todoItems.push(todoItem);
    }
}

> 화면 데이터 (todoItems)

Array:index 0 1 ...
Array:data
└ Object:key
1671437696232 1671437702518 ...
Array:data
└ Object:value
투두 리스트 기능 구현하기 투두 리스트 CSS 적용하기 ...

> Local Storage

Object:key 1671437696232 1671437702518 ...
Object:value 투두 리스트 기능 구현하기 투두 리스트 CSS 적용하기 ...

 


 

소스 코드

src\views\Practice\PracticeTodo.vue

Todo List의 최상위 컴포넌트로 하위 컴포넌트에서 전달받은 이벤트 시그널과 최상위 컴포넌트에서 관리하는 자체 데이터를 구현합니다.

 

<template>

<template>
    <div id="todo">
        <TodoHeader></TodoHeader>
        <TodoInput v-on:addItem="addItem"></TodoInput>
        <TodoList v-bind:propsdata="todoItems" v-on:removeItem="removeItem"></TodoList>
        <TodoFooter v-on:removeAll="removeItems"></TodoFooter>
    </div>
</template>
  • <TodoInput>
    • v-on:addItem → 하위 컴포넌트인 TodoInput.vue에서 전달한 이벤트 시그널을 감지하고 PracticeTodo.vue에 정의된 함수를 호출하도록 구현
  • <TodoList>
    • v-on:removeItem → 하위 컴포넌트인 TodoList.vue에서 전달한 이벤트 시그널을 감지하고 PracticeTodo.vue에 정의된 함수를 호출하도록 구현
    • v-bind:propsdata → 하위 컴포넌트인 TodoList.vue에 전달할 데이터 정의
  •  <TodoFooter>
    • v-on:removeAll → 하위 컴포넌트인 TodoFooter.vue에서 전달한 이벤트 시그널을 감지하고 PracticeTodo.vue에 정의된 함수를 호출하도록 구현

 

<script>

<script>
...
export default {
    data: function() {
        return {
            todoItems: []
        }
    },
    created: function() {
        for (let idx = 0; idx < localStorage.length; idx++) {
            let key = localStorage.key(idx);
            let value = localStorage.getItem(key);
            
            this.pushItem(key, value);
        }
    },
    methods: {
        pushItem: function(key, value) {
            let todoItem = {
                key: key,
                value: value
            };
            this.todoItems.push(todoItem);

            console.log("Push Item: " + todoItem.key + " | " + todoItem.value);
        },
        addItem: function(newItem) {
            let todoId = Date.now();
            
            this.pushItem(todoId, newItem);
            localStorage.setItem(todoId, newItem);
        },
        removeItem: function(todoItem, idx) {
            localStorage.removeItem(todoItem.key);
            this.todoItems.splice(idx, 1);

            console.log("Remove Item: (idx|key|value)" + idx + "|" + todoItem.key + "|" + todoItem.value);
        },
        removeItems: function() {
            console.log("Remove All Items");
            
            localStorage.clear();
            this.todoItems = [];
        }
    },
    ...
};
</script>
  • data 영역
    • todoItems
      • 화면에서 보여질 Todo Items를 지니고 있는 변수
      • 배열의 각각의 요소로 객체를 지니고 있으며 Local Storage와 동일한 구조를 지니기 위함
  • created 영역
  • methods 영역
    • pushItem → 전달받은 Id와 텍스트를 객체에 저장하여 todoItems에 저장
    • addItem
      • todoId
        • 사용자가 입력한 텍스트가 저장될 객체의 key
        • Unique value로 지정되어야 하기 때문에 'Date.now()' 코드 사용
      • 사용자가 입력한 텍스트와 todoId를 todoItems에 객체로 저장될 수 있도록 pushItem 호출 및 값 전달
    • removeItem → 삭제 버튼이 클릭된 Todo Item의 정보를 이용하여 Local Storage와 todoItems에서 제거
    • removeItems → Local Storage와 화면에 표시할 데이터인 todoItems를 초기화 

 

PracticeTodo.vue의 전체 코드가 궁금하신 분들은 아래 더보기를 참조해주세요.

더보기
<!-- PracticeTodo.vue -->

<template>
    <div id="todo">
        <TodoHeader></TodoHeader>
        <TodoInput v-on:addItem="addItem"></TodoInput>
        <TodoList v-bind:propsdata="todoItems" v-on:removeItem="removeItem"></TodoList>
        <TodoFooter v-on:removeAll="removeItems"></TodoFooter>
    </div>
</template>

<script>
import TodoHeader from "../../components/Practice/Todo/TodoHeader.vue";
import TodoInput from "../../components/Practice/Todo/TodoInput.vue";
import TodoList from "../../components/Practice/Todo/TodoList.vue";
import TodoFooter from "../../components/Practice/Todo/TodoFooter.vue";

export default {
    data: function() {
        return {
            todoItems: []
        }
    },
    created: function() {
        for (let idx = 0; idx < localStorage.length; idx++) {
            let key = localStorage.key(idx);
            let value = localStorage.getItem(key);
            
            this.pushItem(key, value);
        }
    },
    methods: {
        pushItem: function(key, value) {
            let todoItem = {
                key: key,
                value: value
            };
            this.todoItems.push(todoItem);

            console.log("Push Item: " + todoItem.key + " | " + todoItem.value);
        },
        addItem: function(newItem) {
            let todoId = Date.now();
            
            this.pushItem(todoId, newItem);
            localStorage.setItem(todoId, newItem);
        },
        removeItem: function(todoItem, idx) {
            localStorage.removeItem(todoItem.key);
            this.todoItems.splice(idx, 1);

            console.log("Remove Item: (idx|key|value)" + idx + "|" + todoItem.key + "|" + todoItem.value);
        },
        removeItems: function() {
            console.log("Remove All Items");
            
            localStorage.clear();
            this.todoItems = [];
        }
    },
    components: {
        TodoHeader,
        TodoInput,
        TodoList,
        TodoFooter
    }
};
</script>

<style scoped>
#todo {
    background-color: white;
}
</style>

 

src\components\Practice\Todo{Header, Footer, Input, List}.vue

투두 리스트(Todo List)의 하위 컴포넌트로 각 영역별 Layout 및 기능, CSS 효과를 구현하는 파일입니다.

보다 상세하게 다루기 위해 별도의 게시글로 작성하였으니 관련 코드는 아래 링크를 참조해주세요.

https://logs-jejustone.tistory.com/40

 

 


References

댓글