- 4.1.1 프로젝트 생성 (CLI)
- 4.2 컴포넌트
- 4.3 기초 템플릿 문법
- 4.3.2 속성 반영
- 4.3.3 클래스 반영**
- 4.3.5 조건 디렉티브 v-if, v-else, v-show
- 4.3.6 반복 디렉티브 v-for
- 4.3.7 이벤트 디렉티브 v-on
- 4.4 반응형 상태 ref, reactive, nextTick
- 컴퓨티드 (+옵셔널 체이닝 ?. )
- 4.6 데이터 바인딩 v-model
- 4.7 라이프사이클 훅
- c
- c
- c
참고로 챕터3은 인텔리제이 다운로드라 넘어갑니다.
4.1.1 프로젝트 생성 (CLI)
매번 IDE 통해서만 프로젝트 GUI로 생성하고 그랬는데.. 책에 CLI로 생성하네.. 넘 멋있습니다...
난 아직도 cmd 창만 보면 마음이 콩닥콩닥하다 ...푸후후..
근데.. cmd는 아니구 .. 더 확장된 powerShell 에서 하는 거라고 함 ... 둘이 무슨 차이인지 잘 모름..
검색해보니 .. 파워쉘이 기능이 더 풍부하다고 한다.. 둘 다 .. CLI 쉘 ..
윈도우 검색창 (단축키 Windows + S) 에서 'Windows PowerShell' 검색 후 실행
# cd 명령어 이용해 프로젝트를 만들고 싶은 디렉토리로 이동
PS C:\Users\차승경> C:/
cmd> npm create vue@3.10.4
# 아래 내용 나오면 y 입력 후 엔터 키 눌러 진행
Need to install the following packages:
create-vue#3.10.4
Ok to proceed? (y)
여기에선 프로젝트 이름을 'vue-project'로 하고
Router 옵션 Yes, Pinia 옵션 Yes로 한다.
# 프로젝트 이름
Project name: ... vue-project
# 타입스크립트 사용 여부
Add TypeScript? ... No
# JSX 지원 여부
Add JSX Support? ... No
# Router 추가 여부
Add Vue Router for Single Page Application develoment? ... Yes
# Pinia(스토어) 추가 여부
Add Pinia for state management? ... Yes
# Vitest(유닛 테스트 도구) 추가 여부
Add Vitest for Unit Testing? ... No
# End-to-End 테스트 솔루션 추가 여부
Add an End-to-End Testing Solution? >> No
# ESLint(자바스크립트 코드 분석 도구) 추가 여부
Add ESLint for code quality? ... No
# 디버깅을 위한 Vue 개발 도구 추가 여부
Add Vue DevTools 7 extension for debugging? (experimental) ... No
# 생성한 프로젝트 폴더로 이동
PS C:\Users\차승경> cd vue-project
# 노드 모듈 설치
PS C:\Users\차승경> npm install
#프로젝트 실행
PS C:\Users\차승경> npm run dev
실행 완료되면 URL (http://localhost:5173/) 에 접속하여 Vue 화면 뜨는지 체크
파워쉘 터미널에서 Ctrl + C 누르고 'y'입력하여 서버 종료 -> 터미널 종료
4.2 컴포넌트
- 컴포넌트 이름은 일반적으로 파스칼케이스(Pascal case)를 따른다.
ex) FrontWheel.vue
- SFC (single file component)에서 template 나 script 중 하나는 반드시 있어야 한다.
만약 스크립트만 있는 경우 setup()함수 안에서 return 으로 JSX또는 h()함수를 통해 가상 DOM을 반환하면 된다.
(직접 렌더링 코드를 작성해줘야 화면이 보임)
[Tire.vue]
<script setup>
const props = defineProps({
color: String
});
</script>
<template>
<div :style="{color: props.color}"> {{props.color || 'BLACK' }} TIRE</div>
</template>
[FrontWheel.vue]
<script setup>
import Tire from "@/components/Tire.vue";
</script>
<template>
<div class="front-wheel>
<span>FRONT WHEEL</span>
<Tire color="BLUE" />
</div>
</template>
이렇게 하면, FrontWheel.vue가 prop으로 넘기는 내용을 Tire.vue가 받아서 사용한다.
4.3 기초 템플릿 문법
기본적으로 {{, }} 를 이용해서 텍스트를 출력한다.
콧수염 닮아서 mustache 머스태시 문법이라고도 함
<srcipt setup>
const message = "There";
</script>
<template>
<div> Hello {{ message }} </div>
</template>
화면에 Hello There 출력
v-text 이용
<srcipt setup>
const message = "There";
</script>
<template>
<div>
<span>Hello </span>
<span v-text="message"></span>
</div>
</template>
디렉티브
: <template> 템플릿 블록에서 HTML 요소에 입력하는 v- 로 시작하는 특수한 속성
: 동적으로 텍스트 출력하거나 클래스 표시, 일부 요소의 출력을 제어하거나 이벤트를 다루는 역할
** 만약 message 값이 객체라면 ?
(자바스크립트에서 {}중괄호로 감싸서 키-값 쌍으로 구성된 값을 만들면. 바로 그것이 객체!!!!!!!!!!!!!!)
console.log(typeof message);
이걸로 객체인지 뭔지 확인 가능
<srcipt setup>
const message = { name: "kim", age: 20 };
</script>
<template>
<div>
<span>Hello </span>
<span v-text="message"></span>
</div>
</template>
이런식이면 Hello [Object Object] 출력
이 객체를 출력하려면 JSON.stringify()를 호출하여 JSON으로 변환해야 한다.
<srcipt setup>
const message = { name: "kim", age: 20 };
</script>
<template>
<div>
<span>Hello </span>
<span v-text="JSON.stringify(message)"></span>
</div>
</template>
Hello {"name":"kim","age":20}
출력
<span v-text="JSON.stringify(message.name)"></span>
message.name 작성해야
Hello kim 출력
4.3.2 속성 반영
binding ...
div태그의 id속성에 변수 값을 반영할 수 있다.
(v-bind 는 생략하고 앞에 : 만 입력해도 된다.)
<srcipt setup>
const page = "history";
</script>
<template>
<div v-bind:id="page">HISTORY PAGE</div>
</template>
이러면 <div> 의 id가 history 가 된다.
<srcipt setup>
let loading = true;
</script>
<template>
<input type="text" placeholder="아이디" :readonly="loading" />
<input type="password" placeholder="패스워드" :readonly="loading" />
<button :disabled="loading">로그인</button>
</template>
이러면 input 태그의 readonly 값은 ture 다.
<input type="text" placeholder="아이디" readonly /> 와 같음
4.3.3 클래스 반영
<srcipt setup>
const parent = "fruits"
const child = "item"
</script>
<template>
<ul>
<li :class="child">사과</li>
<li :class="child">수박</li>
<li :class="child">딸기</li>
<li :class="child">포도</li>
</ul>
</template>
이렇게 하면 <li> 태그는 class = "item" 이 된다.
<srcipt setup>
const fruits = "apple avocado"
const fruitArr = ["banana", "blueberry"]
</script>
<template>
<div :class="fruits">사과 / 아보카도</div>
<div :class="fruitArr">바나나 / 블루베리</div>
<div :title="fruitArr">바나나 / 블루베리</div>
</template>
결과
<div class="apple avocado">사과 / 아보카도</div>
<div class ="banana blueberry">바나나 / 블루베리
클래스는 띄어쓰기로 구분된다.
<div title="banana,blueberry">바나나 / 블루베리</div>
타이틀은 쉼표로 구분된다. (타이틀 : 브라우저에서 마우스 커서를 올렸을 때 뜨는 툴팁 텍스트)
<srcipt setup>
const visible = {fruit: true}
const hidden = {fruit: false}
</script>
<template>
<div :class="visible">사과</div>
<div :class="hidden">오이</div>
<div :class="{fruit: true}">복숭아</div>
</template>
객체로도 지정할 수 있다.
값이 참(Truthy) 일 때 키가 클래스에 반영된다.
결과
<div class="fruit">사과</div>
<div class>오이</div>
<div class="fruit">복숭아</div>
물~론~ 스타일도 반영 가능 ^-^
const rect = "background:red; width:200px"
<div :style="rect"></div>
4.3.5 조건 디렉티브
v-if, v-else
<srcipt setup>
let loading = true;
</script>
<template>
<div v-if="loading">로딩 중</div>
<div v-else>로딩 완료</div>
</template>
결과
<div id="app" data-v-app>
<div>로딩 중</div>
</div>
특수 태그인 템플릿 태그를 활용하면 별도의 태그 없이 텍스트만 출력할 수 있다.
<script setup>
let loading = true;
</script>
<template>
<template v-if="loading">로딩 중</template>
<template v-else>로딩 완료</template>
</template>
결과
<div id="app" data-v-app>로딩 중<div>
**
<div id="app" data-v-app> 는 Vue에서 자동으로 추가되는 특수한 의미의 HTML 속성이다.
템플릿 태그만 쓰면 "로딩 중" 텍스트만 추가된다.
v-show
<script setup>
let loading = true;
</script>
<template>
<div v-show="loading">로딩 중</div>
<div v-show="!loading">로딩 완료</div>
</template>
결과
<div id="app" data-v-app>
<div>로딩 중</div>
<div style="display: none;">로딩 완료</div>
</div>
v-show 는 display 값으로 출력 여부를 결정한다.
따라서 특수 태그 template을 사용하면 속성의 값이 참true 더라도 화면에 출력되지 않는다.
<template>
<template v-show="true">로딩 중</template>
</template>
출력되지 않음
<div v-show="true">로딩 중</div>
이건 출력된다.
4.3.6 반복 디렉티브
v-for
<script setup>
const fruits = ["apple", "banana", "pear"];
</script>
<template>
<div v-for="f in fruits">{{ f }}</div>
</template>
배열의 키(인덱스)를 받아올 수도 있다.
<script setup>
const fruits = ["apple", "banana", "pear"];
</script>
<template>
<div v-for="(f, idx) in fruits">{{ idx }} {{ f }}</div>
</template>
결과
<div id="app" data-v-app>
<div>0 apple</div>
<div>1 banana</div>
<div>2 pear</div>
</div>
v-for 도 template 태그와 함께 쓸 수 있다.
<script setup>
const fruits = ["apple", "banana", "pear"];
</script>
<template>
<tempalte v-for="f in fruits">{{ f }}</tempalte>
</template>
결과
<div id="app" data-v-app>
"apple"
"banana"
"pear"
</div>
<script setup>
const members = [
{id: 1, name: "kim"},
{id: 2, name: "lee"},
... // 많은 데이터
];
</script>
<template>
<ul>
<li v-for="m in members" :key="m.id">{{ m.name }}</li>
</ul>
</template>
members 배열을 기준으로 v-for 루프가 돌아가고 각 객체의 name이 출력된다.
결과
<ul>
<li>kim</li>
<li>lee</li>
...
</ul>
4.3.7 이벤트 디렉티브 v-on
<srcipt setup>
const pop = (message) => {
window.alert(message);
}
</script>
<template>
<button v-on:click="pop('Hello')">Hello</button>
</template>
v-on: 은 @로 대체할 수 있다.
메서드의 인수가 없다면 메서드 이름만 입력해도 호출된다.
<srcipt setup>
const run = () => {
window.alert("Run");
}
</script>
<template>
<button v-on:click="run">Run</button>
</template>
@submit :요소가 제출될 때까지 동작
@mousedown : 요소에 대해 마우스 버튼을 눌렀을 때 동작
@focus : 요소에 포커스됐을 때 동작
@blur : 요소에 포커스돼 있다가 풀렸을(blur) 때 동작
@keyup : 요소에서 키보드를 눌렀다가 떼면 동작
4.4 반응형 상태
<script setup>
let pushUp = 0;
let sitUp = 0;
const incrementPushUp = () => {
pushUp++;
console.log(`${pushUp} pushUp`);
}
const incrementSitUp = () => {
sitUp++;
console.log('sitUp', sitUp);
}
</script>
<template>
<div>
<h1>체력 검사</h1>
<hr/>
<ul>
<li id="pushUp">팔굽혀펴기: {{pushUp}}</li>
<li id="sitUp">윗몸일으키기: {{sitUp}}</li>
</ul>
<hr />
<button @click="incrementPushUp()">팔굽혀펴기 증가</button>
<button @click="incrementSitUp()">윗몸일으키기 증가</button>
</div>
</template>

