๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
โญ Personal_Study/Vue

Vuex ํ™œ์šฉํ•ด Todo SPA ์•ฑ ๋งŒ๋“ค๊ธฐ

by ํฌ์ŠคํŠธ์‰์ดํฌ 2022. 11. 15.

Todo

์‚ฌ์ „ ์ค€๋น„

ํ”„๋กœ์ ํŠธ ๊ฐœ์‹œ

  1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋ฐ vuex ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€
$ vue create todo-vuex-app
$ cd todo-vuex-app
$ vue add vuex
  1. HelloWorld ์ปดํฌ๋„ŒํŠธ ๋ฐ ๊ด€๋ จ ์ฝ”๋“œ ์‚ญ์ œ

์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ

// components/TodoListItem.vue

<template>
  <div>Todo</div>
</template>

<script>
export default {
  name: "TodoListItem",
};
</script>

<style></style>
// components/TodoList.vue

<template>
  <div>
    <TodoListItem />
  </div>
</template>

<script>
import TodoListItem from "./TodoListItem.vue";

export default {
  name: "TodoList",
  components: {
    TodoListItem,
  },
};
</script>

<style></style>
// components/TodoForm.vue

<template>
  <div>Todo Form</div>
</template>

<script>
export default {
  name: "TodoForm",
};
</script>

<style></style>
// App.vue

<template>
  <div id="app">
    <h1>Todo List</h1>
    <TodoList />
    <TodoForm />
  </div>
</template>

<script>
import TodoForm from "./components/TodoForm.vue";
import TodoList from "./components/TodoList.vue";

export default {
  name: "App",
  components: {
    TodoList,
    TodoForm,
  },
};
</script>

ํŽ˜์ด์ง€ ํ™•์ธ

Read Todo

State ์„ธํŒ…

// index.js

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    todos: [
      {
        title: 'ํ•  ์ผ 1',
        isCompleted: false,
      },
      {
        title: 'ํ•  ์ผ 2',
        isCompleted: false,
      }
    ]
  },
  ...
})

state ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

// components/TodoList.vue

<template>
  <div>
    <TodoListItem v-for="(todo, index) in todos" :key="index" :todo="todo" />
  </div>
</template>

<script>
import TodoListItem from "./TodoListItem.vue";

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

<style></style>

Pass Props

// components/TodoListItem.vue

<template>
  <div>{{ todo.title }}</div>
</template>

<script>
export default {
  name: "TodoListItem",
  props: {
    todo: Object,
  },
};
</script>

<style></style>

Create Todo

TodoForm, Actions

// components/TodoForm.vue

<template>
  <div>
    <input type="text" v-model="todoTitle" @keyup.enter="createTodo" />
  </div>
</template>

<script>
export default {
  name: "TodoForm",
  data() {
    return {
      todoTitle: null,
    };
  },
  methods: {
    createTodo() {
      // console.log(this.todoTitle)
      this.$store.dispatch("createTodo", this.todoTitle);
      this.todoTitle = null;
    },
  },
};
</script>

โœ” todoTitle์„ ์ž…๋ ฅ ๋ฐ›์„ input ํƒœ๊ทธ ์ƒ์„ฑ
โœ” todoTitle์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด data๋ฅผ ์ •์˜ํ•˜๊ณ  input๊ณผ v-model์„ ์ด์šฉํ•ด ์–‘๋ฐฉํ–ฅ ๋ฐ”์ธ๋”ฉ
โœ” enter ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด createTodo ๋ฉ”์„œ๋“œ ์ถœ๋ ฅ ํ™•์ธ


โœ” createTodo ๋ฉ”์„œ๋“œ์—์„œ actions๋ฅผ ํ˜ธ์ถœ(dispatch)

Mutations

// index.js

export default new Vuex.Store({
  ...
  actions: {
    createTodo(context, todoTitle) {
      // Todo ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ
      const todoItem = {
        title: todoTitle,
        isCompleted: false,
      };
      // console.log(todoItem)
      context.commit('CREATE_TODO', todoItem)
    },
  },
  modules: {},
});

โœ” CREATE_TODO mutations ๋ฉ”์„œ๋“œ์— todoItem์„ ์ „๋‹ฌํ•˜๋ฉฐ ํ˜ธ์ถœ(commit)

// index.js

export default new Vuex.Store({
  ...
  mutations: {
    CREATE_TODO(state, todoItem) {
      state.todos.push(todoItem)
    }
  },
  ...
});

โœ” mutations์—์„œ state์˜ todos์— ์ ‘๊ทผํ•ด ๋ฐฐ์—ด์— ์š”์†Œ ์ถ”๊ฐ€

export default new Vuex.Store({
  state: {
    todos: [],
  },
  ...
})

โœ” ๊ธฐ์กด dummy data ์‚ญ์ œ

์ค‘๊ฐ„ ์ •๋ฆฌ!

