ํ๋ก์ ํธ ์ค์น๋ถํฐ create-vue, SFC ๊ตฌ์กฐ, ํ
ํ๋ฆฟ ๋ฌธ๋ฒ, ref ๋ฐ์ํ, v-model, ์ปดํฌ๋ํธ ํต์ ๊น์ง ์ด๋ณด์๋ฅผ ์ํ ๋จ๊ณ๋ณ ํต ๋ ํผ๋ฐ์ค
## 1. ํ๋ก์ ํธ ์์ฑ & ์ค์น (create-vue / Vite)
```bash
# 1. ๊ณต์ ์ถ์ฒ ๋ฐฉ์์ผ๋ก ์ Vue 3 ํ๋ก์ ํธ ์์ฑ (Vite ๊ธฐ๋ฐ)
npm create vue@latest
# ํ๋กฌํํธ ์ ํ ๊ฐ์ด๋:
# โ Project name: โฆ my-vue-app
# โ Add TypeScript? โฆ Yes (๊ถ์ฅ)
# โ Add JSX Support? โฆ No
# โ Add Vue Router for SPA? โฆ Yes (ํ์ด์ง ๋ผ์ฐํ
ํ์ ์)
# โ Add Pinia for state management? โฆ Yes (์ ์ญ ์ํ ๊ด๋ฆฌ ํ์ ์)
# โ Add ESLint & Prettier? โฆ Yes
# 2. ์์กด์ฑ ์ค์น ๋ฐ ๋ก์ปฌ ๊ฐ๋ฐ ์๋ฒ ์คํ
cd my-vue-app
npm install
npm run dev
# ๐ก ์ด๊ฐ๋จ CDN ๋ฐฉ์ (๋ณ๋ ์ค์น ์์ด HTML ํ์ผ ํ๋๋ก ํ
์คํธํ ๋)
# <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
```
## 2. ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ & SFC (.vue) ๊ธฐ๋ณธ ๊ฐ๋
```text
my-vue-app/
โโโ index.html โ ๋ธ๋ผ์ฐ์ ๊ฐ ๊ฐ์ฅ ๋จผ์ ์ฝ๋ HTML ์ํธ๋ฆฌ ํฌ์ธํธ
โโโ src/
โ โโโ main.ts โ Vue ์ฑ ์์ฑ(createApp) ๋ฐ #app์ ๋ง์ดํธ
โ โโโ App.vue โ ์ต์์ ๋ฃจํธ ์ปดํฌ๋ํธ
โ โโโ components/ โ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์์ ์ปดํฌ๋ํธ ํด๋
โ โโโ assets/ โ ์ด๋ฏธ์ง, ์ ์ญ CSS ๋ฑ ์ ์ ๋ฆฌ์์ค
โโโ package.json โ ํ๋ก์ ํธ ์ค์ ๋ฐ ์์กด์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
```
```vue
<!-- SFC (Single File Component): HTML + JS + CSS๋ฅผ ํ๋์ ํ์ผ๋ก ๊ตฌ์ฑ -->
<script setup lang="ts">
// [1] ์คํฌ๋ฆฝํธ ์์ญ: ์๋ฐ์คํฌ๋ฆฝํธ/ํ์
์คํฌ๋ฆฝํธ ๋ก์ง (Composition API)
const message = 'Hello, Vue 3!'
</script>
<template>
<!-- [2] ํ
ํ๋ฆฟ ์์ญ: ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ง HTML ํ๋ฉด -->
<div class="container">
<h1>{{ message }}</h1>
</div>
</template>
<style scoped>
/* [3] ์คํ์ผ ์์ญ: scoped๋ฅผ ๋ถ์ด๋ฉด ์ด ์ปดํฌ๋ํธ ๋ด๋ถ์๋ง CSS ๊ฒฉ๋ฆฌ ์ ์ฉ */
.container { text-align: center; margin-top: 2rem; }
</style>
```
## 3. ํ
ํ๋ฆฟ ๊ธฐ๋ณธ ๋ฌธ๋ฒ: ํ๋ฉด์ ๋ฐ์ดํฐ ๋์ฐ๊ธฐ
```vue
<script setup lang="ts">
const title = 'Vue 3 ๊ธฐ๋ณธ ๋ฌธ๋ฒ'
const imgUrl = 'https://vuejs.org/images/logo.png'
const isVisible = true
const fruits = ['์ฌ๊ณผ', '๋ฐ๋๋', 'ํฌ๋']
const handleClick = () => alert('๋ฒํผ์ด ํด๋ฆญ๋์์ต๋๋ค!')
</script>
<template>
<!-- 1. ํ
์คํธ ๋ณด๊ฐ: {{ ๋ณ์ }} -->
<h1>{{ title }}</h1>
<!-- 2. ์์ฑ ๋ฐ์ธ๋ฉ: v-bind ๋๋ ์ถ์ฝํ ์ฝ๋ก (:) -->
<img :src="imgUrl" alt="๋ก๊ณ " width="40" />
<!-- 3. ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ: v-on ๋๋ ์ถ์ฝํ ๊ณจ๋ฑ
์ด(@) -->
<button @click="handleClick">ํด๋ฆญ ์ด๋ฒคํธ</button>
<!-- 4. ์กฐ๊ฑด๋ถ ๋ ๋๋ง: v-if, v-else (DOM ์์ฒด๋ฅผ ์์ฑ/์ ๊ฑฐ) -->
<p v-if="isVisible">์กฐ๊ฑด์ด ์ฐธ์ผ ๋ ํ์</p>
<p v-else>์กฐ๊ฑด์ด ๊ฑฐ์ง์ผ ๋ ํ์</p>
<!-- 5. ๋ฐ๋ณต ๋ ๋๋ง: v-for (๋ฐ๋์ ๊ณ ์ ํ :key ๋ฐ์ธ๋ฉ) -->
<ul>
<li v-for="(fruit, index) in fruits" :key="index">{{ fruit }}</li>
</ul>
</template>
```
## 4. ๋ฐ์ํ ๋ณ์ ๋ง๋ค๊ธฐ: ref ์ reactive
```vue
<script setup lang="ts">
import { ref, reactive } from 'vue'
// [1] ref: ์ซ์, ๋ฌธ์, ๋ถ๋ฆฌ์ธ, ๋ฐฐ์ด, ๊ฐ์ฒด ๋ชจ๋ ํ์
์ง์ (๊ฐ์ฅ ๋ฒ์ฉ์ !)
// ๐ก JS ๋ด๋ถ์์๋ '.value'๋ก ์ ๊ทผํ๊ณ , <template>์์๋ '.value' ์์ด ์๋ ์ธ๋ํ!
const count = ref(0)
const userName = ref('ํ๊ธธ๋')
const increase = () => {
count.value++ // ์คํฌ๋ฆฝํธ์์๋ .value ํ์
}
// [2] reactive: ์ค์ง '๊ฐ์ฒด/๋ฐฐ์ด' ์ ์ฉ (์์ฑ์ ์ง์ ์ ๊ทผ ๊ฐ๋ฅ)
const user = reactive({
age: 20,
city: '์์ธ'
})
const birthday = () => {
user.age++ // .value ์์ด ์ง์ ์์
}
</script>
<template>
<!-- ํ
ํ๋ฆฟ์์๋ .value ์์ด ๋ณ์๋ช
๊ทธ๋๋ก ์ฌ์ฉ -->
<p>{{ userName }}๋์ ์นด์ดํธ: {{ count }}</p>
<button @click="increase">+1 ์ฆ๊ฐ</button>
<p>๋์ด: {{ user.age }}์ธ (๊ฑฐ์ฃผ์ง: {{ user.city }})</p>
<button @click="birthday">ํ ์ด ๋จน๊ธฐ</button>
</template>
```
## 5. ํผ ์
๋ ฅ & ์๋ฐฉํฅ ๋ฐ์ธ๋ฉ: v-model
```vue
<script setup lang="ts">
import { ref } from 'vue'
const message = ref('')
const isSubscribed = ref(false)
const selectedCategory = ref('tech')
</script>
<template>
<!-- ํ
์คํธ ์ธํ: ์
๋ ฅํ๋ ์ฆ์ message ๋ณ์์ ์ค์๊ฐ ๋ฐ์ -->
<input v-model="message" placeholder="๋ฉ์์ง๋ฅผ ์
๋ ฅํ์ธ์" />
<p>์ค์๊ฐ ์
๋ ฅ๊ฐ: {{ message }}</p>
<!-- ๋จ์ผ ์ฒดํฌ๋ฐ์ค (๋ถ๋ฆฌ์ธ ๋ฐ์ธ๋ฉ) -->
<label>
<input type="checkbox" v-model="isSubscribed" /> ๋ด์ค๋ ํฐ ๊ตฌ๋
</label>
<p>๊ตฌ๋
์ฌ๋ถ: {{ isSubscribed ? '๊ตฌ๋
์ค' : '๋ฏธ๊ตฌ๋
' }}</p>
<!-- ์
๋ ํธ ๋ฐ์ค ๋ฐ์ธ๋ฉ -->
<select v-model="selectedCategory">
<option value="tech">๊ธฐ์ </option>
<option value="life">์ผ์</option>
</select>
<!-- ๐ก ์ ์ฉํ v-model ์์์ด -->
<!-- v-model.trim : ์๋ค ๊ณต๋ฐฑ ์๋ ์ ๊ฑฐ -->
<!-- v-model.number : ์
๋ ฅ๊ฐ์ ์๋์ผ๋ก ์ซ์(Number) ํ์
์ผ๋ก ํ๋ณํ -->
<!-- v-model.lazy : ์
๋ ฅ ์ค(input)์ด ์๋๋ผ ํฌ์ปค์ค๋ฅผ ์์ ๋(change) ๋๊ธฐํ -->
</template>
```
## 6. ๊ณ์ฐ๋ ์์ฑ: computed (๋ฐ์ดํฐ ๊ฐ๊ณต & ์บ์ฑ)
```vue
<script setup lang="ts">
import { ref, computed } from 'vue'
const firstName = ref('๊ธธ๋')
const lastName = ref('ํ')
const items = ref([
{ name: '๋
ธํธ๋ถ', price: 1500000 },
{ name: '๋ง์ฐ์ค', price: 35000 }
])
// ๐ก computed: ์์กดํ๋ ๋ฐ์ํ ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ฌ๊ณ์ฐ๋๊ณ ๊ฒฐ๊ณผ๋ฅผ ์บ์ฑํจ
// (๋จ์ ํจ์ ํธ์ถ๊ณผ ๋ฌ๋ฆฌ ํ
ํ๋ฆฟ ๋ฆฌ๋ ๋๋ง๋ง๋ค ๋ถํ์ํ๊ฒ ์ฌ์คํ๋์ง ์์)
const fullName = computed(() => `${lastName.value} ${firstName.value}`)
const totalPrice = computed(() => {
return items.value.reduce((sum, item) => sum + item.price, 0)
})
</script>
<template>
<p>์ฌ์ฉ์ ์ด๋ฆ: {{ fullName }}</p>
<p>์ด ๊ฒฐ์ ๊ธ์ก: {{ totalPrice.toLocaleString() }}์</p>
</template>
```
## 7. ๋ฐ์ดํฐ ๋ณํ ๊ฐ์ง: watch ์ watchEffect
```vue
<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue'
const searchWord = ref('')
const searchHistory = ref<string[]>([])
// 1. watch: ํน์ ์ํ์ [์ด์ ๊ฐ]๊ณผ [์๋ก์ด๊ฐ]์ ๋น๊ต ๊ฐ์
watch(searchWord, (newVal, oldVal) => {
console.log(`๊ฒ์์ด ๋ณ๊ฒฝ: "${oldVal}" โ "${newVal}"`)
searchHistory.value.push(newVal)
}, { immediate: false }) // immediate: true ์ ๋ง์ดํธ ์ฆ์ 1ํ ์คํ
// 2. watchEffect: ์ฝ๋ฐฑ ์์์ ์ฌ์ฉ๋ ๋ฐ์ํ ๋ณ์๋ฅผ ์๋์ผ๋ก ์ถ์ ํ์ฌ ์ฆ์ ์คํ
watchEffect(() => {
console.log(`ํ์ฌ ๊ฒ์์ด ๊ธฐ์ค ์๋ ๋์: ${searchWord.value}`)
})
</script>
<template>
<input v-model="searchWord" placeholder="๊ฒ์์ด๋ฅผ ์
๋ ฅํ์ธ์" />
</template>
```
## 8. ๋ถ๋ชจ-์์ ์ปดํฌ๋ํธ ํต์ : defineProps & defineEmits
```vue
<!-- ์์ ์ปดํฌ๋ํธ: UserCard.vue -->
<script setup lang="ts">
// 1. defineProps: ๋ถ๋ชจ๋ก๋ถํฐ ์ ๋ฌ๋ฐ๋ ์์ฑ ์ ์ (TypeScript ํ์
์ง์)
const props = withDefaults(defineProps<{
name: string
age?: number // ์ ํ์ prop
}>(), {
age: 20 // ๊ธฐ๋ณธ๊ฐ
})
// 2. defineEmits: ๋ถ๋ชจ์๊ฒ ๋ฐ์์ํฌ ์ด๋ฒคํธ ์ ์
const emit = defineEmits<{
(e: 'greet', message: string): void
}>()
const sendGreeting = () => {
emit('greet', `${props.name}๋์ด ์ธ์ฌํ์ต๋๋ค!`)
}
</script>
<template>
<div class="user-card">
<h3>{{ props.name }} ({{ props.age }}์ธ)</h3>
<button @click="sendGreeting">์ธ์ฌ ๋ณด๋ด๊ธฐ</button>
</div>
</template>
```
```vue
<!-- ๋ถ๋ชจ ์ปดํฌ๋ํธ: App.vue -->
<script setup lang="ts">
import UserCard from './UserCard.vue' // import๋ง ํ๋ฉด ํ
ํ๋ฆฟ์์ ๋ฐ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
const handleGreet = (msg: string) => {
alert(msg)
}
</script>
<template>
<!-- :props๋ช
="๋ฐ์ดํฐ", @emit๋ช
="ํธ๋ค๋ฌํจ์" -->
<UserCard name="์ด์์ " :age="30" @greet="handleGreet" />
</template>
```
## 9. ์ปดํฌ๋ํธ v-model ์ฐ๋: defineModel (Vue 3.4+)
```vue
<!-- ์ปค์คํ
์ธํ ์ปดํฌ๋ํธ: MyInput.vue -->
<script setup lang="ts">
// ๐ก defineModel ํ๋๋ก ์์์์ props์ emit ์ ์ธ ์์ด ๋ถ๋ชจ์ v-model๊ณผ ์ง๊ฒฐ!
const text = defineModel<string>({ default: '' })
</script>
<template>
<input v-model="text" class="custom-input" placeholder="์ฌ๊ธฐ์ ์
๋ ฅ..." />
</template>
```
```vue
<!-- ์ฌ์ฉํ๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ -->
<script setup lang="ts">
import { ref } from 'vue'
import MyInput from './MyInput.vue'
const message = ref('์๋
ํ์ธ์')
</script>
<template>
<!-- ๋ถ๋ชจ์ message ๋ณ์์ ์์ ์ปดํฌ๋ํธ์ input์ด ์๋ฐฉํฅ ๋๊ธฐํ๋จ -->
<MyInput v-model="message" />
<p>๋ถ๋ชจ์ message: {{ message }}</p>
</template>
```
## 10. ์๋ช
์ฃผ๊ธฐ ํ
, DOM ์ฐธ์กฐ & ์ปดํฌ์ ๋ธ (์ค์ ์์ฉ)
```vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted, useTemplateRef } from 'vue'
// 1. ํ
ํ๋ฆฟ DOM ์ง์ ์ ๊ทผ (Vue 3.5+ useTemplateRef)
const myInput = useTemplateRef<HTMLInputElement>('inputRef')
// 2. ์๋ช
์ฃผ๊ธฐ ํ
: onMounted (ํ๋ฉด์ด ๋ธ๋ผ์ฐ์ ์ ๋ง์ดํธ๋ ์งํ 1ํ ์คํ - API ํธ์ถ ์์ )
onMounted(async () => {
console.log('ํ๋ฉด ์ค๋น ์๋ฃ!')
myInput.value?.focus() // ์ธํ์ ์๋ ํฌ์ปค์ค
})
// 3. ์๋ช
์ฃผ๊ธฐ ํ
: onUnmounted (ํ๋ฉด์์ ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ผ์ง ๋ ํ์ด๋จธ/์ด๋ฒคํธ ์ ๋ฆฌ)
onUnmounted(() => {
console.log('์ปดํฌ๋ํธ ์ข
๋ฃ: ๋ฉ๋ชจ๋ฆฌ ์ ๋ฆฌ')
})
</script>
<template>
<input ref="inputRef" placeholder="์๋ ํฌ์ปค์ค ์ธํ" />
</template>
```
```typescript
// ๐ก ์ปดํฌ์ ๋ธ (Composables): ์ฌ์ฌ์ฉ ๋ก์ง์ ํจ์ ํ์ผ๋ก ๊น๋ํ๊ฒ ๋ถ๋ฆฌ
// src/composables/useCounter.ts
import { ref } from 'vue'
export function useCounter(init = 0) {
const count = ref(init)
const inc = () => count.value++
const dec = () => count.value--
return { count, inc, dec }
}
// ์ฌ์ฉ ์ปดํฌ๋ํธ์์:
// const { count, inc } = useCounter(10)
```
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์