컴포넌트

VUE.JS BASIC
Chapter 6. 컴포넌트
하나의 화면을 작은 기능 단위로 나누고,
props와 emit으로 컴포넌트 사이에 데이터를 주고받는 방법을 배웁니다.
프로그래밍 코드가 표시된 개발 화면
프로그램이 커질수록 하나의 파일에 모든 기능을 넣기보다 역할별로 나누어 관리하는 것이 중요합니다.
6.1 컴포넌트란?

지금까지는 대부분의 코드를 App.vue 하나에 작성했습니다. 작은 예제에서는 문제가 없지만 프로그램이 커지면 하나의 파일 안에 여러 기능이 계속 추가됩니다.

App.vue
사원등록 · 사원목록 · 사원수정 · 검색 · 메뉴 · 버튼 · 메시지 · ...

기능이 많아질수록 App.vue가 길어지고, 원하는 코드를 찾거나 수정하는 것도 어려워집니다.

💡 컴포넌트(Component)란?

Vue에서 화면과 기능을 여러 개의 작은 단위로 나누어 관리할 수 있는데, 이렇게 나눈 하나하나의 단위를 컴포넌트라고 합니다.

전체 화면

├─ 메뉴
├─ 사원 등록
├─ 사원 목록
└─ 사원 상세정보

각 영역은 필요에 따라 별도의 .vue 파일로 만들 수 있습니다.

6.2 컴포넌트를 사용하는 이유

컴포넌트로 프로그램을 나누면 다음과 같은 장점이 있습니다.

  • 프로그램을 기능별로 나눌 수 있습니다.
  • 하나의 파일이 지나치게 길어지는 것을 줄일 수 있습니다.
  • 같은 화면이나 기능을 여러 곳에서 재사용할 수 있습니다.
  • 수정해야 할 코드의 위치를 찾기 쉬워집니다.
  • 여러 개발자가 기능을 나누어 작업하기 편리합니다.

예를 들어 사원관리 프로그램은 다음과 같이 구성할 수 있습니다.

App.vue │ ├─ EmployeeForm.vue │ └─ 사원 등록 │ ├─ EmployeeList.vue │ └─ 사원 목록 │ └─ EmployeeInfo.vue └─ 사원 상세정보
💡 핵심 생각

하나의 거대한 App.vue를 만드는 것이 아니라 역할이 분명한 작은 컴포넌트들을 조립해 전체 화면을 만든다고 생각하면 이해하기 쉽습니다.

6.3 첫 번째 컴포넌트 만들기

먼저 src/components 폴더에 Hello.vue 파일을 만들어 봅니다.

src │ ├─ components │ └─ Hello.vue │ └─ App.vue
Hello.vue
src/components/Hello.vue
<template>
  <h2>
    안녕하세요. Vue 컴포넌트입니다.
  </h2>
</template>

이제 Hello.vue를 App.vue에서 불러와 사용합니다.

App.vue
src/App.vue
<script setup>
import Hello from './components/Hello.vue'
</script>

<template>
  <h1>Vue 프로그램</h1>

  <Hello />
</template>
실행 결과
Vue 프로그램
안녕하세요. Vue 컴포넌트입니다.
컴포넌트 파일 작성

import

template에서 사용
💡 컴포넌트 사용 3단계

① .vue 파일 만들기 → ② import 하기 → ③ template에서 태그처럼 사용하기 순서로 기억하면 됩니다.

6.4 컴포넌트에 프로그램 작성하기

컴포넌트도 일반적인 .vue 파일이므로 script, template, style을 사용할 수 있습니다.

Counter.vue
src/components/Counter.vue
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increase() {
  count.value++
}
</script>

<template>
  <h2>카운터</h2>

  <p>
    현재 값 : {{ count }}
  </p>

  <button @click="increase">
    증가
  </button>
</template>
App.vue
src/App.vue
<script setup>
import Counter from './components/Counter.vue'
</script>

<template>
  <h1>메인 화면</h1>

  <Counter />
</template>
💡 독립적인 데이터

Counter.vue 내부의 count 데이터와 increase() 함수는 해당 Counter 컴포넌트 안에서 독립적으로 관리됩니다.

노트북으로 웹 프로그램을 개발하는 모습
하나의 프로그램도 작은 기능 단위로 나누면 코드의 역할과 구조를 파악하기 쉬워집니다.
6.5 컴포넌트 재사용하기

