App.vue
<template>
<h1>{{ count }}</h1>
</template>
<script>
export default {
data() {
return {
count: 2
}
},
// 컴포넌트 생성 전
beforeCreate() {
console.log(this.count) // undefined
},
// 컴포넌트 생성 후
created() {
console.log(this.count) // 2
},
// 렌더링 전
beforeMount() {
console.log(document.querySelector('h1')) // null
},
// 렌더링 후
mounted() {
console.log(document.querySelector('h1')) // <h2>2</h2>
}
}
</script>
v-once
: 일회성 보간{{ }}
: 일반 텍스트로 해석v-html
: 실제 HTML로 해석v-bind
: HTML 속성 사용 v-bind
약어 : :
v-on
약어 : @
예1)
App.vue
<template>
<h1
v-once
@click='add'>
{{ msg }}
</h1>
<h1 v-html="msg"></h1>
</template>
<script>
export default {
data() {
return {
msg: '<div style="color: red;";>Hello!!</div>'
}
},
methods: {
add() {
this.msg += '!'
}
}
}
</script>
예2)
App.vue
<template>
<h1 :[attr]="'active'"
@[event]="add">
{{ msg }}
</h1>
</template>
<script>
export default {
data() {
return {
msg: 'active',
attr: 'class',
event: 'click'
}
},
methods: {
add() {
this.msg += '!'
}
}
}
</script>
<style scoped>
.active {
color: royalblue;
font-size: 100px;
}
</style>
App.vue
<template>
<Fruits />
</template>
<script>
import Fruits from '~/components/Fruits'
export default {
components: {
Fruits
}
}
</script>
Fruits.vue
<template>
<section v-if="hasFruit">
<h1>Fruits</h1>
<ul>
<li
v-for="fruit in fruits"
:key="fruit">
{{ fruit }}
</li>
</ul>
</section>
<section>
<h1>Reverse Fruits</h1>
<ul>
<li
v-for="fruit in reverseFruits"
:key="fruit">
{{ fruit }}
</li>
</ul>
</section>
</template>
<script>
export default {
data() {
return {
fruits: [
'Apple', 'Banana', 'Cherry'
]
}
},
computed: {
hasFruit() {
return this.fruits.length > 0
},
reverseFruits() {
return this.fruits.map(fruit => {
// 'Apple' => ['A','p','p','l','e']
// => ['e','l','p','p','A'] => 'elppA'
return fruit.split('').reverse().join('')
})
}
}
}
</script>
App.vue
<template>
<h1>{{ reversedMessage }}</h1>
<h1>{{ reversedMessage }}</h1>
<h1>{{ reversedMessage }}</h1>
<h1>{{ reversedMessage }}</h1>
</template>
<script>
export default {
data() {
return {
msg: 'Hello Computed!'
}
},
computed: {
reversedMessage() {
return this.msg.split('').reverse().join('')
}
}
}
</script>
App.vue
<template>
<button @click="add">
ADD
</button>
<h1>{{ reversedMessage }}</h1>
<h1>{{ reversedMessage }}</h1>
<h1>{{ reversedMessage }}</h1>
<h1>{{ reversedMessage }}</h1>
</template>
<script>
export default {
data() {
return {
// Getter, Setter
msg: 'Hello Computed!'
}
},
computed: {
// Getter
// reversedMessage() {
// return this.msg.split('').reverse().join('')
// }
// Getter, Setter
reversedMessage: {
get() {
return this.msg.split('').reverse().join('')
},
set(value) {
this.msg = value // !detupmoC olleH
}
}
},
methods: {
add() {
this.reversedMessage += '!?'
}
}
}
</script>
App.vue
<template>
<h1 @click="changeMessage">
{{ msg }}
</h1>
<h1>{{ reversedMessage }}</h1>
</template>
<script>
export default {
data() {
return {
msg: 'Hello?'
}
},
computed: {
reversedMessage() {
return this.msg.split('').reverse().join('')
}
},
watch: {
msg() {
console.log('msg:', this.msg) // msg: Good!
},
reversedMessage() {
console.log('reversedMessage:', this.reversedMessage) // reversedMessage: !dooG
}
},
methods: {
changeMessage() {
this.msg = 'Good!'
}
}
}
</script>
클래스 바인딩 )
App.vue
<template>
<h1
:class="{active: isActive}"
@click="activate">
Hello?!({{ isActive }})
</h1>
</template>
<script>
export default {
data() {
return {
isActive: false
}
},
methods: {
activate() {
this.isActive = true
}
}
}
</script>
<style scoped>
.active {
color: red;
font-weight: bold;
}
</style>
스타일 바인딩 )
App.vue
<template>
<h1
:style="[fontStyle, backgroundStyle]"
@click="changeStyle">
Hello?!
</h1>
</template>
<script>
export default {
data() {
return {
fontStyle: {
color: 'orange',
fontSize: '30px'
},
backgroundStyle: {
backgroundColor: 'black'
}
}
},
methods: {
changeStyle() {
this.fontStyle.color = 'red'
this.fontStyle.fontSize = '50px'
}
}
}
</script>
예1)
App.vue
<template>
<button @click="handler">
Click me!
</button>
<h1 v-if="isShow">
Hello?!
</h1>
<h1 v-else-if="count > 3">
Count > 3
</h1>
<h1 v-else>
Good~
</h1>
</template>
<script>
export default {
data() {
return {
isShow: true,
count: 0
}
},
methods: {
handler() {
this.isShow = !this.isShow
this.count += 1
}
}
}
</script>
<template>
:<template>
에는 v-if
를 쓰면 안된다.예2)
<template>
<button @click="handler">
Click me!
</button>
<template v-if="isShow">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
</template>
<script>
export default {
data() {
return {
isShow: true,
}
},
methods: {
handler() {
this.isShow = !this.isShow
}
}
}
</script>
예3)
v-if
: 조건이 false면 렌더링이 안된다.v-show
: <template>
엘리먼트를 지원하지 않는다.v-else
와 함께 쓸 수 없다.<template>
<button @click="handler">
Click me!
</button>
<h1 v-show="isShow">
Hello?!
</h1>
</template>
<script>
export default {
data() {
return {
isShow: true,
}
},
methods: {
handler() {
this.isShow = !this.isShow
}
}
}
</script>
예1)
App.vue
<template>
<ul>
<li
v-for="(f, i) in fruits"
:key='f'>
{{ f }}-{{ i + 1 }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
fruits: ['Apple', 'Banana', 'Cherry']
}
},
computed: {
newFruits() {
}
}
}
</script>
예2)
App.vue
<template>
<ul>
<li
v-for="fruit in newFruits"
:key="fruit.id">
{{ fruit.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
fruits: ['Apple', 'Banana', 'Cherry']
}
},
computed: {
newFruits() {
return this.fruits.map((fruit, index) => {
return {
id: index,
name: fruit
}
})
}
}
}
</script>
예3) $ npm i -D shortid
App.vue
<template>
<ul>
<li
v-for="fruit in newFruits"
:key="fruit.id">
{{ fruit.name }}-{{ fruit.id }}
</li>
</ul>
</template>
<script>
import shortid from 'shortid'
export default {
data() {
return {
fruits: ['Apple', 'Banana', 'Cherry']
}
},
computed: {
newFruits() {
return this.fruits.map(fruit => ({
id: shortid.generate(),
name: fruit
}))
}
}
}
</script>
예4) 객체구조분해로 간소화
App.vue
<template>
<ul>
<li
v-for="{ id, name } in newFruits"
:key="id">
{{ name }}-{{ id }}
</li>
</ul>
</template>
<script>
import shortid from 'shortid'
export default {
data() {
return {
fruits: ['Apple', 'Banana', 'Cherry']
}
},
computed: {
newFruits() {
return this.fruits.map(fruit => ({
id: shortid.generate(),
name: fruit
}))
}
}
}
</script>
예5) 반응성
App.vue
<template>
<button @click="handler">
Click me!
</button>
<ul>
<li
v-for="{ id, name } in newFruits"
:key="id">
{{ name }}-{{ id }}
</li>
</ul>
</template>
<script>
import shortid from 'shortid'
export default {
data() {
return {
fruits: ['Apple', 'Banana', 'Cherry']
}
},
computed: {
newFruits() {
return this.fruits.map(fruit => ({
id: shortid.generate(),
name: fruit
}))
}
},
methods: {
handler() {
this.fruits.push('Orange')
}
}
}
</script>
예1) 메소드에 인수가 없는 경우
App.vue
<template>
<button @click="handler">
Click me!
</button>
</template>
<script>
export default {
methods: {
handler(event) {
console.log(event) // PointerEvent { ... }
console.log(event.target) // <button> Click me! </button>
console.log(event.target.textContent) // Click me!
}
}
}
</script>
예2) 메소드에 인수가 있는 경우
App.vue
<template>
<button @click="handler('hi', $event)">
Click 1
</button>
<button @click="handler('what', $event)">
Click 2
</button>
</template>
<script>
export default {
methods: {
handler(msg, event) {
console.log(msg) // Click 1 => hi, Click 2 => what
console.log(event) // PointerEvent { ... }
}
}
}
</script>
예3) 메소드가 2개이상일 경우
App.vue
<template>
<button @click="handlerA(), handlerB()">
Click me!
</button>
</template>
<script>
export default {
methods: {
handlerA() {
console.log('A')
},
handlerB() {
console.log('B')
}
}
}
</script>
.prevent
: 기본기능 동작 방지<template>
<a
href="https://naver.com"
target="_blank"
@click.prevent="handler">
NAVER
</a>
</template>
<script>
export default {
methods: {
handler() {
console.log('ABC!')
}
}
}
</script>
.once
: 메소드 기능을 한번만 동작시킴<template>
<a
href="https://naver.com"
target="_blank"
@click.once="handler">
NAVER
</a>
</template>
<script>
export default {
methods: {
handler() {
console.log('ABC!')
}
}
}
</script>
<template>
<a
href="https://naver.com"
target="_blank"
@click.prevent.once="handler">
NAVER
</a>
</template>
<script>
export default {
methods: {
handler() {
console.log('ABC!')
}
}
}
</script>
.stop
: 이벤트 버블링(자 > 부 순서) 방지<template>
<div
class="parent"
@click="handlerA">
<div
class="child"
@click.stop="handlerB"></div>
</div>
</template>
<script>
export default {
methods: {
handlerA() {
console.log('A')
},
handlerB() {
console.log('B')
}
}
}
</script>
<style scoped lang="scss">
.parent {
width: 200px;
height: 100px;
background-color: royalblue;
margin: 10px;
padding: 10px;
.child {
width: 100px;
height: 100px;
background-color: orange;
}
}
</style>
.capture
: 이벤트 버블링의 반대 개념(부 > 자 순서)<template>
<div
class="parent"
@click.capture="handlerA">
<div
class="child"
@click="handlerB"></div>
</div>
</template>
<script>
export default {
methods: {
handlerA() {
console.log('A')
},
handlerB() {
console.log('B')
}
}
}
</script>
<style scoped lang="scss">
.parent {
width: 200px;
height: 100px;
background-color: royalblue;
margin: 10px;
padding: 10px;
.child {
width: 100px;
height: 100px;
background-color: orange;
}
}
</style>
.self
: 선택한 부분(target)과 선택한 부분과 직접 연결된 메소드 부분(currentTarget)이 같을때만 동작App.vue
<template>
<div
class="parent"
@click.self="handlerA">
<div
class="child"></div>
</div>
</template>
<script>
export default {
methods: {
handlerA() {
console.log('A')
},
handlerB() {
console.log('B')
}
}
}
</script>
<style scoped lang="scss">
.parent {
width: 200px;
height: 100px;
background-color: royalblue;
margin: 10px;
padding: 10px;
.child {
width: 100px;
height: 100px;
background-color: orange;
}
}
</style>
.passive
: 화면처리와 로직처리를 분리시킴(5배이상 속도가 빨라질 수 있다.)App.vue
<template>
<div
class="parent"
@wheel.passive="handler">
<div
class="child"></div>
</div>
</template>
<script>
export default {
methods: {
handler(event) {
for (let i = 0; i < 10000; i += 1) {
console.log(event)
}
},
}
}
</script>
<style scoped lang="scss">
.parent {
width: 200px;
height: 100px;
background-color: royalblue;
margin: 10px;
padding: 10px;
overflow: auto;
.child {
width: 100px;
height: 2000px;
background-color: orange;
}
}
</style>
@keydown.키값
(케밥케이스)App.vue
<template>
<input
type="text"
@keydown.ctrl.shift.enter="handler" />
</template>
<script>
export default {
methods: {
handler() {
console.log('Eter!!')
}
}
}
</script>
예1) 양방향 바인딩
App.vue
<template>
<h1>{{ msg }}</h1>
<input
type="text"
:value="msg"
@input="handler"/>
</template>
<script>
export default {
data() {
return {
msg: 'Hello world!'
}
},
methods: {
handler(event) {
console.log(event.target.value)
this.msg = event.target.value
}
}
}
</script>
예2) 예1) 간소화
App.vue
<template>
<h1>{{ msg }}</h1>
<input
type="text"
:value="msg"
@input="msg = $event.target.value"/>
</template>
<script>
export default {
data() {
return {
msg: 'Hello world!'
}
}
}
</script>
예3) 한글은 한박자 늦게 바인딩되기 때문에 예2)로 쓰는것이 좋다.
App.vue
<template>
<h1>{{ msg }}</h1>
<input
type="text"
:value="msg"
v-model="msg"/>
<h1>{{ checked }}</h1>
<input
type="checkbox"
v-model="checked" />
</template>
<script>
export default {
data() {
return {
msg: 'Hello world!',
checked: false
}
}
}
</script>
예1) 데이터 변경이 다 끝나고 동작 시킴
App.vue
<template>
<h1>{{ msg }}</h1>
<input
type="text"
:value="msg"
v-model.lazy="msg"/>
</template>
<script>
export default {
data() {
return {
msg: 'Hello world!',
}
},
}
</script>
예2) 양방향 바인딩 데이터 숫자로 유지
App.vue
<template>
<h1>{{ msg }}</h1>
<input
type="text"
v-model.number="msg"/>
</template>
<script>
export default {
data() {
return {
msg: 123,
}
},
watch: {
msg() {
console.log(typeof this.msg)
}
}
}
</script>
예3) 띄어쓰기 무시
App.vue
<template>
<h1>{{ msg }}</h1>
<input
type="text"
v-model.trim="msg"/>
</template>
<script>
export default {
data() {
return {
msg: 'Hello world!',
}
},
watch: {
msg() {
console.log(this.msg)
}
}
}
</script>
App.vue
<template>
<MyBtn>Banana</MyBtn>
<MyBtn :color="color">
<span style="color: red;">Banana</span>
</MyBtn>
<MyBtn
large
color="royalblue">
Apple
</MyBtn>
<MyBtn>Cherry</MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
},
data() {
return {
color: '#000'
}
}
}
</script>
MyBtn.vue
<template>
<div
:class="{ large }"
:style="{ backgroundColor: color }"
class="btn">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
color: {
type: String,
default: 'gray'
},
large: {
type: Boolean,
default: false
},
}
}
</script>
<style scoped lang="scss">
.btn {
display: inline-block;
margin: 4px;
padding: 6px 12px;
border-radius: 4px;
background-color: gray;
color: white;
cursor: pointer;
&.large {
font-size: 20px;
padding: 10px 20px;
}
}
</style>
예1) 요소가 두 개 이상일때 상속하기
App.vue
<template>
<MyBtn
class="heropy"
style="color: red;"
title="Hello world!">
Banana
</MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
},
}
</script>
MyBtn.vue
<template>
<div class="btn">
<slot></slot>
</div>
<div v-bind="$attrs"></div>
</template>
<script>
export default {
inheritAttrs: false,
created() {
console.log(this.$attrs)
}
}
</script>
<style scoped lang="scss">
.btn {
display: inline-block;
margin: 4px;
padding: 6px 12px;
border-radius: 4px;
background-color: gray;
color: white;
cursor: pointer;
}
</style>
App.vue
<template>
<MyBtn
@heropy="log"
@change-msg="logMsg">
Banana
</MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
},
methods: {
log(event) {
console.log('Click!!')
console.log(event)
},
logMsg(msg) {
console.log(msg)
}
}
}
</script>
MyBtn.vue
<template>
<div class="btn">
<slot></slot>
</div>
<h1 @dblclick="$emit('heropy', $event)">
ABC
</h1>
<input
type="text"
v-model="msg" />
</template>
<script>
export default {
emits: [
'heropy',
'changeMsg'
],
data() {
return {
msg: ''
}
},
watch: {
msg() {
this.$emit('changeMsg', this.msg)
}
}
}
</script>
<style scoped lang="scss">
.btn {
display: inline-block;
margin: 4px;
padding: 6px 12px;
border-radius: 4px;
background-color: gray;
color: white;
cursor: pointer;
}
</style>
App.vue
<template>
<MyBtn>
<template #icon>
<span>(B)</span>
</template>
<template #text>
<span>Banana</span>
</template>
</MyBtn>
<MyBtn></MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
},
}
</script>
MyBtn.vue
<template>
<div class="btn">
<slot name="icon"></slot>
<slot name="text">Apple</slot>
</div>
</template>
<style scoped lang="scss">
.btn {
display: inline-block;
margin: 4px;
padding: 6px 12px;
border-radius: 4px;
background-color: gray;
color: white;
cursor: pointer;
}
</style>
App.vue
<template>
<Parent />
</template>
<script>
import Parent from '~/components/Parent'
export default {
components: {
Parent
},
data() {
return {
message: 'Hello world!'
}
},
provide() {
return {
msg: this.message
}
}
}
</script>
Parent.vue
<template>
<Child />
</template>
<script>
import Child from '~/components/Child'
export default {
components: {
Child
}
}
</script>
Child.vue
<template>
<div>
Child: {{ msg }}
</div>
</template>
<script>
export default {
inject: ['msg']
}
</script>
반응성을 적용해 주는 예)
App.vue
<template>
<button @click="message = 'Good?'">
Click!
</button>
<h1>App: {{ message }}</h1>
<Parent />
</template>
<script>
import Parent from '~/components/Parent'
import { computed } from 'vue'
export default {
components: {
Parent
},
data() {
return {
message: 'Hello world!'
}
},
provide() {
return {
msg: computed(() => this.message)
}
}
}
</script>
Parent.vue
<template>
<Child />
</template>
<script>
import Child from '~/components/Child'
export default {
components: {
Child
}
}
</script>
Child.vue
<template>
<div>
Child: {{ msg.value }}
</div>
</template>
<script>
export default {
inject: ['msg']
}
</script>
예1)
App/vue
<template>
<h1 ref="hello">
Hello world!
</h1>
</template>
<script>
export default {
created() {
console.log(this.$refs.hello) // undefined
},
mounted() {
console.log(this.$refs.hello) // <h1> Hello world! </h1>
}
}
</script>
예2) 컴포넌트를 연결할때
App.vue
<template>
<Hello ref="hello"/>
</template>
<script>
import Hello from '~/components/Hello'
export default {
components: {
Hello
},
mounted() {
console.log(this.$refs.hello.$el) // <h1> Hello~ </h1>
}
}
</script>
Hello.vue
<template>
<h1>Hello~</h1>
</template>
예3) 컴포넌트의 최상위 요소가 2개 이상일때
App.vue
<template>
<Hello ref="hello"/>
</template>
<script>
import Hello from '~/components/Hello'
export default {
components: {
Hello
},
mounted() {
console.log(this.$refs.hello.$refs.good) // <h1> Good? </h1>
}
}
</script>
Hello.vue
<template>
<h1>Hello~</h1>
<h1 ref="good">
Good?
</h1>
</template>
App.vue
<template>
<div @click="increase">
{{ count }}
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let count = ref(0)
function increase() {
count.value += 1
}
return {
count,
increase
}
}
}
</script>
Option API | setup 내부의 훅 |
---|---|
created | 필요하지 않음* |
mounted | onMounted |
App.vue
<template>
<h1 @click="increase">
{{ count }} / {{ doubleCount }}
</h1>
<h1 @click="changeMessage">
{{ message }} / {{ reversedMessage }}
</h1>
</template>
<script>
import { ref, computed, watch, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const doubleCount = computed(() => {
return count.value * 2
})
function increase() {
count.value += 1
}
const message = ref('Hello world!')
const reversedMessage = computed(() => {
return message.value.split('').reverse().join('')
})
watch(message, newValue => {
console.log(newValue)
})
function changeMessage() {
message.value = 'Good?!'
}
console.log(message.value)
onMounted(() => {
console.log(count.value)
})
return {
count,
doubleCount,
increase,
message,
changeMessage,
reversedMessage
}
}
}
</script>
App.vue
<template>
<MyBtn
class="heropy"
style="color: red;"
color="#ff0000"
@hello='log'>
Apple
</MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
},
methods: {
log() {
console.log('Hello world!')
}
}
}
</script>
MyBtn.vue
<template>
<div
v-bind="$attrs"
class="btn"
@click="hello">
<slot></slot>
</div>
</template>
<script>
import { onMounted } from 'vue'
export default {
inheritAttrs: false,
props: {
color: {
type: String,
default: 'gray'
}
},
emits: ['hello'],
setup(props, context) {
function hello() {
context.emit('hello')
}
onMounted(() => {
console.log(props.color)
console.log(context.attrs)
})
return {
hello
}
}
}
</script>