Syw.Frontend

๐Ÿงฉ Vue 2๋กœ ๋ฐฐ์šฐ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ์ดˆ

1๋‹จ๊ณ„. Vue 2 + Express + Axios ์ธ์ฆ ๊ธฐ๋ฐ˜ ํˆฌ๋‘์•ฑ ๋งŒ๋“ค๊ธฐ

1-24. Todo ์ƒํƒœ๋ฅผ Vuex๋กœ ์ด๊ด€ํ•˜๊ธฐ

1. store/modules/todo.js ์ƒ์„ฑ

// store/modules/todo.js
import axios from '@/utils/axios';

const state = {
  todos: [],
};

const getters = {
  allTodos: (state) => state.todos,
};

const mutations = {
  setTodos(state, todos) {
    state.todos = todos;
  },
  addTodo(state, todo) {
    state.todos.unshift(todo);
  },
  removeTodo(state, id) {
    state.todos = state.todos.filter((t) => t.id !== id);
  },
  updateTodo(state, updated) {
    state.todos = state.todos.map((todo) =>
      todo.id === updated.id ? updated : todo
    );
  },
};

const actions = {
  async fetchTodos({ commit }) {
    const res = await axios.get('/api/todos');
    commit('setTodos', res.data);
  },
  async addTodo({ commit }, title) {
    const res = await axios.post('/api/todos', { title, completed: false });
    commit('addTodo', res.data);
  },
  async deleteTodo({ commit }, id) {
    await axios.delete(`/api/todos/${id}`);
    commit('removeTodo', id);
  },
  async toggleTodo({ commit, state }, id) {
    const target = state.todos.find((t) => t.id === id);
    if (!target) return;
    const updated = { ...target, completed: !target.completed };
    await axios.put(`/api/todos/${id}`, { completed: updated.completed });
    commit('updateTodo', updated);
  },
  async updateTodo({ commit }, { id, title }) {
    await axios.put(`/api/todos/${id}`, { title });
    commit('updateTodo', { id, title });
  },
};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

2. store/index.js์— ๋“ฑ๋ก

import Vue from 'vue';
import Vuex from 'vuex';
import auth from './modules/auth';
import theme from './modules/theme';
import todo from './modules/todo'; // โœ… ์ถ”๊ฐ€

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    auth,
    theme,
    todo, // โœ… ๋“ฑ๋ก
  },
});

3. TodoApp.vue ์ˆ˜์ •

<template>
  <div class="todo-container">
    <h2>๐Ÿงพ ํ•  ์ผ ๊ด€๋ฆฌ (Vuex)</h2>
    <div class="form">
      <input v-model="newTodo" placeholder="ํ•  ์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”" />
      <button @click="handleAdd">์ถ”๊ฐ€</button>
    </div>
    <ul class="todo-list">
      <TodoItem
        v-for="todo in allTodos"
        :key="todo.id"
        :content="todo.title"
        :completed="todo.completed"
        :editing="editingId === todo.id"
        @delete="handleDelete(todo.id)"
        @edit="startEdit(todo.id)"
        @update="handleUpdate(todo.id, $event)"
        @toggle="handleToggle(todo.id)"
      />
    </ul>
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';
import TodoItem from './TodoItem.vue';

export default {
  components: { TodoItem },
  data() {
    return {
      newTodo: '',
      editingId: null,
    };
  },
  computed: {
    ...mapGetters('todo', ['allTodos']),
  },
  mounted() {
    this.fetchTodos();
  },
  methods: {
    ...mapActions('todo', [
      'fetchTodos',
      'addTodo',
      'deleteTodo',
      'updateTodo',
      'toggleTodo',
    ]),
    async handleAdd() {
      if (!this.newTodo.trim()) return;
      await this.addTodo(this.newTodo);
      this.newTodo = '';
    },
    async handleDelete(id) {
      await this.deleteTodo(id);
    },
    startEdit(id) {
      this.editingId = id;
    },
    async handleUpdate(id, newTitle) {
      if (newTitle !== null && newTitle.trim()) {
        await this.updateTodo({ id, title: newTitle });
      }
      this.editingId = null;
    },
    async handleToggle(id) {
      await this.toggleTodo(id);
    },
  },
};
</script>

์ด์ œ ๋ชจ๋“  Todo ๊ด€๋ จ ์ƒํƒœ์™€ ๋กœ์ง์€ Vuex๋กœ ๊ด€๋ฆฌ๋˜๊ณ , ์ปดํฌ๋„ŒํŠธ๋Š” ์˜ค๋กœ์ง€ ๋ฐ์ดํ„ฐ ํ‘œํ˜„์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

FE ์ €์žฅ์†Œ

GitHub - heroyooi/vue2-app at ch24

๐Ÿ’ฌ ๋Œ“๊ธ€

    โ€ป ๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.