본문으로 바로가기
04. Vue 컴포넌트 설계: props, emit, slot과 생명주기
LinkedInGitHub
WORKSPACE

EXPLORER

157 POSTS
BLOG
면접질문
해야하는 거
AI 시대, 개발자는 사라지는가?
Python Counter, 빈도수를 쉽게 세는 방법
정렬
1. 투 포인터
DFS와 BFS
알고리즘 논리
코딩 테스트 핵심 알고리즘 정리파이썬 딕셔너리와 코딩 테스트 활용
예시로 살펴보는 AXAX 프로젝트, 문제 정의부터 확산까지AX 시대의 가치 정의와 현장 리딩
MSA
1. Singleton PatternSOLID
Kafka의 핵심 설계 원리Kafka 메시징 시스템의 구성과 동작 방식Kafka 기본 개념과 EC2 Docker 구성
쿠버네티스 입문
Modular Monolith1. MonolithMSA, 서비스 분리와 운영의 원리
Socket이란 무엇인가
네크워크 참조 모델
전체 데이터 구조REST API의 개념과 설계 원칙2. Field/ Parameter / Argument/ this
Actuator 란?EntityManagerJPA 연관관계 매핑JPA 트랜잭션(Transaction)입력값 검증Java의 AOP(Aspect Oriented Programming)Async : AsynchronousBeanJPA(Java Persistence API)Proxy 패턴Spring MVCdocker container1. Spirng 필요컴포넌트 스캔
Spring 컨테이너
application.yamlLombok
이번 강의는 무엇을 노리고 있을까?Spring AI를 배우기 전에 정리할 것23
1. 기술 스택
개발 문서를 읽기 위한 핵심 기술 용어
Docker를 이해하기 위한 운영체제 기초
sigterm
6. 설정과 저장소, 앱을 운영할 수 있는 상태로 만들기5. Service와 Ingress, 요청은 어디로 흐를까4. 직접 실험하는 Kubernetes, Pod 복구부터 롤백까지3. kubectl과 Pod, 상태에서 원인을 찾는 법2. 클러스터는 명령을 어떻게 Pod로 바꿀까1. 쿠버네티스, 원하는 상태와 컨테이너 이미지Calico쿠버네티스 입문, 원하는 상태를 유지하는 시스템
Psql JSONB, 행 잠금, 멱등성Psql 함수,프로시져,트리거
머신러닝 입문딥러닝 학습 기본 개념데이터 시각화기초 통계와 ML 파이프라인 연결분석 자동화와 파이프라인 설계
CNN 아키텍처 발전 과정이미지 세그멘테이션 모델과 핵심 개념객체 탐지 모델과 핵심 개념
딥러닝 데이터셋 엔지니어링
딥러닝 학습 문제 진단과 디버깅
도메인 적응 방법현대 LLM 워크플로의 패턴
Transformer에서 LoRA 적용 대상 정하기LoRA (Low-Rank Adaptation)
LLM 양자화와 QLoRA
분산 학습과 MLOps딥러닝 모델 경량화와 추론 최적화딥러닝 기본 학습 테크닉딥러닝 중급 학습 테크닉
Mixture of Experts(MoE) 핵심 개념멀티모달 파운데이션 모델 핵심 개념State Space Model과 MambaTransformer와 Vision Transformer
Latent Space
1Chunking?
DevOps 기초 1편
실습에서는?Agile 개요, 왜 필요한가?
CI/CD 기초 4편: 배포 전략과 운영CI/CD 기초 3편: Jenkins와 Argo CD를 이용한 GitOps 배포CI/CD 기초 2편: Docker 이미지와 배포 파이프라인CI/CD 기초 1편: 개념과 GitHub Actions
OCI: 컨테이너 이미지와 런타임의 공통 표준Docker 기초 13편: Compose Healthcheck와 실전 구성Docker 기초 12편: Compose 네트워크와 VolumeDocker 기초 11편: Compose 명령어와 환경 변수Docker 기초 10편: Compose 기본 구조와 이미지 빌드Docker 기초 9편: Docker 및 Kubernetes 네트워크Docker 기초 8편: 컨테이너 런타임과 격리Docker 기초 7편: 이미지 Layer와 tar 내부 구조Docker 기초 6편: 이미지 Layer와 빌드 최적화Docker 기초 5편: 컨테이너 기본 명령어와 VolumeDocker 기초 4편: 가상화와 컨테이너 이미지 생명주기Docker 기초 3편: CI/CD 연결과 배포 원칙Docker 기초 2편: Layer, Registry, Volume과 NetworkDocker 기초 1편: Dockerfile, Image와 Container
NginxNginx 로드 밸런싱과 HTTPSNginx 리버스 프록시와 Spring Boot 연결Nginx 기초와 동작 구조
05. Pinia 상태 관리: store 설계와 사용법04. Vue 컴포넌트 설계: props, emit, slot과 생명주기03. Vue Composition API 정리02. Vue 기초 문법 점검: JavaScript, 템플릿01. Vue.js 입문: 핵심 구조와 렌더링
Java 심화 Part 3: 함수형 프로그래밍과 LambdaJava 심화 Part 2: AnnotationJava 심화 Part 1: Reflection
Java 기초 Part 5: Stream APIJava 기초 Part 4: 제네릭Java 기초 Part 3: 제어문Java 기초 Part 2: 주석과 JavadocJava 기초 Part 1: 백엔드 배경과 Java 실행 구조
Java 디버깅 Part 1: 자주 헷갈리는 핵심 개념Java 디버깅 Part 2: VS Code 자동 컴파일과 프로젝트 구조
Java 실행과 JVM Part 3: ClassLoader와 JVM 메모리Java 실행과 JVM Part 2: 메모리와 데이터 흐름Java 실행과 JVM Part 1: Java와 Python 컴파일 비교
Java 객체지향 Part 5: static 메서드와 중첩 클래스Java 객체지향 Part 4: 상속과 인터페이스Java 객체지향 Part 3: 좋은 설계와 OOP 4대 특성Java 객체지향 Part 2: OOP 핵심 문법Java 객체지향 Part 1: 클래스, 객체, 필드와 생성자
Spring 기초 Part 11: Actuator와 애플리케이션 모니터링Spring 기초 Part 10: 비동기 처리와 @AsyncSpring 기초 Part 9: JPA 트랜잭션과 동시성 제어Spring 기초 Part 8: AOP와 공통 관심사 분리Spring 기초 Part 7: Proxy 패턴과 Spring ProxySpring 기초 Part 6: JPA 연관관계 매핑Spring 기초 Part 5: EntityManager와 영속성 컨텍스트Spring 기초 Part 4: JPA, Entity와 RepositorySpring 기초 Part 3: REST API 요청값과 입력값 검증Spring 기초 Part 2: Spring MVC 요청 처리 흐름Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotation
DNS = Domain Name System
Spring Boot, WebSocket, Vue, Docker 로 Raspberry Pi 실시간 모니터링 프로젝트 만들기 - 1편1. Spring Boot 구현
1. GitHub Project 만들기
Python 코드 품질: 디버깅부터 테스트와 자동화까지Python 01. 실행 구조와 실무 기초
sLLM 핵심 기술과 전체 구조제한된 자원에서 sLLM 구축하기
시대 단상에 대한 주저리주저리
WORKSPACE