한 번 만든 컴포넌트는 화면에서 여러 번 사용할 수 있습니다.

App.vue
<template>
  <h1>카운터 프로그램</h1>

  <Counter />
  <Counter />
  <Counter />
</template>
실행 화면
카운터 1
현재 값 : 0
[증가]

카운터 2
현재 값 : 0
[증가]

카운터 3
현재 값 : 0
[증가]

세 개의 Counter 컴포넌트가 생성되며, 각 컴포넌트는 자신의 count 데이터를 따로 가지고 있습니다.

💡 컴포넌트 재사용의 장점

동일한 코드를 복사해서 세 번 작성하는 것이 아니라, 한 번 만든 컴포넌트를 필요한 곳에서 반복해서 사용할 수 있습니다.

6.6 부모 컴포넌트와 자식 컴포넌트

Vue 컴포넌트는 서로 부모와 자식의 관계를 가질 수 있습니다.

App.vue

└─ EmployeeInfo.vue

App.vue가 EmployeeInfo.vue를 사용하고 있다면 다음과 같이 부릅니다.

컴포넌트 관계
  • App.vue → 부모 컴포넌트
  • EmployeeInfo.vue → 자식 컴포넌트

실제 프로그램에서는 이런 관계가 여러 단계로 이어질 수 있습니다.

App.vue │ ├─ Header.vue │ ├─ EmployeeForm.vue │ ├─ EmployeeList.vue │ │ │ └─ EmployeeItem.vue │ └─ Footer.vue
6.7 props

부모 컴포넌트가 가지고 있는 데이터를 자식 컴포넌트로 전달하려면 props를 사용합니다.

App.vue
부모 컴포넌트
<script setup>
import EmployeeInfo from './components/EmployeeInfo.vue'

const employeeName = '홍길동'
</script>

<template>
  <EmployeeInfo
    :name="employeeName"
  />
</template>
EmployeeInfo.vue
자식 컴포넌트
<script setup>
const props = defineProps({
  name: String
})
</script>

<template>
  <h2>사원정보</h2>

  <p>
    이름 : {{ props.name }}
  </p>
</template>
실행 결과
사원정보
이름 : 홍길동
💡 defineProps()

자식 컴포넌트는 부모가 전달한 값을 defineProps()를 이용해 받을 수 있습니다.

6.8 props의 데이터 흐름

props는 기본적으로 부모에서 자식 방향으로 데이터를 전달합니다.

부모 App.vue
employeeName = "홍길동"
자식 EmployeeInfo.vue
name = "홍길동"
App.vue
employeeName = "홍길동"

│ props

EmployeeInfo.vue
name = "홍길동"


화면 출력
⚠ 방향을 기억하세요

props를 처음 배울 때 가장 중요한 것은 부모 → 자식 방향으로 데이터가 전달된다는 점입니다.

6.9 여러 값 전달하기

하나의 값뿐 아니라 이름, 전화번호, 부서처럼 여러 값을 자식 컴포넌트로 전달할 수도 있습니다.

App.vue
부모 컴포넌트
<script setup>
import EmployeeInfo from './components/EmployeeInfo.vue'

const employee = {
  name: '홍길동',
  phone: '010-1111-2222',
  department: '개발팀'
}
</script>

<template>
  <EmployeeInfo
    :name="employee.name"
    :phone="employee.phone"
    :department="employee.department"
  />
</template>
EmployeeInfo.vue
자식 컴포넌트
<script setup>
const props = defineProps({
  name: String,
  phone: String,
  department: String
})
</script>

<template>
  <h2>사원정보</h2>

  <p>이름 : {{ props.name }}</p>

  <p>
    전화번호 : {{ props.phone }}
  </p>

  <p>
    부서 : {{ props.department }}
  </p>
</template>
6.10 객체 자체 전달하기

전달해야 할 값이 많아지면 각각의 속성을 따로 전달하는 대신 객체 자체를 props로 전달할 수 있습니다.

App.vue
부모 컴포넌트
<script setup>
import EmployeeInfo from './components/EmployeeInfo.vue'

const employee = {
  id: 1,
  name: '홍길동',
  phone: '010-1111-2222',
  department: '개발팀'
}
</script>

<template>
  <EmployeeInfo
    :employee="employee"
  />