console 보면 pushUp `` 백틱으로 문자열 표시한 건 그냥 문자열이고
sitUp처럼 객체를 출력한 건 글자 색이 다르다
sitUp은 Vue객체라서 sitUp RefImpl { value: 5 }
- ref
그리고 변수를 ref로 선언해야 반응형이 된다. 지금은 로그만 찍힘

<script setup>
import { ref } from "vue";
let pushUp = ref(0);
let sitUp = ref(0);
const incrementPushUp = () => {
pushUp.value++;
console.log(`pushUp ${pushUp.value}`);
}
const incrementSitUp = () => {
sitUp.value++;
console.log('sitUp', sitUp.value);
}
</script>
ref 를 import 해주고 변수를 ref 메서드로 선언하고
ref 메서드를 스크립트 블록에서 사용할 때는 .value 사용해야 한다.

- reactive
또는 reactive 를 써야 한다.
** ref 와 다르게 .value를 쓰지 않음
<script setup>
import { reactive } from "vue";
const state= reactive({
pushUp: 0,
sitUp: 0,
});
const incrementPushUp = () => {
state.pushUp++;
console.log(`pushUp: ${state.pushUp}`);
}
const incrementSitUp = () => {
state.sitUp++;
console.log(`sitUp: ${state.sitUp}`);
}
</script>
<template>
// 생략
<li id="pushUp">팔 굽혀 펴기 : {{ state.pushUp }}</li>
...
<button @click="increasePushUp()">팔 굽혀 펴기 증가</button>
...
</template>
ref 메서드는 문자열, 숫자, 불리언 같은 원시형 데이터를 반응형으로 만들 때 사용한다. (배열, 객체도 가능)
reactive 메서드는 주로 객체를 반응형으로 만들 때 사용한다.
- nextTick
const increasePushUp = () => {
state.pushUp++;
console.log(document.getElementById("pushUp").innerText);
}
이렇게 하고 log찍어보면

