DevYoon

[Vue] Vuex를 이용하여 TodoList App을 만들어 보자 ⚒️ 본문

Web/Vue

[Vue] Vuex를 이용하여 TodoList App을 만들어 보자 ⚒️

gimewn 2022. 5. 12. 18:50

 

1️⃣ Store

import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'

Vue.use(Vuex)

export default new Vuex.Store({
  plugins:[
    createPersistedState()
  ],
  state: {
    todos:[
    ]
  },
  getters: {
    isdone(state){
      
      return state.todos.filter(todo => {
        return todo.done === true
      }).length
    },
    isnotyet(state){
      return state.todos.filter(todo => {
        return todo.done === false
      }).length
    },
    allcount(state){
      return state.todos.length
    }
  },
  mutations: {
    CREATE_TODO(state, todoItem){
      state.todos.push(todoItem)
    },
    DELETE_TODO(state, todoItem){
      const todoIndex = state.todos.indexOf(todoItem)
      state.todos.splice(todoIndex, 1)
    },
    UPDATE_TODO(state, todoItem){
      state.todos = state.todos.map(todo=>{
        if(todo===todoItem){
          return {
            ...todo,
            done : !todo.done
          }
        }else{
          return todo
        }
      })
    },
  },
  actions: {
    createTodo({commit}, todoItem){
      commit('CREATE_TODO', todoItem)
    },
    deleteTodo(context, todoItem){
      context.commit('DELETE_TODO', todoItem)
    },
    updateTodo(context, todoItem){
      context.commit('UPDATE_TODO', todoItem)
    }
  },
  modules: {
  }
})

1️⃣ vuex-persistedstate

  • Vuex의 state에 저장된 값을 웹 브라우저의 localStorage에 저장 및 업데이트
  • 새로고침 후에도 localStorage에서 값을 불러올 수 있음

 

2️⃣ App

<template>
  <div id="app">
    <h1><i>ToDoList</i></h1>
    <i class="fa-solid fa-check"></i>
    <span><b>All</b> : {{allcount}}</span>
    <br>
    <i class="fa-solid fa-check"></i>
    <span><b>Completed</b> : {{isdone}}</span>
    <br>
    <i class="fa-solid fa-check"></i>
    <span><b>Not yet</b> : {{isnotyet}}</span>
    <todo-form></todo-form>
    <todo-list></todo-list>
  </div>
</template>

<script>
import TodoList from './components/TodoList.vue'
import TodoForm from './components/TodoForm.vue'
import {mapGetters} from 'vuex'

export default {
  name: 'App',
  components: {
    TodoList,
    TodoForm
  },
  computed:{
    ...mapGetters(['isdone', 'isnotyet', 'allcount'])
    // isdone(){
    //   return this.$store.getters.isdone
    // },
    // isnotyet(){
    //   return this.$store.getters.isnotyet
    // },
    // allcount(){
    //   return this.$store.getters.allcount
    // }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin: 60px auto;
  width: 70%;
  padding:30px;
  border-radius:20px;
  box-shadow: 1px 1px 3px 1px #dadce0 inset;
  background-color:white;
}
body{
  background-color: #F7F7F7;
}
h1{
  margin-bottom: 50px;
}
#app span{
  margin-left:10px;
}
#app .fa-solid{
  margin-bottom:20px;
  color:lightsalmon
}
</style>

 

3️⃣ TodoForm

<template>
  <div class='TodoForm' @click='FormClick'>
    <h2>오늘의 할 일은?</h2>
    <input type="text" v-model.trim="todoTitle" @keyup.enter="createTodo" ref='Input'>
    <button @click="createTodo" class='addTodo'>add</button>
  </div>
</template>

<script>
export default {
  name:'TodoForm',
  data(){
    return{
      todoTitle:''
    }
  },
  methods:{
    createTodo(){
      const todoItem={
        title:this.todoTitle,
        done:false,
        date:new Date().getTime()
      }
      if(this.todoTitle){
        this.$store.dispatch('createTodo', todoItem)
      }
      this.todoTitle = ''
    },
    FormClick(){
      this.$refs.Input.focus();
    }
  }
}
</script>

<style>
.TodoForm{
  /* border:2px solid lightsteelblue;
  padding:20px; */
  margin:50px;
}
.addTodo{
  background-color: lightsalmon;
  border:0px;
  border-radius:5px;
  color:white;
  padding:5px 10px;
}
input{
  width:150px;
  height:30px;
}
</style>

 

4️⃣ TodoList

<template>
  <div class='TodoList'>
    <todo-list-item v-for="todo in todos" :key="todo.date" :todo="todo"/>
  </div>
</template>

<script>
import TodoListItem from './TodoListItem.vue'
import {mapState} from 'vuex'

export default {
  name:'TodoList',
  components:{
    TodoListItem
  },
  computed:{
      ...mapState(['todos']),
      // todos(){
      //   return this.$store.state.todos
      // }
  }
}
</script>

<style>
.TodoList{
  /* border: 2px solid orange;
  padding: 30px; */
  margin: 50px;
}
</style>

 

5️⃣ TodoListItem

<template>
  <div class='TodoListItem'>
    <span @click='updateTodo(todo)' v-bind:class="{'is-done': todo.done}">{{todo.title}}</span>
    <button @click='deleteTodo(todo)' class='deleteButton'>X</button>
  </div>

</template>

<script>
import {mapActions} from 'vuex'

export default {
  name:'TodoListItem',
  props:{
    todo:{
      type:Object
    }
  },
  methods:{
    ...mapActions(['deleteTodo', 'updateTodo']),
    // deleteTodo(){
    //   this.$store.dispatch('deleteTodo', this.todo)
    // },
    // updateTodo(){
    //   this.$store.dispatch('updateTodo', this.todo)
    // }
  },
  create(){
    console.log(this.$store.getters)
  }
}
</script>

<style>
.TodoListItem span{
  display:inline-block;
  background-color:lightsteelblue;
  border-radius:20px;
  margin-top: 25px;
  padding: 15px 20px;
  cursor:pointer;
  box-shadow: 1px 1px 5px 1px #7A96B2 inset;
  color:#2c3e50;
}
button{
  margin-left:15px;
}
.deleteButton{
  background-color: transparent;
  border:0px;
  color:lightsalmon;
}
.is-done{
  text-decoration:line-through;
}
</style>