</template>
EmployeeInfo.vue
자식 컴포넌트
<script setup>
const props = defineProps({
  employee: Object
})
</script>

<template>
  <h2>사원정보</h2>

  <p>
    번호 : {{ props.employee.id }}
  </p>

  <p>
    이름 : {{ props.employee.name }}
  </p>

  <p>
    전화번호 :
    {{ props.employee.phone }}
  </p>

  <p>
    부서 :
    {{ props.employee.department }}
  </p>
</template>
💡 객체 전달

사원처럼 여러 속성을 하나의 데이터로 다루는 경우에는 객체 자체를 props로 전달하는 형태를 활용할 수 있습니다.

여러 화면을 보며 프로그래밍하는 개발 환경
부모와 자식 컴포넌트의 데이터 흐름을 이해하면 Vue 화면 구조를 훨씬 체계적으로 구성할 수 있습니다.
6.11 emit

props는 부모의 데이터를 자식에게 전달할 때 사용합니다. 반대로 자식 컴포넌트에서 어떤 일이 발생했음을 부모에게 알려야 할 때는 emit을 사용합니다.

부모 컴포넌트 데이터 관리
자식 컴포넌트 props로 데이터 받기
부모 컴포넌트 이벤트 처리
자식 컴포넌트 emit으로 이벤트 전달
props : 부모 → 자식
emit : 자식 → 부모

예를 들어 자식 컴포넌트의 삭제 버튼을 클릭했을 때 부모에게 "이 사원을 삭제해 주세요"라고 알려야 하는 상황이 있습니다.

6.12 emit 기본 예제
EmployeeItem.vue
자식 컴포넌트
<script setup>
const props = defineProps({
  name: String
})

const emit = defineEmits([
  'remove'
])

function removeEmployee() {
  emit('remove')
}
</script>

<template>
  <p>
    {{ props.name }}

    <button @click="removeEmployee">
      삭제
    </button>
  </p>
</template>
App.vue
부모 컴포넌트
<script setup>
import EmployeeItem from './components/EmployeeItem.vue'

function remove() {
  alert('사원을 삭제합니다.')
}
</script>

<template>
  <EmployeeItem
    name="홍길동"
    @remove="remove"
  />
</template>
자식의 삭제 버튼 클릭

emit('remove')

부모에게 이벤트 전달

부모의 remove() 실행
💡 defineEmits()

자식 컴포넌트에서 부모에게 전달할 이벤트를 정의할 때 defineEmits()를 사용합니다.

6.13 emit으로 데이터 전달하기

emit은 이벤트 이름뿐 아니라 부모에게 필요한 데이터도 함께 전달할 수 있습니다.

EmployeeItem.vue
자식 컴포넌트
<script setup>
const props = defineProps({
  id: Number,
  name: String
})

const emit = defineEmits([
  'remove'
])

function removeEmployee() {
  emit(
    'remove',
    props.id
  )
}
</script>

<template>
  <p>
    {{ props.id }}.
    {{ props.name }}

    <button @click="removeEmployee">
      삭제
    </button>
  </p>
</template>
App.vue
부모 컴포넌트
<script setup>
import EmployeeItem from './components/EmployeeItem.vue'

function remove(id) {
  alert(
    id + '번 사원을 삭제합니다.'
  )
}
</script>

<template>
  <EmployeeItem
    :id="1"
    name="홍길동"
    @remove="remove"
  />
</template>
삭제 버튼 클릭
1번 사원을 삭제합니다.
emit('remove', props.id)

부모의 remove(id)

id = 1
6.14 실습 — 사원목록 컴포넌트
🧪 실습 목표
사원 배열은 부모 App.vue가 관리하고, 실제 목록 출력은 EmployeeList.vue가 담당하도록 프로그램을 분리합니다.
src │ ├─ components │ └─ EmployeeList.vue │ └─ App.vue
App.vue
src/App.vue
<script setup>
import { ref } from 'vue'
import EmployeeList from './components/EmployeeList.vue'

const employees = ref([
  {
    id: 1,
    name: '홍길동',
    department: '개발팀'
  },
  {
    id: 2,
    name: '김철수',
    department: '영업팀'
  },
  {
    id: 3,
    name: '이영희',
    department: '관리팀'
  }
])

function removeEmployee(id) {
  employees.value =
    employees.value.filter(
      emp => emp.id !== id
    )
}
</script>

