Syw.Frontend

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

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

1-3. ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ โ€“ props์™€ $emit ์ดํ•ดํ•˜๊ธฐ

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

  • ์ปดํฌ๋„ŒํŠธ๋ฅผ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•์„ ์ตํžŒ๋‹ค.
  • ๋ถ€๋ชจ โ†’ ์ž์‹ ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์€ props, ์ž์‹ โ†’ ๋ถ€๋ชจ ์ด๋ฒคํŠธ ์ „๋‹ฌ์€ $emit์„ ์‚ฌ์šฉํ•œ๋‹ค.

1๏ธโƒฃ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ

ํด๋” ๊ตฌ์กฐ ์˜ˆ์‹œ

src/
โ”œโ”€โ”€ components/
โ”‚   โ””โ”€โ”€ TodoItem.vue
โ”œโ”€โ”€ App.vue
โ””โ”€โ”€ main.js

2๏ธโƒฃ TodoItem.vue (์ž์‹ ์ปดํฌ๋„ŒํŠธ)

<template>
  <li class="todo-item">
    โœ… {{ content }}
    <button @click="$emit('delete')">์‚ญ์ œ</button>
  </li>
</template>

<script>
export default {
  props: {
    content: {
      type: String,
      required: true
    }
  }
}
</script>

<style scoped>
.todo-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 0;
  border-bottom: 1px dashed #ddd;
}
.todo-item button {
  background: #ff5c5c;
  color: white;
  border: none;
  padding: 4px 10px;
  border-radius: 6px;
  cursor: pointer;
}
.todo-item button:hover {
  background: #e14e4e;
}
</style>

3๏ธโƒฃ App.vue (๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ)

<template>
  <div class="todo-container">
    <h2>๐Ÿ“ ์˜ค๋Š˜์˜ ํ•  ์ผ</h2>
    <div class="form">
      <input v-model="newTodo" placeholder="ํ•  ์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”" />
      <button @click="addTodo">์ถ”๊ฐ€</button>
    </div>
    <ul class="todo-list">
      <TodoItem
        v-for="(todo, index) in todos"
        :key="index"
        :content="todo"
        @delete="removeTodo(index)"
      />
    </ul>
  </div>
</template>

<script>
import TodoItem from './components/TodoItem.vue'

export default {
  components: {
    TodoItem
  },
  data() {
    return {
      newTodo: '',
      todos: []
    }
  },
  methods: {
    addTodo() {
      const trimmed = this.newTodo.trim()
      if (trimmed) {
        this.todos.push(trimmed)
        this.newTodo = ''
      }
    },
    removeTodo(index) {
      this.todos.splice(index, 1)
    }
  }
}
</script>

<style scoped>
@import './assets/base.css';

.todo-container {
  max-width: 400px;
  margin: 40px auto;
  padding: 24px;
  border: 1px solid #ccc;
  border-radius: 12px;
  background: #f9f9f9;
  box-shadow: 0 4px 10px rgba(0,0,0,0.05);
}

h2 {
  text-align: center;
  margin-bottom: 20px;
}

.form {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}

input {
  flex: 1;
  padding: 8px;
  border: 1px solid #aaa;
  border-radius: 6px;
  font-size: 14px;
}

button {
  padding: 8px 16px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

button:hover {
  background-color: #36956d;
}

.todo-list {
  list-style: none;
  padding: 0;
}
</style>

๐Ÿ“ src/assets/base.css

์ด ํŒŒ์ผ์€ ์ „์—ญ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋ณธ ๋ฆฌ์…‹ ๋ฐ ๊ณตํ†ต ์Šคํƒ€์ผ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

/* src/assets/base.css */

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  background-color: #f2f2f2;
  color: #333;
}

ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

button {
  font-family: inherit;
  font-size: inherit;
}

๐Ÿ“˜ ๋˜ ๋‹ค๋ฅธ base.css ์ ์šฉ ๋ฐฉ์‹

main.js์—์„œ global import

// src/main.js
import Vue from 'vue'
import App from './App.vue'
import './assets/base.css' // ์ „์—ญ ์Šคํƒ€์ผ

new Vue({
  render: h => h(App),
}).$mount('#app')

๐Ÿง  props์™€ $emit ๊ฐœ๋… ์š”์•ฝ

๐Ÿ“Œ props: ๋ถ€๋ชจ๊ฐ€ ์ž์‹์—๊ฒŒ ๊ฐ’์„ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉ

// ๋ถ€๋ชจ์—์„œ
<TodoItem :content="todo" />

// ์ž์‹์—์„œ
props: {
  content: {
    type: String,
    required: true
  }
}

props๋Š” ์ฝ๊ธฐ ์ „์šฉ์ž…๋‹ˆ๋‹ค. ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋Š” props ๊ฐ’์„ ์ง์ ‘ ๋ณ€๊ฒฝํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ“Œ $emit: ์ž์‹์ด ๋ถ€๋ชจ์—๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ ์•Œ๋ฆด ๋•Œ ์‚ฌ์šฉ

// ์ž์‹์—์„œ
<button @click="$emit('delete')">์‚ญ์ œ</button>

// ๋ถ€๋ชจ์—์„œ
<TodoItem @delete="removeTodo(index)" />

$emit('์ด๋ฒคํŠธ๋ช…')์€ ๋งˆ์น˜ ์ž์‹์ด ๋ถ€๋ชจ์—๊ฒŒ โ€œ๋‚˜ ๋ญ”๊ฐ€ ํ–ˆ๋‹ค!โ€๊ณ  ์•Œ๋ ค์ฃผ๋Š” ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค.


๐ŸŽฏ ๊ด€๊ณ„ ์ •๋ฆฌ

๋ฐฉํ–ฅ ๊ธฐ๋Šฅ ์˜ˆ์‹œ
๋ถ€๋ชจ โ†’ ์ž์‹ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ :content="todo" (props)
์ž์‹ โ†’ ๋ถ€๋ชจ ์ด๋ฒคํŠธ ์ „๋‹ฌ $emit('delete') (event)

โœ… ํ•ต์‹ฌ ๊ฐœ๋… ์ •๋ฆฌ

๊ธฐ๋Šฅ ์„ค๋ช…
props ๋ถ€๋ชจ โ†’ ์ž์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์†์„ฑ
$emit ์ž์‹ โ†’ ๋ถ€๋ชจ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉ
์ปดํฌ๋„ŒํŠธ ์žฌ์‚ฌ์šฉ v-for๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜๋ณต ๋ Œ๋”๋ง ๊ฐ€๋Šฅ
scoped ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„ CSS ์บก์Аํ™”

GitHub - heroyooi/vue2-app at ch3

๐Ÿ’ฌ ๋Œ“๊ธ€

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