코드공부방

Vue.js To Do List 만들기 #4 컴포넌트 구현(3), 문제해결 본문

웹프로그래머/vue.js

Vue.js To Do List 만들기 #4 컴포넌트 구현(3), 문제해결

:- ) 2019. 9. 6. 16:32
반응형

Vue.js To Do List 만들기 #4 컴포넌트 구현(3), 문제해결


컴포넌트 생성 및 등록(3)

컴포넌트 구현 > TodoFooter

이제 마지막 TodoFooter 컴포넌트 구현만 남겨두고있다. TodoFooter컴포넌트에서는 전체 목록을 삭제하는 버튼을 넣고 버튼을 클릭하면 clearTodo메소드를 실행한다.

<!-- /src/components/TodoFooter.vue > <template></template> -->

<div>
    <button type="button" v-on:click="clearTodo">전체삭제</button>
</div>
/* /src/components/TodoFooter.vue > <script></script> */

export default {
    methods: {
        clearTodo(){
            localStorage.clear();
        }
    }
}

전체삭제 버튼을 클릭하면 localStorage는 비워지지만 화면의 목록은 비워지지 않는다.

전체삭제 버튼을 누르면 localStorage의 모든 목록이 삭제된다. 화면의 할 일 목록은 새로고침을 해야만 삭제가 된다.

TodoInput컴포넌트 에서 할 일을 추가했을때와 TodoFooter컴포넌트 에서 할 일을 모두 삭제했을때 화면의 할 일 목록이 자동으로 갱신되지 않는 문제가 있긴 하지만 To Do List에 필요한 모든 컴포넌트를 구현했다.

그럼 이제 문제를 해결해보자.

문제해결

현재까지 나타난 애플리케이션의 문제는 아래 2가지이다.

  • TodoInput컴포넌트에서 할 일을 추가했을 때 목록에 바로 반영되지 않는다.
  • TodoFooter컴포넌트에서 전체삭제를 했을 때 목록에 바로 반영되지 않는다.

뭔가 이상하다. 할 일 1개를 삭제하는 버튼을 눌렀을때는 localStorage와 목록이 바로 삭제처리가 반영되는데 왜 할일 추가와 전체삭제는 바로 반영이 안되는걸까? 바로 컴포넌트를 나눠놨기 때문이다. (1) 할 일 추가TodoInput컴포넌트에, (2) 할 일 목록, 선택 삭제TodoList컴포넌트에, (3) 할 일 전체삭제TodoFooter컴포넌트에 기능이 존재하기 때문에 서로간의 변화를 감지할 수 없고 1개의 선택한 할 일을 삭제하는 버튼은 같은 TodoList컴포넌트에 존재하기 때문에 삭제와 동시에 목록 갱신이 가능한 것이다. (글로 이 상황을 표현하는 것이 나에게는 너무 어려운 일 ㅠㅠ)

이 문제를 해결하려면 각각의 기능을 컴포넌트별로 나누지 않고 1개의 컴포넌트에서 모든 기능을 수행하게끔 수정하면 된다. 하지만 그것이 정답은 아니다. 기능이 복잡해졌을때 문제소지가 많고 애플리케이션 규모가 커졌을때 컴포넌트의 재활용성도 떨어지게 된다. 

 

현재 어플리케이션 문제

현재의 구조는 각각의 컴포넌트에서만 사용할 수 있는 뷰 데이터 속성(newTodoItem, todoItems)을 갖고 있다. 하지만 각 컴포넌트의 newTodoItem, todoItems는 모두 "할 일"이라는 같은 성격의 데이터를 사용하고 있으니 이를 최상위(루트) 컴포넌트인 App컴포넌트에 TodoItems라는 데이터를 정의하고, 하위 컴포넌트 TodoList에 props로 전달한다. 그렇게 되면 뷰 데이터 속성 todoItems와 localStorage의 데이터 조회, 추가, 삭제를 모두 App 컴포넌트에서 하게 되고, 하위 컴포넌트들은 데이터를 표현하거나, 데이터 조작에 대한 요청만 하게 된다. (글로 이 상황을 표현하는 것이 나에게는 너무 어려운 일 ㅠㅠ) 글만으로는 이해가 어려우니 코드를 직접 보자. 최상위 컴포넌트인 App컴포넌트에 데이터속성 todoItems과 로컬스토리지에 데이터를 추가하는 addTodo() 메소드를 추가한다.