SEARCH

제목, 카테고리와 태그로 검색하세요.

VERSION CONTROL

SOURCE CONTROL

masterGitHub Pages
저장소 열기
BUILD STATUS

RUN AND DEBUG

게시물은 GitHub Actions에서 검증하고 정적 페이지로 빌드합니다.

Actions 열기
WORKSPACE

MANAGE

홈 열기전체 게시물태그 보기블로그 소개
04. Vue 컴포넌트 설계: props, emit, slot과 생명주기●
workspace>posts>frontend>vue>vue-component.md
Frontend / Vue2026.08.031 min read7 tags

04. Vue 컴포넌트 설계: props, emit, slot과 생명주기

Vue 컴포넌트의 구조와 데이터 흐름, 상태 위치를 결정하는 기준, 생명주기 훅을 예제로 정리.

QUICK SUMMARY

핵심 요약

Vue 컴포넌트의 입력과 출력, 콘텐츠 확장, 상태 공유 범위와 생명주기 정리 방법을 살펴본다.

KEY CONCEPTS

핵심 개념

  • 부모는 props로 데이터를 전달하고 자식은 emit으로 변경 의도를 알림.
  • slot은 공통 구조를 유지하면서 내부 콘텐츠를 교체.
  • 상태는 필요한 컴포넌트 범위 중 가장 작은 곳에 둠.

컴포넌트란?

Vue 컴포넌트는 화면과 동작을 독립적이고 재사용 가능한 단위로 나눈 것이다. 한 컴포넌트가 하나의 명확한 책임을 가지면 테스트와 변경이 쉬워진다.