โœ” Vue ์ปดํฌ๋„ŒํŠธ์˜ method์—์„œ dispatch๋ฅผ ์‚ฌ์šฉํ•ด actions ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
โœ” Actions์— ์ •์˜๋œ ํ•จ์ˆ˜๋Š” commit()๋ฅผ ์‚ฌ์šฉํ•ด mutations ํ˜ธ์ถœ
โœ” Mutations์— ์ •์˜๋œ ํ•จ์ˆ˜๊ฐ€ ์ตœ์ข…์ ์œผ๋กœ state ๋ณ€๊ฒฝ

Delete Todo

TodoListItem

// components/TodoListItem.vue

<template>
  <div>
    {{ todo.title }}
    <button @click="deleteTodo">Delete</button>
  </div>
</template>

<script>
export default {
  name: "TodoListItem",
  props: {
    todo: Object,
  },
  method: {
    deleteTodo() {
      this.$store.dispatch("deleteTodo");
    },
  },
};
</script>

โœ” TodoListItem ์ปดํฌ๋„ŒํŠธ์— ์‚ญ์ œ ๋ฒ„ํŠผ ๋ฐ deleteTodo ๋ฉ”์„œ๋“œ ์ž‘์„ฑ

Actions

export default new Vuex.Store({
  ...
  actions: {
    createTodo(context, todoTitle) {
      // Todo ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ
      const todoItem = {
        title: todoTitle,
        isCompleted: false,
      };
      // console.log(todoItem)
      context.commit('CREATE_TODO', todoItem)
    },

    // ์ด ๊ฒฝ์šฐ๋Š” ์ƒ๋žตํ•˜๊ณ  ๋ฐ”๋กœ mutations ํ˜ธ์ถœ ๊ฐ€๋Šฅ
    deleteTodo(context, todoItem) {
      context.commit('DELETE_TODO', todoItem)
    },
  },
  modules: {},
});

โœ” ์ด ๊ฒฝ์šฐ๋Š” ๋”ฐ๋กœ ๊ธฐ๋Šฅ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ”๋กœ mutations๋กœ ํ˜ธ์ถœ์ด ๊ฐ€๋Šฅํ•˜๋‹ค

Mutations

export default new Vuex.Store({
  ...
  mutations: {
    CREATE_TODO(state, todoItem) {
      state.todos.push(todoItem)
    },
    DELETE_TODO(state, todoItem) {
      const index = state.todos.indexOf(todoItem)
      state.todos.splice(index, 1)
    }
  },
})

โœ” ์ „๋‹ฌ๋  ๋•Œ todoItem์— ํ•ด๋‹นํ•˜๋Š” todo ์‚ญ์ œ

Update Todo

TodoListItem

// components/TodoListItem.vue

<template>
  <div>
    <span @click="updateTodoStatus">
      {{ todo.title }}
    </span>
    <button @click="deleteTodo">Delete</button>
  </div>
</template>

<script>
export default {
  name: 'TodoListItem',
  props: {
    todo: Object,
  },
  methods: {
    ...
    updateTodoStatus() {
      this.$store.dispatch('updateTodoStatus', this.todo)
    }
  },
}
</script>

Actions

// index.js

export default new Vuex.Store({
  ...
  actions: {
    ...
    updateTodoStatus(context, todoItem) {
      context.commit('UPDATE_TODO_STATUS', todoItem)
    }
  },
  modules: {},
});

Mutations

export default new Vuex.Store({
  ...
  mutations: {
    ...
    UPDATE_TODO_STATUS(state, todoItem) {
      // console.log(todoItem)
      state.todos = state.todos.map((todo) => {
        if (todo === todoItem) {
          todo.isCompleted = !todo.isCompleted
        }
        return todo
      })
    }
  },
  ...
});

โœ” map ๋ฉ”์„œ๋“œ ํ™œ์šฉํ•ด ์„ ํƒ๋œ todo์˜ isCompleted๋ฅผ ๋ฐ˜๋Œ€๋กœ ๋ณ€๊ฒฝ ํ›„ ๊ธฐ์กด ๋ฐฐ์—ด ์—…๋ฐ์ดํŠธ

์ทจ์†Œ์„  ์Šคํƒ€์ผ๋ง

// components/TodoListItem.vue

<template>
  <div>
    <span 
      @click="updateTodoStatus"
      :class="{ 'is-completed' : todo.isCompleted }"
      >
      {{ todo.title }}
    </span>
  <button @click="deleteTodo">Delete</button>
  </div>
</template>

<script>
export default {
  name: 'TodoListItem',
  props: {
    todo: Object,
  },
  methods: {
    deleteTodo() {
      this.$store.dispatch('deleteTodo', this.todo)
    },
    updateTodoStatus() {
      this.$store.dispatch('updateTodoStatus', this.todo)
    }
  },
}
</script>

<style>
  .is-completed {
    text-decoration: line-through;
  }
</style>

โœ” CSS ์ž‘์„ฑ ํ›„ v-bind ํ™œ์šฉํ•ด isCompleted ๊ฐ’์— ๋”ฐ๋ผ css ํด๋ž˜์Šค๊ฐ€ ํ† ๊ธ€ ๋ฐฉ์‹์œผ๋กœ ์ ์šฉ๋˜๋„๋ก ์ž‘์„ฑ