이런식으로 버튼 클릭하면 UI에 반영되기 전에 콘솔이 먼저 출력되어서 화면과 로그가 다르게 출력되는 것을 볼 수 있다.
이럴 때 쓰는 게 nextTick
const increasePushUp = () => {
state.pushUp++;
nextTick(() => {
console.log(document.getElementById("pushUp").innerText);
})
}

4.5 컴퓨티드
<script setup>
import {computed, reactive} from "vue";
const state = reactive({
mvpId:7,
players: [
{id:7, name: "John"},
{id:9, name: "Jane"},
{id:11, name: "James"}
]
});
</script>
<template>
<div>
<template v-if="state.mvpId">
{{state.players.find(m => m.id === state.mvpId)?.name || '없음'}}
</template>
<template v-else>없어요</template>
</div>
</template>
1. state.mvpId state.mvpId 가 있을 때만(truthy일 때만) <template> 안의 내용을 보여준다.
2. mvpId가 null, undefined, 0, false 면 아무것도 안 보여줌
3. state.players 배열 안에서 id가 mvpId와 같은 플레이어 객체 하나를 찾음
4. ?. 은 옵셔널 체이닝으로 find()의 결과가 없을 수도 있을 때 안전하게 접근하는 방식
ex) const result = state.players.find(...)
result?.name // result 가 null이면 에러 안 나고 undifined 반환
5. 여기서 m은 state.players 배열 안에 있는 각 플레이어 객체를 가리키는 임시 변수
players: [
{id:7},
{id:9, name: "Jane"},
{id:11, name: "James"}
]
만약 이런식으로 state.mvpId와 같은 값인 id:7 에 name이 없으면 || '없음' 의 없음 출력