App
├── AppHeader
├── ProductList
│   └── ProductCard
└── ShoppingCart

Vue에서는 보통 .vue 파일 하나에 템플릿, 로직, 스타일을 작성한다. 이를 SFC(Single-File Component)라고 한다.

<script setup>
const title = '상품 목록'
</script>

<template>
  <section class="product-list">
    <h2>{{ title }}</h2>
  </section>
</template>

<style scoped>
.product-list {
  padding: 1rem;
}
</style>

지역 등록과 전역 등록

<script setup>에서 컴포넌트를 import하면 현재 컴포넌트의 템플릿에서 바로 사용할 수 있다.

<script setup>
import BaseButton from './components/BaseButton.vue'
</script>

<template>
  <BaseButton />
</template>

이런 지역 등록을 기본으로 사용하면 의존성이 파일에 드러나고, 사용하지 않는 컴포넌트를 빌드 도구가 제거하기 쉽다.

앱 전체에서 반복하는 매우 기본적인 컴포넌트는 전역 등록할 수도 있다.

import { createApp } from 'vue'
import App from './App.vue'
import BaseButton from './components/BaseButton.vue'

const app = createApp(App)

app.component('BaseButton', BaseButton)
app.mount('#app')

전역 등록이 많아지면 어디에서 컴포넌트가 왔는지 찾기 어려워지므로 제한적으로 사용한다.

컴포넌트의 공개 인터페이스

재사용 가능한 컴포넌트는 입력과 출력을 명확하게 만든다.

기능방향역할
props부모 → 자식데이터와 설정을 전달
emit자식 → 부모사용자 행동과 변경 의도를 알림
slot부모 → 자식 내부자식의 특정 위치에 콘텐츠를 넣음

props: 부모가 데이터를 전달

ProductCard는 어떤 상품을 보여줄지 직접 찾지 않고 props로 받는다. 그래야 같은 컴포넌트로 여러 상품을 표현할 수 있다.

<!-- ProductCard.vue -->
<script setup>
const props = defineProps({
  product: {
    type: Object,
    required: true
  }
})

const logProduct = () => {
  console.log(props.product.id)
}
</script>

<template>
  <article>
    <h3>{{ product.name }}</h3>
    <p>{{ product.price.toLocaleString() }}원</p>
    <button @click="logProduct">확인</button>
  </article>
</template>
<!-- 부모 컴포넌트 -->
<ProductCard :product="selectedProduct" />

defineProps()와 defineEmits()는 <script setup>에서 사용하는 컴파일러 매크로이므로 import하지 않는다. props는 읽기 전용이며 자식이 직접 수정하지 않는다.

emit: 자식이 변경 의도를 알린다

자식은 부모의 상태를 직접 바꾸는 대신 이벤트를 발생시킨다.

<!-- ProductCard.vue -->
<script setup>
defineProps({
  product: {
    type: Object,
    required: true
  }
})

const emit = defineEmits(['add-to-cart'])
</script>

<template>
  <button @click="emit('add-to-cart', product.id)">
    장바구니에 담기
  </button>
</template>
<!-- 부모 컴포넌트 -->
<script setup>
const addToCart = (productId) => {
  console.log('추가할 상품:', productId)
}
</script>

<template>
  <ProductCard
    :product="selectedProduct"
    @add-to-cart="addToCart"
  />
</template>

데이터 흐름을 한 방향으로 유지하면 상태를 누가 소유하고 변경하는지 추적하기 쉽다.

부모 상태 ──props──> 자식 화면
부모 함수 <──emit── 자식 행동

slot: 구조는 자식이, 콘텐츠는 부모가 정한다

자 재탕해보자, 어머니의 사골국이 떠오른다. 연속으로 12일동안 먹어봤다

카드의 테두리와 간격은 같지만 내부 콘텐츠가 달라진다면,

slot이 적합하다.

<!-- BaseCard.vue -->
<template>
  <article class="card">
    <header><slot name="header" /></header>
    <div><slot /></div>
  </article>
</template>
<!-- 부모 컴포넌트 -->
<BaseCard>
  <template #header>
    <h2>서울 날씨</h2>
  </template>

  <p>현재 기온은 28도입니다.</p>
</BaseCard>

상태는 어디에 둘까?

가장 중요한 기준은 상태를 실제로 필요한 범위 중 가장 작은 곳에 두는 것

