Syw.Frontend

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

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

1-12. ํ•  ์ผ ์™„๋ฃŒ/๋ฏธ์™„๋ฃŒ ํ† ๊ธ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„, nodemon ์„ค์น˜ ๋ฐ ์ ์šฉ

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • ํ•  ์ผ์„ ์™„๋ฃŒํ•˜๊ฑฐ๋‚˜ ๋‹ค์‹œ ๋ฏธ์™„๋ฃŒ ์ƒํƒœ๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก UI์™€ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒดํฌ๋ฐ•์Šค๋ฅผ ํ†ตํ•ด ์ƒํƒœ๋ฅผ ํ† ๊ธ€ํ•˜๊ณ , ์„œ๋ฒ„์— PUT ์š”์ฒญ์œผ๋กœ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.

โœ… 1. ์„œ๋ฒ„(PUT) ๋ผ์šฐํ„ฐ ๋จผ์ € ์ค€๋น„

์ด๋ฏธ ๊ตฌํ˜„๋œ PUT /api/todos/:id ๋ผ์šฐํ„ฐ์— completed ํ•„๋“œ๋ฅผ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ™•์žฅํ•ด ๋‘ก๋‹ˆ๋‹ค:

// routes/todos.js (๋ณ€๊ฒฝ ์‚ฌํ•ญ)
router.put('/:id', async (req, res) => {
  const userId = req.body.userId;
  const id = Number(req.params.id);
  const { title, completed } = req.body;

  if (!userId || !todosByUser[userId]) {
    return res.status(400).json({ message: '์ž˜๋ชป๋œ ์š”์ฒญ' });
  }

  todosByUser[userId] = todosByUser[userId].map((todo) => {
    if (todo.id !== id) return todo;
    return {
      ...todo,
      ...(title !== undefined && { title }),
      ...(completed !== undefined && { completed }),
    };
  });

  await saveTodos(); // โœ… ๋ณ€๊ฒฝ ํ›„ ํŒŒ์ผ ์ €์žฅ
  res.sendStatus(200);
});

โœ… 2. TodoItem.vue ์ˆ˜์ • โ€“ ์ฒดํฌ๋ฐ•์Šค UI ์ถ”๊ฐ€

<template>
  <li class="todo-item">
    <template v-if="isEditing">
      <input v-model="editText" @keyup.enter="submitEdit" />
      <button @click="submitEdit">ํ™•์ธ</button>
      <button @click="cancelEdit">์ทจ์†Œ</button>
    </template>
    <template v-else>
      <label class="label">
        <input type="checkbox" :checked="completed" @change="$emit('toggle')" />
        <span :class="{ done: completed }">{{ content }}</span>
      </label>
      <div class="actions">
        <button @click="$emit('edit')">โœ๏ธ</button>
        <button @click="$emit('delete')">๐Ÿ—‘๏ธ</button>
      </div>
    </template>
  </li>
</template>

<script>
export default {
  props: {
    content: String,
    editing: Boolean,
    completed: Boolean,
  },
  data() {
    return {
      editText: this.content,
    };
  },
  computed: {
    isEditing() {
      return this.editing;
    },
  },
  methods: {
    submitEdit() {
      this.$emit('update', this.editText.trim());
    },
    cancelEdit() {
      this.editText = this.content;
      this.$emit('update', null); // null์€ ์ทจ์†Œ ์˜๋ฏธ
    },
  },
};
</script>

<style scoped>
.todo-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 0;
  border-bottom: 1px dashed #ddd;
}

.label {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
}

.done {
  text-decoration: line-through;
  color: gray;
}

.todo-item input[type='text'] {
  flex: 1;
  margin-right: 8px;
}
.todo-item button {
  margin-left: 4px;
}
.actions {
  display: flex;
  gap: 4px;
}
</style>

โœ… 3. App.vue์—์„œ ์™„๋ฃŒ ์ƒํƒœ ํ† ๊ธ€ ๊ธฐ๋Šฅ ์ถ”๊ฐ€

<TodoItem
  v-for="todo in todos"
  :key="todo.id"
  :content="todo.title"
  :completed="todo.completed"
  :editing="editingId === todo.id"
  @delete="removeTodo(todo.id)"
  @edit="startEdit(todo.id)"
  @update="updateTodo(todo.id, $event)"
  @toggle="toggleComplete(todo.id)"
/>
methods: {
  // ...๊ธฐ์กด ์ฝ”๋“œ ์œ ์ง€
  async toggleComplete(id) {
    const target = this.todos.find((todo) => todo.id === id);
    const updated = { ...target, completed: !target.completed };

    await axios.put(`${API_URL}/${id}`, {
      completed: updated.completed,
      userId: this.userId,
    });

    this.todos = this.todos.map((todo) => (todo.id === id ? updated : todo));
  },
}

๐Ÿงช ํ™•์ธ ์‚ฌํ•ญ

  1. ์ฒดํฌ๋ฐ•์Šค๋ฅผ ํด๋ฆญํ•˜๋ฉด ํ•  ์ผ์ด ์™„๋ฃŒ/๋ฏธ์™„๋ฃŒ๋กœ ํ† ๊ธ€๋จ
  2. ์„œ๋ฒ„์—๋„ ์ €์žฅ๋จ โ†’ ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ์ƒํƒœ ์œ ์ง€๋จ
  3. ํ…์ŠคํŠธ๋Š” ์™„๋ฃŒ ์‹œ ํšŒ์ƒ‰ + ์ทจ์†Œ์„  ํ‘œ์‹œ๋จ

๐Ÿš€ ํŒ: ๋ณ€๊ฒฝ๋งˆ๋‹ค ์„œ๋ฒ„ ์ž๋™ ์žฌ์‹œ์ž‘ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

ํ˜„์žฌ๋Š” api ์„œ๋ฒ„์˜ ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์žฌ์‹œ์ž‘ ํ•˜๊ธฐ ์ „์—๋Š” ๊ธฐ๋Šฅ์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•ญ์ƒ ๋งค๋ฒˆ express ์„œ๋ฒ„๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„๋ฅผ ์žฌ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กœ์šฐ๋ฏ€๋กœ ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ์„œ๋ฒ„๋ฅผ ์žฌ์‹œ์ž‘ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜์—ฌ ์ ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘‰ nodemon ์„ค์น˜

npm install -D nodemon

๐Ÿ‘‰ package.json ์ˆ˜์ •

"scripts": {
  "dev": "nodemon server.js"
}
npm run dev

๐Ÿง  ์š”์•ฝ

๊ธฐ๋Šฅ ์„ค๋ช…
์™„๋ฃŒ ํ‘œ์‹œ ์ฒดํฌ๋ฐ•์Šค UI๋กœ ๊ตฌํ˜„
์ƒํƒœ ๋ณ€๊ฒฝ PUT ์š”์ฒญ์œผ๋กœ completed ํ•„๋“œ ์—…๋ฐ์ดํŠธ
์ƒํƒœ ๋ฐ˜์˜ ๋ฆฌ์ŠคํŠธ UI + ์„œ๋ฒ„ ๋™๊ธฐํ™” ์œ ์ง€

FE ์ €์žฅ์†Œ

GitHub - heroyooi/vue2-app at ch12

BE ์ €์žฅ์†Œ

GitHub - heroyooi/vue2-api at ch12

๐Ÿ’ฌ ๋Œ“๊ธ€

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