<template>
  <h1>사원관리 프로그램</h1>

  <EmployeeList
    :employees="employees"
    @remove="removeEmployee"
  />
</template>
💡 App.vue의 역할

employees 데이터를 가지고 있으며, 사원 삭제가 필요할 때 실제 배열을 변경하는 역할을 담당합니다.

6.15 EmployeeList.vue 작성

EmployeeList.vue는 부모가 전달한 employees를 props로 받아 화면에 목록을 출력합니다.

src/components/EmployeeList.vue
<script setup>
const props = defineProps({
  employees: Array
})

const emit = defineEmits([
  'remove'
])

function removeEmployee(id) {
  emit('remove', id)
}
</script>

<template>
  <h2>사원목록</h2>

  <p>
    전체 사원 :
    {{ props.employees.length }}명
  </p>

  <table border="1">
    <thead>
      <tr>
        <th>번호</th>
        <th>이름</th>
        <th>부서</th>
        <th>관리</th>
      </tr>
    </thead>

    <tbody>
      <tr
        v-for="emp in props.employees"
        :key="emp.id"
      >
        <td>{{ emp.id }}</td>
        <td>{{ emp.name }}</td>
        <td>{{ emp.department }}</td>

        <td>
          <button
            @click="removeEmployee(emp.id)"
          >
            삭제
          </button>
        </td>
      </tr>
    </tbody>
  </table>
</template>
App.vue

│ employees 전달

EmployeeList.vue

│ props

사원목록 출력

│ 삭제 버튼 클릭

emit('remove', id)


App.vue


employees에서 삭제


화면 자동 갱신
💡 중요한 구조

데이터는 부모가 관리하고, 자식은 받은 데이터를 출력하면서 필요한 이벤트를 부모에게 전달하는 구조입니다.

6.16 사원 등록 컴포넌트 만들기

이번에는 사원 등록 화면도 별도의 컴포넌트로 분리합니다.

src │ ├─ components │ ├─ EmployeeForm.vue │ └─ EmployeeList.vue │ └─ App.vue
EmployeeForm.vue
src/components/EmployeeForm.vue
<script setup>
import { ref } from 'vue'

const name = ref('')
const department = ref('개발팀')

const emit = defineEmits([
  'add'
])

function addEmployee() {
  if (name.value === '') {
    alert('이름을 입력하세요.')
    return
  }

  emit('add', {
    name: name.value,
    department: department.value
  })

  name.value = ''
  department.value = '개발팀'
}
</script>

<template>
  <h2>사원 등록</h2>

  <p>
    이름 :
    <input
      v-model="name"
    >
  </p>

  <p>
    부서 :
    <select
      v-model="department"
    >
      <option>개발팀</option>
      <option>영업팀</option>
      <option>관리팀</option>
    </select>
  </p>

  <button @click="addEmployee">
    등록
  </button>
</template>
사용자 입력

name / department

등록 버튼

emit('add', 사원정보)

부모 컴포넌트
💡 등록 데이터는 emit으로 전달

EmployeeForm.vue가 부모의 employees 배열을 직접 수정하는 것이 아니라, 새로 입력한 사원 정보를 add 이벤트와 함께 부모에게 전달합니다.

6.17 부모 컴포넌트에서 등록 처리하기

이제 App.vue에서 등록과 삭제 기능을 모두 처리합니다.

src/App.vue
<script setup>
import { ref } from 'vue'
import EmployeeForm from './components/EmployeeForm.vue'
import EmployeeList from './components/EmployeeList.vue'

const employees = ref([
  {
    id: 1,
    name: '홍길동',
    department: '개발팀'
  },
  {
    id: 2,
    name: '김철수',
    department: '영업팀'
  }
])

let nextId = 3

function addEmployee(employee) {
  employees.value.push({
    id: nextId++,
    name: employee.name,
    department: employee.department
  })
}

function removeEmployee(id) {
  employees.value =
    employees.value.filter(
      emp => emp.id !== id
    )
}
</script>

<template>
  <h1>사원관리 프로그램</h1>

  <EmployeeForm
    @add="addEmployee"
  />

  <hr>

  <EmployeeList
    :employees="employees"
    @remove="removeEmployee"
  />
</template>

이제 사원관리 프로그램은 각 기능별로 역할이 분리되었습니다.

App.vue

├─ employees 데이터 관리

