vue.js/vue.js 2

47. <vue-todo> vuex로 리팩토링

DEV-Front 2022. 8. 2. 20:50
반응형


<App.vue>

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput></TodoInput>
    <TodoList></TodoList>
    <TodoFooter></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 {
  components: {
   // 컴포넌트 태그명 : 컴포넌트 내용
    TodoHeader,
    TodoInput,
    TodoList,
    TodoFooter
  }
 }
</script>

<style>
body{
  text-align: center;
  background-color: #f6f6f6;
  font-family: Ubuntu, sans-serif;
  font-weight: 500;
  letter-spacing: -0.8px;
}
input{
  border-style: groove;
  width: 200px;
}
button{
  border-style: groove;
}
.shadow{
  box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.03);
}
</style>

<store.js>

import Vue from 'vue'
import Vuex from 'vuex'

//use 는 vue의 플러그인, vue를 사용할때 전역으로 모든 영역에 특정 기능을 추가하고 싶을때 사용한다.
Vue.use(Vuex);


const storage = {
    // 원격 API를 간편하게 호출할수 있도록 브라우저에서 제공하는 함수
    fetch(){
            const arr = [];
            if (localStorage.length > 0) {
                for (let i = 0; i < localStorage.length; i++) {
                    if (localStorage.key(i) !== 'loglevel:webpack-dev-server') {
                        arr.push(JSON.parse(localStorage.getItem(localStorage.key(i))));
                    }
                }
            }
        return arr;
        },
    }


export const store = new Vuex.Store({

    state:{
      todoItems : storage.fetch()      
    },
    getters:{
        storedTodoItems(state){
            return state.todoItems
        }
    },
    mutations:{
        // 뮤테이션에서 state 접근하는 방법은 인자로 받고 접근
        addOneItem(state, todoItem) {
            var obj = { completed: false, item: todoItem };
            localStorage.setItem(todoItem, JSON.stringify(obj));
            state.todoItems.push(obj);
        },
        remobvOneItem(state, payload) {
            localStorage.removeItem(payload.todoItem.item);
            state.todoItems.splice(payload.index, 1); //idx 데이터 하나 삭제
        },
        toggleOneItem(state, payload) {
            state.todoItems[payload.index].completed = !state.todoItems[payload.index].completed;

            // 로컬 스토리지 데이터를 갱신
            localStorage.removeItem(payload.todoItem.item); //아이템 지우기
            localStorage.setItem(payload.todoItem.item, JSON.stringify(payload.todoItem)); //아이템 다시 세팅
        },
        clearAllItem(state) {
            localStorage.clear();
            state.todoItems = [];
        },
    }
});

<TodoInput.vue>

<template>
    <div class="inputBox shadow">
        <input type="text" v-model="newTodoTtem" v-on:keyup.enter="addTodo">
        <span class="addContainer" v-on:click="addTodo">
            <i class="fa-solid fa-plus" style="color:#fff;"></i>
        </span>

        <Modal v-if="showModal" @close="showModal = false">
            <!--
            you can use custom content here to overwrite
            default content
            -->
            <h3 slot="header">경고!</h3>
            <p slot="body">할 일을 입력하세요</p>
        </Modal>
    </div>
</template>

<script>

import Modal from './common/AlertModal.vue'

export default {
    components : {
        Modal
    },
    // data는 함수. 하나의 객체만을 반환
    data(){
        return {
            newTodoTtem:"",      
            showModal: false      
        }
    },
    methods: {
        addTodo(){
            if (this.newTodoTtem !== '') { //값이 있을때
            //    this.$emit('addTodoItem', this.newTodoTtem);   
            
                const text = this.newTodoTtem.trim();
                this.$store.commit('addOneItem', text);              
               
               this.cleaerInput();          
            }else{
                this.showModal = !this.showModal;
            }
        },
        cleaerInput(){
            this.newTodoTtem='';
        },
    }
};
</script>