/* /src/App.vue > <script></script> */

export default {
    data(){
        return {
            todoItems: []
        }
    },
    methods: {
        addTodo(todoItem){
            //로컬스토리지에 데이터를 추가하는 로직
            localStorage.setItem(todoItem, todoItem);
            this.todoItems.push(todoItem);
        }
    },
    components: {
        'TodoHeader': TodoHeader,
        'TodoInput': TodoInput,
        'TodoList': TodoList,
        'TodoFooter': TodoFooter
    }
}

다음으로 선언한 todoItem속성을 TodoList컴포넌트에 props로 전달하고, TodoInput컴포넌트에는 할 일 추가 버튼을 클릭했을 때 App컴포넌트로 이벤트를 전달할 수 있게 v-on디렉티브를 추가한다.

<!-- /src/App.vue > <template></template> -->

<div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput v-on:addTodo="addTodo"></TodoInput>
    <TodoList v-bind:propsdata="todoItems"></TodoList>
    <TodoFooter></TodoFooter>
</div>

TodoList컴포넌트에서는 App컴포넌트에서 전달받은 props속성을 추가한다. 기존에 사용하던 data todoItems는 더이상 사용하지 않음으로 삭제한다.

/* /src/Components/TodoList.vue > <script></script> */

export default {
    created(){
        if(localStorage.length > 0){
            for(var i = 0; i < localStorage.length; i++){
                this.todoItems.push(localStorage.key(i));
            }
        }
    },
    //props속성 추가
    props: ['propsdata'],
    methods: {
    	removeTodo(todoItem, index){
            localStorage.removeItem(todoItem);
            this.todoItems.splice(index, 1);
        }
    }    
}

그리고 TodoInput컴포넌트에서 App컴포넌트로 이벤트를 전달하기 위하여 addTodo()메소드의 코드를 수정한다. 기존에 localStorage에 값을 저장하던 코드는 App컴포넌트에 있으므로 삭제한다.

/* /src/Components/TodoInput.vue > <script></script> */

export default {
    data(){
        return {
            newTodoItem: ''
        }
    },
    methods: {
        addTodo(){
            //inputbox 빈값인지 체크, 빈값이 아니면 로직 수행
            if(this.newTodoItem !== ''){
                //inputbox에 입력된 텍스트의 앞, 뒤 공백문자열 제거
                var value = this.newTodoItem && this.newTodoItem.trim();
                //App컴포넌트로 이벤트 전달
                this.$emit('addTodo', value);
                //inputbox 초기화
                this.clearInputbox();
            }
        },
        clearInputbox(){
            this.newTodoItem = '';
        }
    }
}

다음은 TodoList컴포넌트의 <template> 내용을 수정한다. 기존에 <li>태그에서 v-for 디렉티브의 반복 대상인 todoItems대신 App컴포넌트에서 전달받은 propsdata로 변경해준다.

<!-- /src/Components/TodoList.vue > <template></template> -->

<section>
    <ul>
        <!-- v-for대상을 propsdata로 변경 -->
        <li v-for="(todoItem, index) in propsdata" :key="todoItem">
            {{ todoItem }}
            <button type="button" v-on:click="removeTodo(todoItem, index)">삭제</button>
        </li>
    </ul>
</section>

여기까지 진행하면 할일 추가시에는 별도의 새로고침 필요 없이 바로 화면에 localStorage의 데이터가 반영이 된다.

 

새로고침 없이 할일 목록이 갱신 된다.

다음은 TodoList컴포넌트에 존재하던 created()로직을 App컴포넌트로 이동시킨다. 이렇게 되면 할 일 데이터(TodoItems)는 모두 App컴포넌트에서 관리하게된다.

/* /src/Components/TodoList.vue > <script></script> */

export default {
    props: ['propsdata'],
    methods: {
    	removeTodo(todoItem, index){
            localStorage.removeItem(todoItem);
            this.todoItems.splice(index, 1);
        }
    }    
}
/* /src/App.vue > <script></script> */

export default {
    data(){
        return {
            todoItems: []
        }
    },
    created(){
        if(localStorage.length > 0){
            for(var i = 0; i < localStorage.length; i++){
                this.todoItems.push(localStorage.key(i));
            }
        }
    },
    methods: {
        addTodo(todoItem){
            //로컬스토리지에 데이터를 추가하는 로직
            localStorage.setItem(todoItem, todoItem);
            this.todoItems.push(todoItem);
        }
    },    
    components: {
        'TodoHeader': TodoHeader,
        'TodoInput': TodoInput,
        'TodoList': TodoList,
        'TodoFooter': TodoFooter
    }
}

