웹 개발/Vue

[Vue3][wikibook] 4. Vue3 시작을 위한 기초 학습

cha430 2025. 8. 7. 20:17

 

 

참고로 챕터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)이 업데이트되면 실행되는 훅

 

.innerText 했기 때문에 텍스트가 로그에 찍혀요

<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 마운트 해제 훅