반응형
컴포넌트 > 템플릿 > 렌더함수
| 컴포넌트 정의 > 템플릿 작성(template) > 랜더 함수로 컴파일됨(rander function) > 가상 DOM 트리 생성 > 실제 DOM에 반영(mount) |
컴포넌트
| 컴포넌트 |
재사용 가능한 화면 단위 (UI 조각) Vue는 컴포넌트를 조립해서 SPA ( Single Page Application 단일 페이지 애플리케이션) 을 만듬 1. Vue 의 기본단위 2. HTML + JS +CSS UI 블록 장점 1. 재사용성 여러 곳에서 같은 UI/로직을 재사용 가능 Button, Modal, Card, ProductItem 등 2. 코드 분리 (관심사의 분리) UI 단위를 역할별로 나누어 관리 Header / Footer / Sidebar 컴포넌트 분리 3. 유지보수성 향상 하나의 컴포넌트만 수정하면 전체 반영 UserCard.vue만 수정하면 전체 사용자 카드 일괄 변경 4. 테스트 용이성 컴포넌트 단위로 유닛 테스트 가능 LoginForm.vue 단독 테스트 5. 구조적 설계 가능 트리 구조로 앱을 구성, 관리가 쉬움 App → Layout → Page → Item 구조 등 연결 1. props : 부모-> 자식 2. emit : 자식-> 부모 3. v-model : 양방향 |
||
| 파일명 규칙 | 최소 2개의 단어연결 , 케이스는 프로젝트당 사용하는걸로. MyComponent.vue - 파스칼케이스 my-component.vue - 케밥케이스 |
||
| 위치 | components 하위생성![]() |
||
| 연결 | ** 자기 자신을 컴포넌트로 재귀적으로 등록하면 루프(순환 참조) 오류 import MyButton from './components/MyButton.vue' 하고 import MyButton from '@/components/MyButton.vue' 하고 같음 vite.config.js 에 설정 resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
|
||
| import { createApp } from 'vue' import App from './App.vue' import MyButton from './components/MyButton.vue' const app = createApp(App) app.component('MyButton', MyButton) // 전역 등록 app.mount('#app') <template> <div> <MyButton /> </div> </template> |
전역 등록 (Global Registration) |
||
| <!-- ParentComponent.vue --> <template> <div> <MyButton /> </div> </template> <script> import MyButton from './MyButton.vue' export default { components: { MyButton // 지역 등록 } } </script> or <script setup> import MyButton from './MyButton.vue' // 지역 등록 (자동 인식) </script> |
지역 등록 (Local Registration) |
||
| 컴포넌트 사용 |
부모 -> 자식 props |
src/ ├── App.vue ← 부모 컴포넌트 └── components/ └── InfoBox.vue ← 자식 컴포넌트 App.vue -------------------------------------------------------------- <template> <div> <h1>부모 컴포넌트</h1> //1. 문자열 전달 <InfoBox title="공지사항입니다." /> //2. 바인딩 전달 <InfoBox :score="userScore" /> //3. 객체 <InfoBox :profile="userProfile" /> <!-- 혼합 전달 --> <InfoBox title="사용자 정보" :score="userScore" :profile="userProfile" /> </div> </template> <script> import InfoBox from './components/InfoBox.vue' export default { components: { InfoBox }, data() { return { userScore: 88, userProfile: { name: '홍길동', age: 30 } } } } </script> InfoBox.vue -------------------------------------------------------------- <template> <div class="box"> <h2>자식 컴포넌트</h2> <p v-if="title">[제목] {{ title }}</p> // 공지사항입니다. <p v-if="score !== undefined">[점수] {{ score }}</p>//88 <p v-if="profile">[프로필] 이름: {{ profile.name }}, 나이: {{ profile.age }}</p> // 홍길동 30 </div> </template> <script> export default { props: { title: String, score: Number, profile: Object } } </script> |
props 사용 1. 문자 2. 바인딩 전달 3. 객체전달 4. mounted() 안에서는 props를 안전하게 사용할 수 있습니다. 5. 자식컴포넌트에서 수정 불가. 데이터 흐름 단방향성 유지 |
| props 1. 정의 : 부모 → 자식으로 데이터 전달하는 방법 2. 목적 : 컴포넌틔 재사용성, 유현성 향상 3. 주의 : 자식에서 직접 수정안됨. 읽기전용 4. 선언방법 배열 : props: ['title'] <MyComponent 속성명1="값1" 속성명2="값2" 속성명N="값N" /> export default { props:['속성명1', '속성명2', ...'속성명N'] } 객체 : props: { title: { << 권장 type: String, required: true, // 필수 여부 default: '기본 제목', // 기본값 // 유효성 검사 validator: value => value.length <= 10 // or //validator(value) { // return ['black', 'blue'].includes(value) //} }, 4. 사용 템플릿 : {{ title }} or :style="{ backgroundColor: title }" 스크립트 : this.title mounted() : console.log(this.title) 5. 유효성검사 - type, required, validator >> 개발자 콘솔 warning 발생 |
props 와 mounted |
||
| <template> <div> <h1>부모 컴포넌트</h1> <!-- 1번 InfoBox --> <InfoBox bgcolor="red" size="3" /> <!-- 2번 InfoBox --> <InfoBox bgcolor="yellow" size="5" /> </div> </template> <script> import InfoBox from './components/InfoBox.vue' export default { components: { InfoBox } } </script> ------------------------------------------------------------------------- <template> <div class="box" :style="{ backgroundColor: bgcolor, fontSize: size + 'rem' }"> <p>배경색: {{ bgcolor }}</p> <p>글자 크기: {{ size }}rem</p> </div> </template> <script> export default { props: ['bgcolor', 'size'], mounted() { console.log('✅ InfoBox mounted') console.log('👉 배경색:', this.bgcolor) console.log('👉 글자크기:', this.size) } } </script> |
재사용이 가능한 css 에 적용가능 * mounted() 안에서는 props를 안전하게 사용할 수 있습니다. 1. props { bgcolor: tring, size : string } |
||
| <template> <div> <h2>부모 컴포넌트</h2> <PropsChild :onClickButton="handleChildClick" /> </div> </template> <script> import PropsChild from './components/PropsChild.vue' export default { components: { PropsChild }, methods: { handleChildClick() { alert('부모 함수가 자식에 의해 실행됨!') } } } </script> ------------------------------------------------------------------------ <template> <div> <h3>자식 컴포넌트</h3> <button @click="onClickButton">버튼 클릭</button> </div> </template> <script> export default { props: { onClickButton: { type: Function, required: true } } } </script> |
부모 -> 자식 1. 부모 컴포넌트에서 온 데이터 자식컴포넌트에서 수정불가. props 읽기전용 |
||
| 자식-> 부모 emits |
<template> <div> <h2>부모 컴포넌트</h2> <MessageChild @send-message="receiveMessage" /> </div> </template> <script> import MessageChild from './components/MessageChild.vue' export default { components: { MessageChild }, methods: { receiveMessage(payload) { alert('자식이 보낸 메시지: ' + payload) } } } </script> ----------------------------------------------------------------- <template> <div> <h3>자식 컴포넌트</h3> <button @click="emitToParent">메시지 전송</button> </div> </template> <script> export default { emits: ['send-message'], // 이벤트 이름 명시적 선언, 생략가능 methods: { emitToParent() { this.$emit('send-message', '안녕하세요, 부모님!')// 두번째 부터 매개변수 } } } </script> |
부모 <- 자식 1. emit : 자식이 부모에게 결과를 알림, 자식컴포넌트 수정가능 => 권장 2. 인라인 실행가능 <button @click="$emit('send-message', 'Hello inline emit!')"> 인라인 emit 실행 </button> |
|
| 양방향 v-model |
<template> <ChildComp v-model:title="title" v-model:content="content" /> </template> ----------------------------------------------------------------- <template> <div> <label> 제목: <input :value="title" @input="$emit('update:title', $event.target.value)" /> </label> <label> 내용: <textarea :value="content" @input="$emit('update:content', $event.target.value)" ></textarea> </label> </div> </template> <script> props: ['title', 'content'], emits: ['update:title', 'update:content'] </script> |
1. v-model은 다음을 자동화해주는 편의 문법 :value → modelValue (또는 v-model:foo → foo) @input → @update:modelValue 2. v-model 디렉티브 사용 수식어는 온전히 개발자가 직접 구현 - 뷰 디렉티브 참고 |
스타일
| 스타일 | 1. 지역은 style scoped 속성 적용하여 사용. 2. 전역은 src/assets 폴더 하위 별도 css 파일만들고 src/main.js 파일에서 import 하여 적용. |
||
| 1. 외부스타일 | // main.js import './assets/main.css' |
모든 컴포넌트 전역적용 공통, 초기화, reset.css |
|
| 2. SFC 내부 스타일 | <template> <div class="box">Hello</div> </template> <script> export default { name: 'MyComponent' } </script> <style scoped> .box { color: blue; } </style> |
전역 or 지역 (scoped) * <style scoped> 사용 안하면 정의 하지 않은 components 에도 적용됨 = 전역처리 |
|
| 3. 인라인 스타일 | <template> <div> <!-- 1. 기본 객체 형태 --> <p :style="{ color: 'blue', fontSize: '18px' }">기본 객체형</p> <!-- 2. data로 스타일 정의 후 바인딩 --> <p :style="myStyle">data 스타일 바인딩</p> <!-- 3. 여러 스타일 객체 배열로 적용 --> <p :style="[myStyle, yourStyle]">여러 스타일 합치기</p> <!-- 4. 조건부 스타일 적용 --> <p :style="{ backgroundColor: isActive ? 'yellow' : 'white' }">조건부 스타일</p> <!-- 5. 계산된 스타일 (computed) --> <p :style="computedStyle">계산된 스타일</p> </div> </template> <script> export default { data() { return { isActive: true, myStyle: { color: 'red', fontSize: '20px' }, yourStyle: { border: '1px solid gray', padding: '4px' } } }, computed: { computedStyle() { return { color: this.isActive ? 'green' : 'gray', fontWeight: 'bold' } } } } </script> |
해당 요소에만 적용 1. 기본 객체 형태 2. data로 스타일 정의 후 바인딩 3. 여러 스타일 객체 배열로 적용 4. 조건부 스타일 적용 5. 계산된 스타일 (computed) |
|
뷰 컴포넌트 생성 & 등록
| 뷰 컴포넌트 생성 & 등록 | 템플릿 속성 |
컴포넌트의 내부 구조 1. HTML 처럼 생긴 UI 정의 영역 2. Vue 컴파일러가 이 템플릿을 렌더 함수로 변환함 |
|
| import { createApp } from 'vue/dist/vue.esm-bundler.js' const app = createApp({ template: `<div> <h1>{{ message }}</h1> <button @click="count++">Clicked {{ count }} times</button> </div>`, data() { return { message: 'Hello from runtime-compiled template!', count: 0 } } }) app.mount('#app') <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue Runtime Compile</title> </head> <body> <div id="app"></div> <script type="module" src="/main.js"></script> </body> </html> |
( 가능하지만 추천안함 ) - 런타임 환경에서 컴파일은 vue/dist/vue.esm-bundler 패키지 사용 data, methods 도 가능 컴파일과정에서 (최초 주소호출시실행) SFC (Single File Component ex .vue) 형식의 파일은 vite에 의해 사전컴파일 런타임 환경에 제공 template 속성은 런타임 환경에서 컴파일 ![]() |
||
| 랜더 속성 (render) |
import { createApp, h } from 'vue' const MyComponent = { props: ['msg'], render() { return h('div', { class: 'box' }, [ h('h2', `메시지: ${this.msg}`), h('button', { onClick: () => alert('버튼 클릭됨!') }, '클릭') ]) } } const app = createApp({ render() { return h(MyComponent, { msg: '안녕하세요 Vue!' }) } }) app.mount('#app') <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <title>Vue h() 렌더 예제</title> </head> <body> <div id="app"></div> <script type="module" src="/main.js"></script> </body> </html> |
( 가능하지만 추천안함 ) - 템플릿 컴파일러 필요없음(vue) 사용 - h함수는 vue 에서 가져옴 data, methods 도 가능 템플릿은 내부적으로 JS 함수(rander)로 바뀜 이 함수는 Virtual DOM 트리를 생성함 랜더링되는 요소를 만들기 위한 함수h h(태그명, 태그의 속성, 자식요소) ex) h('div') h('div', null, 'hello') = <div>hello</div> h('div', { class: 'red' }, 'hello') = <div class="red">hello</div> |
|
'Web' 카테고리의 다른 글
| [핵심만 골라 배우는 Vue.js by 수코딩] 8. Options API vs Composition API, 컴포저블 패턴 (0) | 2025.08.04 |
|---|---|
| [핵심만 골라 배우는 Vue.js by 수코딩] 7. 컴포넌트 심화- 상속, 동적렌더링 (1) | 2025.08.04 |
| [핵심만 골라 배우는 Vue.js by 수코딩] 5. 라이프사이클 훅 (1) | 2025.08.01 |
| [핵심만 골라 배우는 Vue.js by 수코딩] 4. 반응형API(Reactivity APIs)- Options API vs Composition API (3) | 2025.08.01 |
| [핵심만 골라 배우는 Vue.js by 수코딩] 3. 디렉티브 - 화면출력, 바인딩, 랜더링, 이벤트 (2) | 2025.07.31 |