마지막으로 전체삭제 버튼을 클릭 시 바로 목록이 갱신되지 않던 문제만 해결해주면 된다. 이를 해결하기 위해서는 todoFooter컴포넌트에서 이벤트를 발생시켜 전달하면 App컴포넌트에서 localStorage의 내용을 모두 삭제시키면 된다. 먼저 App컴포넌트의 TodoFooter태그에 v-on디렉티브를 추가해주고 localStorage의 값을 모두 지우고 todoItems를 초기화 시켜주는 clearAll()메소드를 추가한다.

/* /src/Components/App.vue > <template></template> */

<div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput v-on:addTodo="addTodo"></TodoInput>
    <TodoList v-bind:propsdata="todoItems"></TodoList>
    <!-- TodoFooter v-on 디렉티브 추가 -->
    <TodoFooter v-on:removeAll="clearAll"></TodoFooter>
</div>
/* /src/App.vue > <script></script> */

export default {
    data(){
        return {
            todoItems: []
        }
    },
    created(){
        if(localStorage.length > 0){
            for(var i = 0; i < localStorage.length; i++){
                this.todoItems.push(localStorage.key(i));
            }
        }
    },
    methods: {
        addTodo(todoItem){
            //로컬스토리지에 데이터를 추가하는 로직
            localStorage.setItem(todoItem, todoItem);
            this.todoItems.push(todoItem);
        },
        clearAll(){
            //로컬스토리지 전체 삭제, todoItems 초기화
            localStorage.clear();
            this.todoItems = [];
        }
    },    
    components: {
        'TodoHeader': TodoHeader,
        'TodoInput': TodoInput,
        'TodoList': TodoList,
        'TodoFooter': TodoFooter
    }
}

마지막으로 TodoFooter컴포넌트에서 clearTodo()메소드의 내용을 수정한다.

/* /src/Components/TodoFooter.vue > <script></script> */

export default {
    methods: {
        clearTodo(){
            this.$emit('removeAll');
        }    
    }
}

그리고 화면을 확인해보자. 이제 모든 기능이 새로고침 없이 바로바로 갱신이 된다.

 

전체삭제 기능도 즉시 갱신이 되고있다.

이미 모든 기능은 정상적으로 작동하지만 TodoList컴포넌트에 아직 할일 삭제 로직이 남아있다. 마찬가지로 App컴포넌트로 로직은 옮겨주면 완성 된다. (아래 최종 코드 참고)


각 파일(컴포넌트)별 최종 코드는 아래와 같다.

<!-- /src/App.vue -->

<template>
    <div id="app">
        <TodoHeader></TodoHeader>
        <TodoInput v-on:addTodo="addTodo"></TodoInput>
        <TodoList v-bind:propsdata="todoItems" v-on:removeTodo="removeTodo"></TodoList>
        <TodoFooter v-on:removeAll="clearAll"></TodoFooter>
    </div>
</template>

<script>
import TodoHeader from './components/TodoHeader';
import TodoInput from './components/TodoInput';
import TodoList from './components/TodoList';
import TodoFooter from './components/TodoFooter';

export default {
    data(){
        return {
            todoItems: []
        }
    },
    created(){
        if(localStorage.length > 0){
            for(var i = 0; i < localStorage.length; i++){
                this.todoItems.push(localStorage.key(i));
            }
        }
    },
    methods: {
        addTodo(todoItem){
            //로컬스토리지에 데이터를 추가하는 로직
            localStorage.setItem(todoItem, todoItem);
            this.todoItems.push(todoItem);
        },
        removeTodo(){
            localStorage.removeItem(todoItem);
            this.todoItems.splice(index, 1);
        },
        clearAll(){
            //로컬스토리지 전체 삭제, todoItems 초기화
            localStorage.clear();
            this.todoItems = [];
        }
    },    
    components: {
        'TodoHeader': TodoHeader,
        'TodoInput': TodoInput,
        'TodoList': TodoList,
        'TodoFooter': TodoFooter
    }
}
</script>
<!-- /src/Components/TodoHeader.vue -->

<template>
    <header>
        <h1>TO DO LIST!</h1>
    </header>
</template>