상황우선 선택
한 컴포넌트만 사용하는 상태ref, reactive
부모와 자식이 함께 사용하는 상태부모가 소유하고 props / emit 사용
여러 컴포넌트에서 같은 로직을 재사용composable
가까운 하위 트리에 공통 의존성을 제공provide / inject
멀리 떨어진 화면이 같은 상태를 공유Pinia

드롭다운 열림 여부, 입력 중인 값, hover 상태처럼 해당 컴포넌트가 사라질 때 함께 없어져도 되는 값은 전역 저장소에 둘 이유가 없다.

const isDropdownOpen = ref(false)
const inputValue = ref('')

반대로 로그인 사용자나 장바구니처럼 서로 먼 화면이 함께 읽고 바꾸는 상태는 Pinia 같은 저장소가 유용하다.

composable로 로직 재사용하기

UI가 아니라 상태와 동작이 반복된다면 컴포넌트를 복사하지 말고 composable로 분리

// composables/useToggle.js
import { ref } from 'vue'

export function useToggle(initialValue = false) {
  const value = ref(initialValue)

  const toggle = () => {
    value.value = !value.value
  }

  return { value, toggle }
}
<script setup>
import { useToggle } from './composables/useToggle'

const { value: isOpen, toggle } = useToggle()
</script>

컴포넌트 생명주기

컴포넌트는 설정되고, DOM에 연결되고, 상태 변경에 따라 갱신된 뒤 제거된다.

단계대표 훅자주 하는 작업
마운트 전후onBeforeMount, onMountedDOM 접근, 브라우저 API 연결
업데이트 전후onBeforeUpdate, onUpdated갱신된 DOM 측정
언마운트 전후onBeforeUnmount, onUnmounted타이머와 이벤트 리스너 정리
<script setup>
import { onMounted, onUnmounted } from 'vue'

const handleResize = () => {
  console.log(window.innerWidth)
}

onMounted(() => {
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
})
</script>

onMounted()에서 등록한 외부 이벤트와 타이머는 onUnmounted()에서 해제한다. 단순한 파생 값은 onUpdated()보다 computed()로 표현하는 편이 명확하다.

설계 점검표

컴포넌트를 나눌 때 아래 질문을 확인하자

  1. 이 컴포넌트의 책임을 한 문장으로 설명할 수 있는가?
  2. 입력은 props, 출력은 emit으로 드러나는가?
  3. props를 자식에서 직접 수정하고 있지는 않은가?
  4. 반복되는 UI는 컴포넌트, 반복되는 로직은 composable로 분리했는가?
  5. 지역 상태를 불필요하게 Pinia로 올리지 않았는가?
  6. 외부 이벤트와 타이머를 언마운트 시점에 정리하는가?

컴포넌트 설계의 목표는 파일 수를 늘리는 것이 아니다. 상태의 소유자와 컴포넌트 사이의 계약을 분명하게 만드는 것이다.

참고 자료

  • Vue 공식 문서: Components Basics
  • Vue 공식 문서: Component Events
  • Vue 공식 문서: Slots
  • Vue 공식 문서: Lifecycle Hooks

TAGS#Frontend#Vue.js#Component#Props#Emits#Slots#Lifecycle
PREVIOUS03. Vue Composition API 정리NEXT05. Pinia 상태 관리: store 설계와 사용법
DISCUSSION

COMMENTS

GitHub 계정으로 로그인하여 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions에 공개 저장되며, 작성 내용과 GitHub 프로필 정보가 다른 방문자에게 보일 수 있습니다.

GitHub 로그인 후 댓글 쓰기Discussion 열기
DOCUMENT STRUCTURE

이 문서에는 목차가 없습니다.

DOCUMENT INFO
TYPE
Markdown
DATE
2026.08.03
READ
1 min read
WORDS
0
CATEGORY
Frontend / Vue
RELATED DOCUMENTS
05. Pinia 상태 관리: store 설계와 사용법03. Vue Composition API 정리02. Vue 기초 문법 점검: JavaScript, 템플릿01. Vue.js 입문: 핵심 구조와 렌더링6. 설정과 저장소, 앱을 운영할 수 있는 상태로 만들기5. Service와 Ingress, 요청은 어디로 흐를까
main Frontend / Vue
1 min readUTF-8Markdown
본문 글씨 크기
RECENTLY OPENED1
04. Vue 컴포넌트 설계: props, emit, slot과 생명주기recently opened