const state = reactive({
mvpId:'',
또는
mvpId:null,
만약 state.mvpId 자체가 없거나 null, 빈문자열이면 v-else에 해당하는 값이 출력된다.

옵셔널 체이닝을 쓰지 않으려면 v-if 한 번 더 써주는 방법도 있다.
<template>
<template v-if="state.mvpId">
<template v-if="state.players.find(m => m.id === state.mvpId)">
{{state.players.find(p => p.id === state.mvpId).name }}
</template>
</template>
<template v-else>없어요</template>
</template>
computed로 깔끔하게 template 작성 가능
<script setup>
// 생략 ...
const computedMvpName = computed(() => {
if (state.mvpId) {
const player = state.players.find(p => p.id === state.mvpId);
if(player) {
return player.name;
}
}
return '없음';
})
</script>
<template>
{{ computedMvpName }}
</template>
메서드 활용 깔끔하게 template 작성 가능
const state = reactive({
mvpId: 7,
players: [
{id: 7, name: "John"},
{id: 9, name: "Jane"},
{id: 11, name: "James"}
]
});
const getMvpName = () => {
if (state.mvpId) {
const player = state.players.find(m => m.id === state.mvpId);
if(player) {
return player.name;
}
}
return '없음';
}
</script>
<template>
<div>{{getMvpName()}}</div>
</template>
메서드는 호출될 때마다 값을 계산하고
컴퓨티드는 내부 데이터가 변경될 때만 다시 계산하기 때문에 컴퓨티드가 효율적이다.
그런데~!
computedMvpName 을 console 찍어보면 객체다.
** 그리고 만약 컴퓨티드객체가 콘솔 안찍히면 그건 값 변경이 안되어서 컴퓨티드 실행이 안되기 때문이다^^

그래서 컴퓨티드를 스크립트 내에서 쓰려면 .value써줘야 함

4.6 데이터 바인딩
binding 은 묶는다! 라는 의미
스크립트 영역의 데이터와 템플릿 영역의 UI 데이터를 묶는다 !!
단방향 바인딩, 양방향 바인딩이 있다.
<input type="text" :value="state.player" />
브라우저에서 input 통해 값을 변경해도 state.player 값에 반영되지 않는다. = 단방향 바인딩
v-model
<script setup>
import {reactive} from "vue";
const state = reactive({
player: "Kim";
});
</script>
<template>
<div>
<h1>{{ state.player }}</h1>
<input type="text" v-model="state.player" />
</div>
</template>
웹브라우저에서 사용자가 input 요소의 값을 변경하면 state.player의 값에 반영된다. = 양방향 바인딩
* 로그인 등 사용자 입력 값이 필요할 때 사용된다.
<script setup>
import { reactive } from 'vue';
const state = reactive ({
id: "",
pw:""
})
const login = () => {
// 생략
}
</script>
<template>
<input type="text" v-model="state.id" placeholder="아이디">
<input type="password" v-model="state.pw" placeholder="비밀번호">
<button @click="login">로그인</button>
</template>
4.7 라이프사이클 훅
optionsAPI 에서 beforeCreate, created라는 이름으로 별도의 생성 훅 메서드를 제공했지만compositionAPI 에서는 별도의 생성 훅 메서드를 제공하지 않고 기본적으로 setup 훅에 포함되게 했다.
만약에
<script setup>
console.log(document.getElementById("message"));
</script>
<template>
<div id="message">Aloha</div>
</template>

아직 컴포넌트가 마운트되지 않았기 때문에 null 이 나온다.
mount : 컴포넌트를 DOM에 연결하고 화면에 출력하는 과정
마운트 훅 : 마운트 완료 후 실행되는 훅으로 compositionAPI에서 기본 지원
onMounted(() => {
console.log(document.getElementById("message"));
})

4.7.3 업데이트 훅
업데이트 혹은 반응형 상태의 데이터가 바뀌고 화면(DOM)이 업데이트되면 실행되는 훅


<script setup>
import {onUpdated, reactive} from "vue";
const state = reactive({
message: "Hello!"
})
onUpdated(()=> {
console.log(document.getElementById("btn").innerText);
});
</script>
<template>
<button id="btn" @click="state.message='Aloha!'">{{ state.message }}</button>
</template>
버튼 클릭하면 실행된다.
4.7.4 마운트 해제 훅
가
가
가
'웹 개발 > Vue' 카테고리의 다른 글
| [Vue3] Daum Post API 다음 주소를 Vue에서 사용하기 (주소) (0) | 2025.12.24 |
|---|---|
| [Vue3] emit, props (0) | 2025.09.29 |
| [Vue3] optionsAPI / compositionAPI(script setup) 비교 예제 (0) | 2025.08.07 |
| [Vue3][wikibook] 1. Vue 소개 (3) | 2025.08.06 |
| [Vuetify] ml- , mb- (0) | 2025.06.16 |