<script>
export default {
    
}
</script>
<!-- /src/Components/TodoInput.vue -->

<template>
    <div>
        <input type="text" v-model="newTodoItem" v-on:keyup.enter="addTodo">
        <button v-on:click="addTodo">추가</button>
    </div>
</template>

<script>
export default {
    data(){
        return {
            newTodoItem: ''
        }
    },
    methods: {
        addTodo(){
            //inputbox 빈값인지 체크, 빈값이 아니면 로직 수행
            if(this.newTodoItem !== ''){
                //inputbox에 입력된 텍스트의 앞, 뒤 공백문자열 제거
                var value = this.newTodoItem && this.newTodoItem.trim();
                //App컴포넌트로 이벤트 전달
                this.$emit('addTodo', value);
                //inputbox 초기화
                this.clearInputbox();
            }
        },
        clearInputbox(){
            this.newTodoItem = '';
        }
    }
}
</script>
<!-- /src/Components/TodoInput.vue -->

<template>
    <section>
        <ul>
            <li v-for="(todoItem, index) in propsdata" :key="todoItem">
                {{ todoItem }}
                <button type="button" v-on:click="removeTodo(todoItem, index)">삭제</button>
            </li>
        </ul>
    </section>
</template>

<script>
export default {
    props: ['propsdata'],
    methods: {
    	removeTodo(todoItem, index){
            this.$emit('removeTodo', todoItem, index);
        }
    }    
}
</script>
<!-- /src/Components/TodoFooter.vue -->

<template>
    <div>
        <button type="button" v-on:click="clearTodo">전체삭제</button>
    </div>
</template>

<script>
export default {
    methods: {
        clearTodo(){
            this.$emit('removeAll');
        }    
    }
}
</script>

첫번째는 그냥 책을 보면서 따라 만들어보았고, 두번째는 블로그에 포스팅을 위한 실습을 해보았다. 이해하고 글을 써야하기에 훨씬 도움이 됐다. 문제는 이 글이 누군가에게 아주 작은 도움이라도 되길 바랐건만.. 코드의 품질이나 레벨은 둘째치고 글로 코드를 설명한다는 것이 이렇게 힘들고 어려울 것이라고 생각하지 못하고 호기롭게 시작했는데.. 시리즈를 시작은 했으니 억지로 끝은 봤지만.. 정말 쉽지 않았다. 하다보면 나아지겠지..ㅠㅠ

2019/09/05 - [vue.js] - Vue.js To Do List 만들기 #1 프로젝트 생성

 

Vue.js To Do List 만들기 #1 프로젝트 생성

Vue.js To Do List 만들기 #1 프로젝트 생성 난 이미 포스팅을 하기 전 책을 보고 한번 쭉 따라 만들어보았다. 지겨운 To Do List 이다. 구글에 "Vuejs to do list" 라고 검색하면 수많은 검색결과가 나온다. 그..

code-study.tistory.com

2019/09/05 - [vue.js] - Vue.js To Do List 만들기 #2 컴포넌트 구현(1)

 

Vue.js To Do List 만들기 #2 컴포넌트 구현(1)

Vue.js To Do List 만들기 #2 컴포넌트 구현(1) 컴포넌트 생성 및 등록(1) 프로젝트 초기 구성을 완료하였으니 애플리케이션에 필요한 컴포넌트들을 생성하고 등록한다. To Do List에 필요한 컴포넌트는 TodoHeade..

code-study.tistory.com

2019/09/05 - [vue.js] - Vue.js To Do List 만들기 #3 컴포넌트 구현(2)

 

Vue.js To Do List 만들기 #3 컴포넌트 구현(2)

Vue.js To Do List 만들기 #3 컴포넌트 구현(2) 컴포넌트 생성 및 등록(2) 컴포넌트 구현 > TodoList TodoList컴포넌트는 localStorage에 저장되어있는 item 목록을 화면에 보여주는 역할을 한다. 순서가 없는 목..

code-study.tistory.com

2019/07/24 - [vue.js] - vue.js 입문

 

vue.js 입문

웹퍼블리싱만을 할 줄 아는 것(html, css를 사용하여 레이아웃을 만들고 jQuery로 동적UI 구현)으로는 더이상 큰 경쟁력이 없다. 아마도 2018년부터 웹퍼블리셔 구인시장을 봤다면 누구나 공감할 것이다. 신입 웹..

code-study.tistory.com

 

반응형
Comments