컴포넌트: 기초
Vue.js: 컴포넌트 기초
-
컴포넌트 정의하기
빌드 방식을 사용할 때 일반적으로 싱글파일컴포넌트(SFC)라고 하는 .vue
확장자를 사용하는 전용 파일에 각 Vue 컴포넌트를 정의한다.
빌드 방식을 사용하지 않을 때, Vue 컴포넌트는 Vue 관련 옵션을 포함하는 일반 자바스크립트 객체로 정의할 수 있다.
-
컴포넌트 사용하기
<template>
<MyBtn />
<MyBtn />
<MyBtn />
<MyBtn />
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
}
}
</script>
가져온 컴포넌트를 템플릿에 노출하려면 components
옵션을 사용하여 등록해야 한다. 그러면 컴포넌트는 등록된 키를 사용하여 태그로 사용할 수 있다.
컴포넌트를 전역으로 등록하면, import 없이 지정된 앱의 모든 곳에서 컴포넌트를 사용할 수 있다.
컴포넌트는 원하는 만큼 재사용할 수 있다.
SFC에서는 HTML 요소와 구별하기 위해 자식 컴포넌트에 PascalCase 태그 이름을 사용하는 것이 좋다. 또한 />
를 사용하여 태그를 닫을 수 있다.
-
Props 전달하기
props은 컴포넌트에 등록할 수 있는 사용자 정의 속성이다.
props 속성에 값이 전달되면, 해당 컴포넌트 인스턴스의 속성이 된다.
컴포넌트는 원하는 만큼 props를 가질 수 있으며, 기본적으로 모든 값을 모두 props에 전달할 수 있다.
props
가 등록되면, 다음과 같이 데이터를 사용자 정의 속성으로 전달할 수 있다.
<template>
<MyBtn />
<MyBtn color="royalblue" />
<MyBtn />
<MyBtn />
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
}
}
</script>
<template>
<div
:style="{ backgroundColor: color }"
class="btn">
Apple
</div>
</template>
<script>
export default {
props: {
color: {
type: String,
default: 'gray'
}
}
}
</script>
<style scoped>
.btn {
display: inline-block;
margin: 4px;
padding: 6px 12px;
border-radius: 4px;
background-color: gray;
color: white;
cursor: pointer;
}
</style>
props로 color를 만들어준다.
class btn에 데이터 바인딩으로 style 속성을 출력한다. 내부 객체 데이터에 backgroundColor는 카멜케이스로 작성하여 props에 해당하는 color 데이터를 연결해준다.
props는 부모 컴포넌트에서 자식 컴포넌트로 특정한 데이터를 전달할때 사용하는 용도라고 해서, 부모 컴포넌트와 자식 컴포넌트간의 데이터 통신 방법이라고도 한다.
부모-자식간의 데이터 통신을 props 기능으로 만들어 낸 것이다.
-
slot 태그
App.vue 파일
<template>
<MyBtn>Banana</MyBtn>
<MyBtn text="Banana" />
</template>
MyBtn.vue 파일
<template>
<div
:class="{ large }"
:style="{ backgroundColor: color }"
class="btn">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
text: {
type: String,
default: ''
}
}
}
</script>
컴포넌트도 일반적인 요소처럼 열리고 닫히는 태그의 사이에 내용을 적어줄 수 있다.
적힌 내용을 어디에 출력할 것인지 slot
태그를 통해 지정해줄 수 있다.
slot 태그를 사용하게 되면 text라는 props를 만들어 줄 필요가 사라지게 된다.
<template>
<MyBtn>Banana</MyBtn>
<MyBtn>
<span style="color: red;">Orange</span>
</MyBtn>
</template>
slot 태그는 단순하게 텍스트만 받아서 태그의 위치에 출력만 해주는 것이 아니다. MyBtn 이라는 컴포넌트가 실행되는 부분에서 열리고 닫히는 태그 사이에 있는 모든 내용을 slot 태그 부분에 대체해서 넣어준다.
컴포넌트: 속성 상속
<template>
<MyBtn class="soha">
Banana
</MyBtn>
</template>
<template>
<div class="btn">
<slot></slot>
</div>
</template>
개발자도구를 통해서 MyBtn의 class를 확인하면 btn과 soha가 들어가있는 것을 확인할 수 있다.
<template>
<div class="btn">
<slot></slot>
</div>
<div></div>
</template>
MyBtn 파일에 div 박스를 추가하면 MyBtn의 클래스에 soha가 사라지고 btn만 남아있게 된다. 왜 이렇게 되는 걸까?
하나의 컴포넌트의 경우에는 template 태그 사이에 기본적인 html 구조를 작성하게 된다. 이때, template 태그의 바로 아래 자식요소를 해당하는 컴포넌트의 최상위요소(루트요소)라고 부른다.
Mybtn 컴포넌트의 경우 최상위요소가 두개 (<div class=”btn” …>
, <div></div>
)존재하게 된다.
이렇게 하나의 컴포넌트에 최상위요소가 두개인 경우에는 컴포넌트가 사용되는 곳에적용이 된 여러 속성들의 내용이 첫번째 최상위요소에 들어갈 것인지, 두번째 최상위요소에 들어갈 것인지 정확하게 지정해줄 수 없다. 그렇기에 어떠한 최상위요소에도 들어가지 않게 된다. (어디에도 들어가지 않게 된, class soha)
우리가 최상위 요소를 단 하나만 남겨두게 된다면 해당 내용이 하나만 존재하는 곳에 들어가게 되는 것이다.
그래서, 컴포넌트가 사용되는 곳의 여러 속성들이 컴포넌트의 하나의 요소에 연결되는 것을 속성의 상속이라고 부른다.
-
속성의 상속 지정 옵션
<script>
export default {
inheritAttrs: false
}
</script>
옵션으로 inheritAttrs를 추가하고 값을 false로 하면, 최상위요소가 하나만 존재해도 상속이 일어나지 않는다.
해당 옵션은 상속의 지정을 설정하는 옵션이다. true면 상속이 이뤄지고 false면 어떤 경우에도 상속이 이뤄지지 않는다.
-
들어오는 옵션을 원하는 곳에 배치하기
created()의 경우 렌더링된 직후의 데이터들을 보여준다.
콘솔창에서 데이터를 확인하면 proxy의 target에 class는 soha, style은 red가 들어온 것을 볼 수 있다.
<template>
<div class="btn">
<slot></slot>
</div>
<div
:class="$attrs.class"
:style="$attrs.style"></div>
</template>
<script>
export default {
inheritAttrs: false,
created() {
console.log(this.$attrs)
}
}
</script>
해당 데이터의 값이 $attrs에 들어가 있으므로, 해당 데이터를 넣어주고 싶은 곳에 위와 같이 데이터를 연결해주면 된다.
두번째 최상위요소의 클래스와 스타일을 확인하면 soha와 color:red가 들어가있는 것을 확인할 수 있다.
-
하나의 요소에 내용 모두 적용
<template>
<div class="btn">
<slot></slot>
</div>
<div
v-bind="$attrs"></div>
</template>
v-bind를 통해서 해당 값으로 $attrs를 넣어주면 해당하는 내용들이 모두 들어가게 된다.
해당하는 컴포넌트(App.vue)부분에 여러가지 속성들을 추가할 때마다, MyBtn 컴포넌트의 div 부분에 따로 명시해줄 필요가 사라진다.
컴포넌트: Emit
속성 상속에서 $attrs 라는 객체를 특정한 요소에 직접적으로 연결을 해서 사용한 것처럼, 이벤트(@click…)들도 직접적으로 연결을 해줄 수 있다.
<template>
<MyBtn @click="log">
Banana
</MyBtn>
</template>
<template>
<div class="btn">
<slot></slot>
</div>
<h1 @click="$emit('click')">
abc
</h1>
</template>
<script>
export default {
emits: [
'click'
]
}
</script>
MyBtn 컴포넌트의 스크립트에 emits 옵션을 추가해서 배열 데이터로 ‘click’ 이벤트를 추가해준다. 컴포넌트 내부에서 사용할 것이라는 걸 정의해준 것이다.
click 이벤트를 연결할 부분에 click 이벤트를 추가하고 해당 태그(h1)를 클릭하게 되면 어떤 내용을 실행시켜줄 것인지를 $emit() 메소드를 통해 지정해준다.
emits 옵션을 통해서 click 이벤트를 부모요소(App.vue)에서 적용된 내용을 가지고와서 명시를 했다. 명시된 내용을 $emit 부분에 작성해주면 된다. emit 메소드의 괄호에 ‘click’을 적어주면 된다.
abc를 클릭하게 되면 콘솔창에 Click!이 출력되는 것을 확인할 수 있다.
-
App.vue 이벤트 이름 변경하기
App.vue 파일에서 해당 컴포넌트(MyBtn)를 클릭(@click)을 했을 때 log 메소드가 실행되는 것일까?
정확하게 따지면 그렇지 않다. App파일에서 말하는 click이라는 이벤트는 MyBtn 컴포넌트의 emits 옵션으로 넘어가 내부에서 사용되기 때문에 굳이 click이라는 이름으로 이벤트를 실행하지 않아도 된다.
<template>
<MyBtn @soha="log">
Banana
</MyBtn>
</template>
<template>
<div class="btn">
<slot></slot>
</div>
<h1 @click="$emit('soha')">
abc
</h1>
</template>
<script>
export default {
emits: [
'soha'
]
}
</script>
위와 같이 click 이벤트의 이름을 soha로 변경해도 h1 태그를 클릭했을 때, 콘솔창에 Click!이 출력되는 것을 볼 수 있다.
결국 컴포넌트에 연결하는 이벤트는 우리가 실제로 쓸 수 있는 이벤트의 이름이 아니어도 상관이 없게된다.
단, 컴포넌트(MyBtn)의 이벤트는 실제 동작할 수 있는 이벤트의 이름으로 해야한다.
-
이벤트가 동작했을 때, 객체 활용하기
$emit의 첫번째 인수로는 이벤트의 이름을 문자데이터로 작성하고 두번째 인수로는 해당 이벤트가 실행될 때 같이 넘겨줄 데이터를 작성할 수 있다.
<template>
<MyBtn @soha="log">
Banana
</MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn'
export default {
components: {
MyBtn
},
methods: {
log(event) {
console.log('Click!!')
console.log(event)
}
}
}
</script>
log 메소드에 event를 추가해서 콘솔창에 event를 출력하게 해준다.
MyBtn 컴포넌트에서 $emit에 두번째 인수를 추가하지 않으면 event 값은 undefined로 출력된다.
<template>
<div class="btn">
<slot></slot>
</div>
<h1 @click="$emit('soha',123)">
abc
</h1>
</template>
$emit의 두번째 인수로 123 숫자 데이터를 넣어주게 되어 click을 했을 때 event의 값으로 123이 콘솔창에 출력된다.
-
양방향 데이터 바인딩으로 활용하기
msg 데이터를 실시간으로 반영하는 양방향 데이터 바인딩을 만든다.
msg 데이터가 변경될 때마다, App.vue에서 확인할 수 있게 만든다.
<template>
<MyBtn
@soha="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>
화면에서 input요소에 데이터를 입력할 때마다, watch 옵션을 통해서 msg 메소드가 실행되도록 한다. 이때 this.$emit을 통해서 changeMsg 이벤트를 실행해준다.
주의! html의 속성을 작성할때는 카멜케이스를 사용할 수 없으므로 대쉬케이스로 수정해준다.
changeMsg ⇒ change-msg
<div class="btn">
<slot></slot>
</div>
<h1 @click="$emit('soha',123)">
abc
</h1>
<input
type="text"
v-model="msg" />
</template>
<script>
export default {
emits: [
'soha',
'changeMsg'
],
data() {
return {
msg: ''
}
},
watch: {
msg() {
this.$emit('changeMsg', this.msg)
}
}
}
</script>
MyBtn 컴포넌트의 input 요소에 데이터를 입력할 때마다, msg 데이터가 양방향 데이터 바인딩으로 갱신이 되는 구조이다.
watch 옵션을 통해서 갱신되는 msg 데이터를 감시하여 데이터가 변경될 때마다 해당 내용($emit('changeMsg’…,)
)을 실행하게 된다.
실행되는 내용은 $emit 메소드를 실행하여 내부의 changeMsg라는 이름의 이벤트를 발생시킨다. 해당 이벤트가 발생되면 changeMsg가 연결되어져 있는 App.vue의 change-msg가 실행되면서 연결된 logMsg 메소드가 동작하게 된다.
logMsg 메소드가 동작할 때, 매개변수는 어떤 내용을 받아서 콘솔창에 출력한다. 이때 받는 내용(매개변수 msg)은 MyBtn의 watch의 $emit 메소드에 작성되어있는 두번째 인수 this.msg 즉, 실제 입력한 데이터이다.
결과적으로 콘솔창을 확인하면 input 요소에 123을 입력하면 다음과 같이 출력된다.
Vue.js: Form 입력 바인딩
양방향 데이터 바인딩(연결)
script의 data를 template에서 사용하고 template에 입력된 데이터를 script의 method에서 실행이 되면서 해당 내용이 다시 template에 출력되는..
즉, 데이터의 연결이 양쪽에서 모두 일어나는 것을 말한다.
추가로, 단방향 데이터 바인딩도 있다.
script의 data를 template에서 사용하고 template의 데이터가 script로 주지 않을 때이다.
즉, 어디서 데이터를 주는 것인지 상관없이 어느 한쪽에서만 데이터를 줄때를 단방향 데이터 바인딩이라고 한다.
From 입력 바인딩은 양방향 데이터 바인딩이 발생할 때, v-model
디렉티브를 통해 위의 내용을 단순화할 수 있도록 한다.
<h1></h1>
<input
type="text"
:value="msg"
@input="msg = $event.target.value"
/>
input에 입력받은 값을 msg data에 넣어서 해당 내용을 h1에 출력한다.
from 입력 바인딩을 사용하기 전에는 v-bind와 input 이벤트를 사용하여 해당 동작을 실행시켰다.
v-model을 사용하면 값으로 원하는 데이터만 넣으면 위와 같은 동작을 간단한 코드로 동작할 수 있게 만든다.
<input
type="text"
:v-model="msg"
/>
즉, v-model은 간단하게 데이터를 연결해주는 디렉티브이다.
v-model 사용시 주의할 점은 한글을 값으로 가지는 데이터를 연결할 경우 출력되는 데이터가 한 박자 늦게 반응한다.
한글의 경우에는 자음과 모음을 통해서 하나의 글자가 만들어져야만 결과가 반영되는 구조를 가지기 때문이다.
따라서, 한글 데이터를 사용할 경우 v-model이 아닌 단순화하기 전의 코드(수동)로 사용하는 것이 좋다.
-
.lazy
<h1></h1>
<input
type="text"
:value="msg"
@change="msg = $event.target.value"
/>
input 이벤트와 달리 change 이벤트는 input 창에 입력되는 데이터를 즉시 msg에 출력하는 것이 아니라, 입력이 멈춰야(커서를 없앤) 데이터를 출력한다.
말 그대로 데이터를 change(변경)한다고 보면 된다.
lazy 수식어는 change 이벤트를 v-model로 단순화시킨 것이다.
<h1></h1>
<input
type="text"
v-model.lazy="msg"
/>
-
.number
input 요소에 입력되는 데이터가 숫자 데이터로 존재해야 할 때 사용한다.
해당 수식어를 사용하지 않고 숫자를 입력하면 type은 number가 아닌 string이 된다. number 수식어를 사용하면 type이 number가 된다.
<h1></h1>
<input
type="text"
v-model.number="msg"
/>
-
.trim
input 요소에 입력되는 데이터의 앞, 뒤 공백을 없애준다.
해당 수식어를 사용하고 앞에 공백을 만들어도 값이 변경되지 않으며, 커서를 없애게 되면 앞의 공백이 사라지게 된다.
<h1></h1>
<input
type="text"
v-model.trim="msg"
/>
이벤트 핸들링
Vue.js: 이벤트 핸들링
-
이벤트 리스닝
v-on 디렉티브를 @기호로 작성이 가능하며, DOM 이벤트를 듣고 트리거 될 때와 자바스크립트를 실행할 때 사용한다.
<button @click="count += 1"></button>
-
메소드 핸들러
위와 같은 인라인 핸들러에서는 복잡한 논리를 사용하기 힘들다. 따라서 메소드에 복잡한 내용들을 작성하고 해당 메소드 명을 이벤트 핸들러로 사용한다.
methods: {
greet(event) {
alert(`안녕 ${this.name}!`)
if (event) {
alert(event.target.tagName)
}
}
}
<button @click="greet">환영하기</button>
-
인라인 핸들러에서 메소드 호출
메서드 이름을 직접 바인딩하지 않고 인라인 핸들러에서 메서드를 호출할 수 있다. 결과로 이벤트 객체 대신 사용자 지정 인자를 출력할 수 있다.
methods: {
handler(msg) {
console.log(msg)
}
}
<button @click="handler('hello')">Click!</button>
-
여러가지 이벤트 핸들러 사용
하나의 요소에 어떤 이벤트가 발생했을 때 실행할 메소드가 여러개라면, 해당 메소드를 쉼표로 구분한다.
단, 주의할 점은 호출할때 메소드 명 뒤에 소괄호를 꼭 붙여야 한다. 소괄호를 사용하지 않으면 메소드가 호출되지 않는다.
methods: {
handlerA() {
console.log('A')
},
handlerB() {
console.log('B')
}
}
<button @click="handlerA(),handlerB()">Click!</button>
버튼을 한번만 클릭해도 메소드 두개가 동시에 작동된다.
콘솔창에서 확인해 보면, A와 B가 동시에 출력된 것을 볼 수 있다.
이벤트 핸들링: 이벤트 수식어
이벤트 핸들러 내에서 event.preventDefault(
)또는 event.stopPropagation()
을 호출 자주 호출하게 된다. 메소드 내에서는 해당 작업을 쉽게 수행할 수 있지만 메소드가 데이터 처리 로직만 가지고 있다면 유지보수가 더욱 용이할 것이다.
그렇기에 이벤트 수식어를 사용하여 유지보수가 용이하도록 만든다.
-
이벤트 수식어 체이닝
이벤트 수식어는 체이닝이 가능하다.
체이닝 형태로 여러가지 수식어를 붙여서 사용할 수 있다.
<div @click.prevent.once="handler"></div>
-
preventDefault
preventDefault는 기본 동작을 방지하는 것이다.
예시를 들자면, html에서 a태그는 기본적으로 페이지 링크를 연결해주는 동작을 가지고 있다. a태그의 기본동작은 링크 연결인 것이다.
즉, html이 기본적으로 가지고 있는 동작이라고 생각하면 된다.
오직 event.preventDefault만 사용한다면 a태그가 가지고 있는 기본 동작이 방지가 되면서 동작하지 않게 되고 다음 로직으로 넘어가게 된다.
-
.prevent
preventDefault라는 기능을 prevent 이름만 작성하여 사용할 수 있게 한다.
-
.once
특정 이벤트가 발생했을 때 해당하는 메소드를 단 한번만 실행해준다.
동작을 다시 실행하더라도 해당 메소드는 더 이상 실행되지 않는다.
-
.stop
해당 수식어는 stopPropagation 메소드를 간단하게 사용하기 위해 만든 수식어이다.
이벤트 버블링을 방지할 때 자주 사용한다.
버블링이 발생하지 않기를 원하는 영역에 해당 수식어를 사용하면 버블링이 발생하지 않고 원하는 부분의 메소드만 실행하게 된다.
즉, 전파를 방해하는 역할을 한다.
이벤트 버블링
마치 거품이 일어나듯이 점점 확산되는 개념이다.
a 박스(부모요소) 안에 b 박스(자식요소)가 존재할 때, a 박스 부분을 클릭하면 a 메소드만 출력되지만 b박스 부분을 클릭하면 b 메소드가 실행된 후 a 메소드도 실행된다.
이는, a박스가 b 박스를 감싸고 있기 때문이다. 이런 현상이 이벤트 버블링이다.
-
.capture
해당 수식어는 버블링의 반대 개념인 캡처링을 발생시키는 수식어이다.
이벤트 실행의 순서를 역으로 진행하게 만든다.
이벤트 캡처링(Event Capturing)
이벤트 버블링의 반대가 되는 개념이다.
이벤트 버블링은 자식요소(b박스)가 먼저 실행되고 부모요소(a박스)가 실행이 된다.
하지만, 이벤트 캡처링은 부모요소가 먼저 실행되고 그 이후에 자식요소가 실행되도록 만든다.
-
.self
해당 수식어가 붙어있는 해당 영역을 정확하게 클릭을 했을 때에만 해당 이벤트가 동작하도록 만들어 준다.
예를 들면, 부모요소 위에 자식요소 버튼이 존재한다. 자식요소에는 click 이벤트가 존재하지 않는다.
부모요소에 self 수식어를 사용하지 않았다면 자식요소를 클릭해도 부모요소의 메소드가 실행된다. 버블링으로 인해 자식요소 뒤에 있는 부모요소가 실행되는 것이다.
부모요소에 self 수식어를 사용한다면, 자식요소를 클릭해도 이벤트가 실행되지 않는다. 오직 self 수식어가 붙어있는 영역인 부모요소의 이벤트만 실행되게 된다.
정확이 말하자면, 모든 영역이 부모요소인 것은 맞지만 자식요소로 인해 자식요소가 존재하는 부분은 노출되어져 있지 않다. 자식요소가 해당 부분을 가리기 때문이다.
그렇기에 self는 오직 부모요소가 노출되어져 있는 부분만을 클릭했을 때 이벤트가 실행하게 만든다.
-
.passive
처리해야 하는 로직이 굉장히 많이 존재할 때 동작 실행이 느려지게 된다.
많은 양의 로직과 브라우저에 보이는 화면을 동시에 처리하기 때문이다.
passive 수식어를 사용하게 되면 로직 처리와 브라우저 출력을 독립시킬 수 있다. 그렇게 되면 브라우저에서 보이는 렉(동작 느려짐)이 줄어들고 로직 처리는 따로 돌아가게 된다. 사용자 입장에서 화면을 이용할 때 편리하며, 로직은 내부적으로 따로 돌아가기 때문에 문제가 되지 않는다.
<div @scroll.passive="onScroll">...</div>
즉, passive는 로직처리가 많은 메소드를 호출하는 요소에 사용하면 좋다.
추가로, passive는 prevent와 함께 사용해서는 안된다. passive는 브라우저에게 이벤트의 기본동작을 방해(prevent)하지 않겠다는 의미를 가지기 때문이다.
이벤트 핸들링: 키 수식어
input에서 enter 키를 입력했을 때 콘솔창에 Enter라는 문구가 출력되도록 코드를 작성했다.
key 요소에 값으로 Enter가 들어왔을 때, 콘솔창에 Enter!! 문구를 출력하도록 if문을 사용하여 작성했다.
<template>
<input
type="text"
@keydown="handler" />
</template>
<script>
export default {
methods: {
handler(event) {
if (event.key === 'Enter') {
console.log('Enter!!')
}
}
}
}
</script>
하지만, 이렇게 if문을 사용하지 않고 간단하게 키 수식어를 통해 작동되도록 할 수 있다.
input에 a를 제외한 나머지 키들을 입력하다가 a키를 누르면 콘솔창에 Enter 문구가 출력되도록 작성했다.
이제는 if 문이 아니라 keydown 이벤트 뒤에 a 수식어만 추가해주면 된다.
<template>
<input
type="text"
@keydown.a="handler" />
</template>
<script>
export default {
methods: {
handler() {
console.log('Enter!!')
}
}
}
</script>
위 코드 처럼 원하는 키를 눌렀을 때 어떤 동작이 실행되도록 만들고 싶다면 이벤트 뒤에 원하는 키 수식어를 작성하면 된다.
단, 수식어는 꼭 케밥케이스로 작성해야 한다.
예) a ⇒ .a / enter ⇒ .enter
추가로, 해당 키 수식어 또한 체이닝이 가능하다.
@keydown.ctrl.a=”handler”
a키만 누르면 메소드가 실행되지 않고 ctrl 키와 a키를 같이 눌러야 메소드가 실행된다.
리스트 렌더링
Vue.js 리스트 렌더링
-
v-for
v-for을 사용하여 배열을 리스트로 렌더링할 수 있다.
item in items 형식에서 (해당 코드에서는 fruit in fruits) fruit 부분을 괄호로 묶고 뒤에 index 변수를 생성한다.
index 변수를 내부에서 이중 중괄호 구문을 추가해서 (앞에 하이픈기호 추가) 해당 변수를 넣어준다.
<template>
<ul>
<li
v-for="(fruit, index) in fruits"
:key="fruit">
-
</li>
</ul>
</template>
해당 코드의 결과로는 fruit에 들어온 fruits 배열의 데이터들, fruits 내부에서 반복될때마다 index의 값이 1씩 증가되어 출력된다.
-
객체에 v-for
원본 데이터(fruits 배열데이터)를 가지고 계산된 데이터 (newFruits 배열 데이터)를 만들었다.
<script>
export default {
data() {
return {
fruits: ['Apple', 'Banana', 'Cherry'],
// newFruits: [
// { id: 0, name: 'Apple' },
// { id: 1, name: 'Banana' },
// { id: 2, name: 'Cherry' }
// ]
}
},
computed: {
newFruits() {
return this.fruits.map((fruit, index) => {
return {
id: index,
name: fruit
}
})
}
}
}
</script>
v-for 디렉티브로 배열데이터를 출력할 때, 각각의 배열의 아이템들을 고유하게 구별해 줄 수 있는 (여기서는 id) 특정한 속성이 존재해야 한다.
해당 속성을 통해서 각각의 배열 아이템을 고유하게 구분해줄 수 있어야만 화면에 출력되는 내용을 vue.js를 통해서 최적화를 시켜줄 수 있다.
-
id를 고유하게 만들어주는 패키지
<template>
<ul>
<li
v-for="fruit in newFruits"
:key="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>
id 값에 shortid를 넣고 generate 메소드를 실행한다. 간단한 id(shortid)를 생성(generate)해주는 것이다.
그러면 비교적 짧은 고유한 값의 문자 데이터가 반환된다.
id 값을 확인하기 위해서 fruit.id를 출력해보면 문자 데이터로 된 id값을 확인할 수 있다.
-
배열 변경 감지
-
수정 메소드
감시중인 배열의 변이 메소드를 래핑하여 뷰 갱신을 트리거한다.
즉, 배열의 데이터를 바꿔두면 실제 화면에 갱신되어 출력이 되는 반응성을 가진다는 의미이다.
데이터를 수정했을 때, 반응성을 통해서 화면에 갱신되어 출력된다.
-
push()
배열의 가장 마지막에 데이터를 밀어 넣는다.
-
pop()
배열의 가장 뒤쪽의 데이터를 꺼내서 반환해준다.
-
shift()
배열의 가장 앞쪽의 데이터를 꺼내서 반환한다.
-
unshift()
배열의 가장 앞쪽에 데이터를 밀어 넣는다.
-
splice()
인덱스를 통해서 데이터를 넣거나 빼고 삭제할 때 사용한다.
-
sort()
배열을 정렬시켜준다.
-
reverse()
배열을 뒤짚어준다.
-
배열 교체
변이 메소드는 실행이 되면 원본의 배열 자체를 변경한다.
이에 비해 filter(), concat(), slice() 같은 메소드는 원본의 데이터는 변하지 않고 해당 메소드가 동작한 이후에 새로운 배열 데이터를 반환한다. 이것을 비-변이 메소드라고 말한다.
비-변이 메소드는 이전 배열을 새로운 배열로 바꿔서 반응성을 가지고 화면을 갱신할 수 있다.
this.items = this.items.filter((item) => item.message.match(/Foo/))
새로운 배열 아이템으로 기존의 배열 아이템을 대체하면 이로 인해 Vue가 기존의 DOM(html 구조)을 버리고 전체목록을 다시 렌더링할 것이라고 생각 할 수도 있지만 그렇지 않다.
Vue는 DOM 요소 재사용을 최대한 효율적으로 관리하기 위해서 스마트 휴리스틱(smart heuristics)을 사용하고 있다.
그래서 기존의 배열 데이터에서 약간 수정된 데이터를 다시 할당하게 되면 고유한 값(대표적으로 key 값)을 기준으로 해서 필요한 내용들만 다시 그린다. 필요하지 않는 내용들은 화면에 새롭게 그리지 않는다.
즉, 성능적인 자원을 최소화하여 화면에 내용을 다시 그려낼 수 있다.
조건부 렌더링
Vue.js 조건부 렌더링
-
v-if, v-else
조건에 따라 블록을 렌더링할 때 사용한다. 블록은 디렉티브의 표현식이 true 값을 반환할 때만 렌더링된다.
v-else와 함께 else 블록을 추가할 수 있다.
<h1 v-if="awesome">Vue is awesome</h1>
<h1 v-else>Oh no</h1>
awesome 데이터가 참으로 해석될 수 있는 값(Truthy)이면, 해당 디렉티브가 있는 h1 요소가 화면에 렌더링된다.
awesome 데이터가 true가 아닌 false로 해석된다면 첫번째 h1 태그는 화면에 렌더링되지 않고, v-else가 있는 두번째 h1 태그가 렌더링된다.
-
예시
<template>
<button @click="handler">
Click!
</button>
<h1 v-if="isShow">
Hello!
</h1>
<h1 v-else>
Good~
</h1>
</template>
<script>
export default {
data() {
return {
isShow: true
}
},
methods: {
handler() {
this.isShow = !this.isShow
}
}
}
</script>
handler는 isShow가 true인 경우에는 false가 할당, false인 경우에는 true가 할당되는 구조이다.
결과적으로는 버튼을 누르면 Hello 문구가 사라지고 Good 문구가 나타나며 다시 버튼을 누르게 되면 Hello 문구가 나타난다. (즉 토글 만들기!)
-
v-else-if
<template>
<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>
v-if가 false이고 v-else-if에 작성해둔 조건을 만족하게 되면 v-else-if가 화면에 출력된다.
조건 count 값이 3 이상을 만족하게 되면 true일 때는 Hello가 출력, false일 때는 Count > 3이 출력된다.
Good은 count 값이 3 이하일 때, isShow 값이 false일 시에만 출력된다.
-
template에서 v-if
v-if 조건을 통해서 특정한 요소들을 그룹으로 보이거나 보이지 않게 할 때, 일반 요소 대신에 template 요소를 사용한다.
단, v-if 디렉티브를 해당 파일의 최상위 template에는 사용하면 안된다.
최상위 template에 사용하면 따로 적용되지 않는다.
template의 내용 전체를 없애고 싶다면 최상위 template 안에 새로운 template 태그를 생성하여 해당 내용을 전체 감싸주면 된다.
-
div를 사용하여 감싸기
개발자도구에서 보면 div 태그에 h1, p태그들이 감싸져있는 것을 볼 수 있다.
<template>
<button @click="handler">
Click!
</button>
<div v-if="isShow">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</div>
</template>
-
template로 감싸기
개발자도구에서 확인해보면 template 태그는 보이지 않고 h1, p태그들만 보이게 된다. 즉, 감싼 template 태그는 보이지 않는다!
template는 렌더링이 되지 않는 것!
<template>
<button @click="handler">
Click!
</button>
<template v-if="isShow">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
</template>
-
v-show
연결되어있는 데이터의 값의 상관없이 렌더링한다.
v-if의 경우 데이터 값에 따라 렌더링을 진행한다.
<template>
<button @click="handler">
Click!
</button>
<h1 v-show="isShow">
Hello?
</h1>
</template>
h1 태그가 v-if일 때에는 isShow가 false이기 때문에 화면에 Hello가 출력되지 않고 개발자도구를 열어도 해당 h1태그가 보이지 않는다.
하지만 v-show로 하면 화면상에는 출력되지 않지만 개발자도구에는 해당 태그가 보이게 된다. 또한 h1 태그의 style 속성이 display이 none으로 되어있다.
v-show는 template 요소를 지원하지 않으며, v-else와 함께 사용할 수 없다.
-
v-if 와 v-show 비교
-
v-if
“실제”조건부 렌더링이다. 초기 렌더링 시, 조건이 false이면 아무 작업도 하지 않는다. 조건부 블록또한 조건이 처음으로 true가 될 때까지 렌더링되지 않는다.
즉, 개발자도구에서 해당 내용이 출력되지 않는다.
-
v-show
CSS 기반 전환으로 초기 조건과 관계없이 항상 렌더링된다.
조건과 상관없이 개발자도구를 열어서 확인하면 내용은 항상 렌더링되어있다. 다만, false일 경우 display가 none이 된다.
-
비교
v-if는 전환 비용이 높은 반면에 v-show는 초기 렌더링 비용이 높다.
따라서 자주 전환이 일어나야 한다면 v-show를 사용하는 것이 좋으며, 화면이 동작하고 있을 때 적용해 놓은 데이터의 조건이 자주 변경되지 않는다면 v-if를 사용하는 것이 더 좋다.