<style lang="scss" scoped>

input:focus{
    outline: none;
}
.inputBox{
    background: #fff;
    height: 50px;
    line-height: 50px;
    border-radius: 5px;
}
.inputBox input{
    border-style: none;
    font-size: 0.9rem;
}
.addContainer{
    float: right;
    background: linear-gradient(to right, #6478FB, #8763FB);
    display: block;
    width: 3rem;
    border-radius: 0 5px 5px 0;
}
.addBtn{
    color: #fff;
    vertical-align: middle;
}
</style>

<TodoList.vue>

<template>
    <div>
        <transition-group name="list" tag="ul">
            <li v-for="(todoItem, index) in this.storedTodoItems" v-bind:key="todoItem.item" class="shadow">
                <i class="checkBth fa-solid fa-check" v-bind:class="{ checkBtnCompleted: todoItem.completed }"
                    v-on:click="toggleComplete({todoItem, index})"></i>

                <!-- 객체.속성값으로 접근 -->
                <span v-bind:class="{ textCompleted: todoItem.completed }"> {{ todoItem.item }}</span>

                <span class="removeBtn" v-on:click="removeTodo({todoItem, index})">
                    <i class="fa-solid fa-trash-can"></i>
                </span>
            </li>
        </transition-group>
    </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex';


export default {
    methods: {
        ...mapMutations({
            removeTodo: 'remobvOneItem',
            toggleComplete: 'toggleOneItem'
        })
        // removeTodo(todoItem, index) {
        //     // this.$emit('removeItem', todoItem, index);                   
        //      this.$store.commit('remobvOneItem', {todoItem, index});          
        // },
        // toggleComplete(todoItem, index) {
        //     // this.$emit('toggleItem', todoItem, index);
        //     this.$store.commit('toggleOneItem', { todoItem, index });
        // }
    },
    computed:{
        // todoItems(){
        //     return this.$store.getters.storedTodoItems;
        // },
        ...mapGetters(['storedTodoItems']),
        // ...mapGetters({
        //     todoItems: 'storedTodoItems'            
        // }),
    }
};
</script>

<style lang="scss" scoped>

ul{
    list-style-type: none;
    padding-left: 0px;
    margin-top: 0;
    text-align: left;
}
li{
    display: flex;
    margin: 10px 0;
    min-height: 50px;
    height: 50px;
    line-height: 50px;
    padding: 0 0.9rem;
    background: #fff;
    border-radius: 5px;
}
.checkBth{
    line-height: 45px;
    color: #62acde;
    margin-right: 5px;
}
.checkBtnCompleted{
    color: #b3adad;
}
.textCompleted{
    text-decoration: line-through;
    color: #b3adad;
}
.removeBtn{
    margin-left: auto;
    color: #de4343;
}

// 리스트 아이템 트렌지션 효과
.list-enter-active,
.list-leave-active {
    transition: all 1s;
}

.list-enter,
.list-leave-to {
    /* .list-leave-active below version 2.1.8 */
    opacity: 0;
    transform: translateY(-30px);
}
</style>

<TodoFooter.vue>

<template>
    <div class="clearALlContainer">
        <span class="clearAllBtn" v-on:click="clearTodo">Clear All</span>
    </div>
</template>

<script>
import { mapMutations } from 'vuex';

export default {
   methods: {
        ...mapMutations({
            clearTodo: 'clearAllItem'
        }),
    //   clearTodo() {
    //     // this.$emit('cleartAll');   
    //        this.$store.commit('clearAllItem')
    //  }
  }
};
</script>

<style lang="scss" scoped>
.clearALlContainer{
    width: 8.5rem;
    height: 50px;
    line-height: 50px;
    background-color: #fff;
    border-radius: 5px;
    margin: 0 auto;
}
.clearAllBtn{
    color: #e20303;
    display: block;
}
</style>
반응형