๋™์ž‘ ํ™•์ธ

์ƒํƒœ๋ณ„ todo ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ

์ „์ฒด todo ๊ฐœ์ˆ˜

export default new Vuex.Store({
  state: {
    todos: [],
  },
  getters: {
    allTodosCount(state) {
      return state.todos.length
    }
  },
  ...
})

โœ” allTodosCount getters ์ž‘์„ฑ
โœ” state์— ์žˆ๋Š” todos ๋ฐฐ์—ด์˜ ๊ธธ์ด ๊ณ„์‚ฐ

// App.vue

<template>
  <div id="app">
    <h1>Todo List</h1>
    <h2>All Todos: {{ allTodosCount }}</h2>
    <TodoList/>
    <TodoForm/>
  </div>
</template>

<script>
import TodoForm from './components/TodoForm.vue'
import TodoList from './components/TodoList.vue'

export default {
  name: 'App',
  components: {
    TodoList,
    TodoForm
  },
  computed: {
    allTodosCount() {
      return this.$$store.getters.allTodosCount
    }
  },
}
</script>

โœ” getters์— ๊ณ„์‚ฐ๋œ ๊ฐ’์„ ๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ computed์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ

์™„๋ฃŒ๋œ todo ๊ฐœ์ˆ˜

// index.js

export default new Vuex.Store({
  state: {
    todos: [],
  },
  getters: {
    allTodosCount(state) {
      return state.todos.length
    },
    completedTodosCount(state) {
      // 1. ์™„๋ฃŒ๋œ todo๋งŒ ๋ชจ์•„ ๋†“์€ ์ƒˆ๋กœ์šด ๊ฐ์ฒด ์ƒ์„ฑ
      const completedTodos = state.todos.filter((todo) => {
        return todo.isCompleted == true
      })
      // 2. ๊ธธ์ด ๋ฐ˜ํ™˜
      return completedTodos.length
    },
  },
}

โœ” completedTodosCount getters ์ž‘์„ฑ
โœ” isCompleted๊ฐ€ true ์ธ todo๋“ค๋งŒ ํ•„ํ„ฐ๋งํ•œ ๋ฐฐ์—ด์„ ๋งŒ๋“ค๊ณ  ๊ธธ์ด ๊ณ„์‚ฐ

// App.vue

<template>
  <div id="app">
    <h1>Todo List</h1>
    <h2>All Todos: {{ allTodosCount }}</h2>
    <h2>Completed Todo: {{ completedTodosCount }}</h2>
    <TodoList/>
    <TodoForm/>
  </div>
</template>

<script>
import TodoForm from './components/TodoForm.vue'
import TodoList from './components/TodoList.vue'

export default {
  name: 'App',
  components: {
    TodoList,
    TodoForm
  },
  computed: {
    allTodosCount() {
      return this.$store.getters.allTodosCount
    },
    completedTodosCount() {
      return this.$store.getters.completedTodosCount
    }
  },
}
</script>

๋ฏธ์™„๋ฃŒ๋œ todo ๊ฐœ์ˆ˜

// index.js

export default new Vuex.Store({
  state: {
    todos: [],
  },
  getters: {
    ...
    unCompletedTodosCount(state, getters) {
      return getters.allTodosCount - getters.completedTodosCount
    }
  },
})

โœ” ๋ฏธ์™„๋ฃŒ๋œ todo ๊ฐœ์ˆ˜ === ์ „์ฒด ๊ฐœ์ˆ˜ - ์™„๋ฃŒ ๊ฐœ์ˆ˜
โœ” getters ๊ฐ€ ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ getters ๋ฐ›๋Š” ๊ฒƒ ํ™œ์šฉ

// App.vue

<template>
  <div id="app">
    <h1>Todo List</h1>
    <h2>All Todos: {{ allTodosCount }}</h2>
    <h2>Completed Todo: {{ completedTodosCount }}</h2>
    <h2>unCompleted Todo: {{ unCompletedTodosCount }}</h2>
    <TodoList/>
    <TodoForm/>
  </div>
</template>

<script>
import TodoForm from './components/TodoForm.vue'
import TodoList from './components/TodoList.vue'

export default {
  name: 'App',
  components: {
    TodoList,
    TodoForm
  },
  computed: {
    allTodosCount() {
      return this.$store.getters.allTodosCount
    },
    completedTodosCount() {
      return this.$store.getters.completedTodosCount
    },
    unCompletedTodosCount() {
      return this.$store.getters.unCompletedTodosCount
    }
  },
}
</script>

๋™์ž‘ ํ™•์ธ

 

'โญ Personal_Study > Vue' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

UX & UI  (0) 2022.11.18
Todo: Local Storage  (0) 2022.11.16
Lifecycle Hooks  (0) 2022.11.14
Vuex  (0) 2022.11.14
Emit Event  (0) 2022.11.13

๋Œ“๊ธ€