├───────────────┐
EmployeeForm EmployeeList
│ │
사원 등록 사원 목록
│ │
emit(add) emit(remove)
│ │
└───────┬───────┘

App.vue

employees 변경

화면 자동 갱신
🧪 역할을 다시 확인해 봅시다
  • App.vue — 사원 배열과 등록·삭제 처리
  • EmployeeForm.vue — 사원 입력과 add 이벤트 발생
  • EmployeeList.vue — 사원 목록 출력과 remove 이벤트 발생
6.18 컴포넌트를 사용하지 않는 경우

컴포넌트를 사용하지 않으면 하나의 App.vue 안에 다음 기능이 모두 들어갈 수 있습니다.

App.vue
등록 입력창
등록 함수
목록 출력
삭제 함수
수정 함수
검색 기능
상세정보
메뉴
페이징
...

프로그램이 작을 때는 괜찮지만, 기능이 늘어날수록 코드의 위치를 찾고 수정하는 일이 어려워집니다.

컴포넌트를 사용하면 역할에 따라 다음처럼 분리할 수 있습니다.

EmployeeForm.vue EmployeeList.vue EmployeeItem.vue EmployeeSearch.vue EmployeeDetail.vue
💡 큰 프로그램에서 중요한 이유

화면과 기능을 역할별 컴포넌트로 나누면 프로그램이 커져도 구조를 비교적 명확하게 유지할 수 있습니다.

6.19 컴포넌트 이름 정하기

컴포넌트 파일명은 이름만 보아도 어떤 역할을 담당하는지 알 수 있도록 작성하는 것이 좋습니다.

기능 컴포넌트 이름 예
사원 EmployeeForm.vue / EmployeeList.vue / EmployeeDetail.vue
회원 MemberForm.vue / MemberList.vue
상품 ProductForm.vue / ProductList.vue
게시판 BoardList.vue / BoardWrite.vue / BoardDetail.vue
PascalCase

Vue 컴포넌트 파일명은 보통 각 단어의 첫 글자를 대문자로 작성하는 PascalCase 방식을 많이 사용합니다.

컴포넌트 파일명 예
EmployeeList.vue
ProductDetail.vue
BoardWrite.vue
💡 좋은 컴포넌트 이름

파일 이름만 보고도 어떤 기능인지 파악할 수 있도록 대상 + 역할 형태로 작성하면 관리하기 편리합니다.

📌 Chapter 6 핵심 정리
  • 컴포넌트는 화면과 기능을 작은 단위로 나누어 관리하는 Vue의 중요한 구조입니다.
  • 컴포넌트를 사용하면 코드를 기능별로 분리하고 같은 기능을 여러 곳에서 재사용할 수 있습니다.
  • 컴포넌트 파일은 보통 src/components 폴더에 만들고 App.vue 등에서 import하여 사용합니다.
  • 같은 컴포넌트를 여러 번 사용해도 각 컴포넌트의 내부 데이터는 독립적으로 관리할 수 있습니다.
  • 다른 컴포넌트를 사용하는 쪽을 부모 컴포넌트, 사용되는 쪽을 자식 컴포넌트라고 합니다.
  • props는 부모 컴포넌트의 데이터를 자식 컴포넌트로 전달할 때 사용합니다.
  • props의 기본 데이터 흐름은 부모 → 자식입니다.
  • defineProps()를 이용하면 자식 컴포넌트가 부모에게 받은 값을 사용할 수 있습니다.
  • emit은 자식 컴포넌트에서 발생한 일을 부모 컴포넌트에게 전달할 때 사용합니다.
  • emit의 흐름은 기본적으로 자식 → 부모라고 생각하면 이해하기 쉽습니다.
  • emit을 사용할 때 이벤트뿐 아니라 사원 번호와 같은 데이터도 함께 전달할 수 있습니다.
  • 실제 프로그램에서는 부모가 주요 데이터를 관리하고, 자식 컴포넌트들이 화면과 입력 기능을 분담하는 구조를 사용할 수 있습니다.
  • 컴포넌트 이름은 역할을 쉽게 알 수 있도록 작성하며 일반적으로 PascalCase 형식을 많이 사용합니다.

프로그램·홈페이지·강의가 필요하신가요?

프로그램 판매, 무료 다운로드, 홈페이지 제작, IT 강의 상담을 도와드립니다.

상담 신청하기