3 컴포넌트 Basic
source: categories/study/vue-project/vue-project_3.md
3 컴포넌트 Basic
- 이번 챕터에서는 Vue 컴포넌트 개발을 위한 기본 문법에 대해서 알아봅니다.
- Vue 컴포넌트의 코드 구조를 이해하고, HTML 입력 폼 객체에 따른 데이터 바인딩 방법을 구체적으로 알아보게 됩니다.
- 이외에도 리스트 랜더링, 랜더링 문법, 이벤트 처리 등 Vue 컴포넌트에서 HTML DOM 처리를 위한 문법을 익히게 됩니다.
3.1 컴포넌트란?
- 컴포넌트는 View, Data, Code의 세트라고 생각하면 됩니다.
- 컴포넌트 안에는 HTML 코드가 있고, 이 HTML 코드를 실행하기 위한 자바스크립트 코드 그리고 데이터가 존재합니다.
- 컴포넌트의 가장 큰 특징은 재사용이 가능하다는 점입니다.
- 즉, 다른 컴포넌트에
import
해서 사용할 수 있습니다. - Vue에서 컴포넌트는 우리가 화면에서 보는 페이지 자체일 수도 있고, 페이지 내의 특정 기능 요소일 수 있습니다.
- 우리가 Vue로 프론트엔드를 개발할 때 컴포넌트 설계를 어떻게 하는가는 매우 중요합니다.
- 동일한 기능을 갖는 요소가 발견이 되었을 때 해당 기능을 컴포넌트로 구현해서, 사용하는 모든 페이지에서 호출해서 사용할 수도 있고, 동일한 기능을 필요한 각각의 페이지에서 개별로 작성할 수도 있습니다.
- Vue CLI를 이용해서 프로젝트를 최초 생성했을 때 src 폴더 밑에 components 폴더가 있는 것을 확인할 수 있습니다.
- 그런데 라우터 설정을 위해서
vue-router
를 추가하면 src 폴더 밑에 views 폴더가 생성되는 것을 확인할 수 있습니다. - 실제 프로젝트에서는 views 폴더에 우리가 페이지라고 부르는 화면 하나하나에 해당하는 vue 컴포넌트 파일을 생성하고,
- components 폴더에는 다른 vue 파일에서 호출해서 공통으로 사용할 수 있는 vue 컴포넌트 파일을 생성하고 관리하게 됩니다.
- 이렇게 페이지 전체를 이루는 vue 파일이나 재사용을 위한 화면의 일부 기능 요소에 해당하는 vue 파일이나 vue 입장에서는 모두 컴포넌트이고, 내부적으로 동일한 구조를 가지게 됩니다.
- 그래서 이렇게 물리적으로 프로젝트 폴더를 구분해서 사용하는 것이 관리적인 차원에서 훨씬 효율적입니다.
3.2 컴포넌트 구조 이해하기
- 컴포넌트 구조를 이해한다는 것은 쉽게 말해서, 여러분이 앞으로 작성해야 할 코드의 기본 구조를 만드는 것입니다.
- 사실 저는 지금부터 우리가 배울 컴포넌트 구조에 해당하는 프로그램 코드를 갖는 vue 파일을 작성해놓고, 매번 vue 기반 프로젝트를 할 때마다 해당 vue 파일을 복사해서 사용합니다.
- 요즘은 vs code 뿐만 아니라 대다수의 개발 IDE 도구는
snippet
기능을 제공합니다. snippet
은 특정 코드를 미리 작성하고 등록하여 단축키로 코드를 불러와서 바로 사용할 수 있는 기능입니다.- 저 역시도
vue
의snippet
기능을 이용해서 컴포넌트 기본 구조에 해당하는 코드를 등록해서 사용합니다. - 그렇다면
vue
컴포넌트의 기본 구조를 알아본 다음, 해당 코드를snippet
으로 등록하는 것까지 해보겠습니다.
3.2.1 컴포넌트 기본 구조
- 뷰 컴포넌트 내에는
name
,components
,data
,computed
같은 기본 프로퍼티 외에도 뷰 컴포넌트가 생성이 되고 종료가 되기까지 발생하는 라이프사이클 훅에 해당하는 메소드 등이 있습니다. - 이중에
vue
컴포넌트 개발시 필자가 가장 자주 사용하게 되는 기본 코드 구조를snippet
에 등록하여 사용하겠습니다.
<template>
<div></div>
</template>
<script>
export default {
name: '', // 컴포넌트 이름
components: {}, // 다른 컴포넌트 사용 시 컴포넌트를 import 하고, 배열로 저장
data() { // html과 자바스크립트 코드에서 사용할 데이터 변수 선언
return {
sampleData: ''
}
},
setup() {}, // 컴포지션 API
created() {}, // 컴포넌트가 생성되면 실행
mounted() {}, // template에 정의된 html 코드가 랜더링된 후 실행
unmounted() {}, // unmount가 완료된 후 실행
methods: {} // 컴포넌트 내에서 사용할 메소드 정의
}
</script>
<template>
view
에 해당하는html
코드를 작성하는 영역입니다.
name
- 컴포넌트 이름을 등록합니다.
- 컴포넌트 이름은 등록하지 않아도 사용하는 것에는 지장이 없습니다.
components
- 외부 컴포넌트를 사용하게되면 해당 컴포넌트를
import
한 후, 이곳에 배열로 등록해줘야 합니다.
- 외부 컴포넌트를 사용하게되면 해당 컴포넌트를
data
- 데이터 프로퍼티는
html
코드와 자바스크립트 코드에서 전역 변수로 사용하기 위해 선언하는 데이터입니다. - 데이터 바인딩을 통해 화면에 해당하는
html
코드에 해당하는 자바스크립트 간의 양방향 통신이 가능하도록 합니다. - 데이터 프로퍼티에 정의된 변수는
this
를 통해서 접근해야 합니다.
- 데이터 프로퍼티는
setup
- 컴포지션 API를 구현하는 메소드입니다.
created
- 컴포넌트가 생성되면 실행됩니다.
mounted
- 템플릿에 작성한 HTML 코드가 랜더링 된 후 실행됩니다.
unmounted
- 컴포넌트를 빠져나갈 때 실행됩니다.
methods
- 컴포넌트 내에서 사용할 메소드를 정의하는 곳입니다.
- 이곳에 작성된 메소드는
this
를 통해서 접근해야 합니다.
- 컴포넌트 라이프사이클 훅에는
created
,mounted
,unmounted
외에도 더 많은 라이프사이클 훅이 존재하지만, created
,mounted
,unmounted
3가지만 기본 구조로 사용하는 이유는 실무에서 컴포넌트 개발 시 가장 많이 사용하는 라이프사이클 훅이기 때문입니다.
- 이 코드는 여러분이 앞으로
vue
개발을 할 때 항상 작성해 놓고 시작해야 하는 코드입니다. - 좀 더 자극적으로 말씀드리면 이 코드를 항상 복사해놓고,
.vue
파일을 만들고 나면 바로 붙여놓고 시작하라고 말하고 싶습니다. - 그런데 만들어둔 코드를 매번 복사하고 붙여넣기해서 사용하기에는 너무 귀찮을 것 같습니다.
- 고맙게도 vs code에서는 이런 코드를 등록해서 불러와서 사용할 수 있는 snippet 기능을 제공합니다.
- 그럼 이번에 작성한 컴포넌트의 기본 구조를 snippet에 등록해 보겠습니다.
3.2.2 Lifecycle Hooks
- 모든 컴포넌트는 생성될 때 초기화 단계를 거치게 됩니다.
- 예를 들어, 데이터의 변경사항 감시를 위한 설정, 템플릿 컴파일, 인스턴스를 DOM에 마운트하고, 데이터가 변경되면 DOM을 업데이트 해야 합니다.
Tip
- Vue 컴포넌트가 개발 시 각 라이프사이클 훅에 따라 프로그램을 적절히 배치하면 화면 로딩 시간을 개선할 수 있습니다.
- 정확히는 사용자가 느끼는 체감 속도를 많이 높일 수 있습니다.
- 예를 들어 사용자가 특정 화면에 접속했을 때, 화면에서 제일 먼저 보여줘야 하는 데이터 영역의 경우는
created()
에 정의해서 서버로부터 미리 받아오고, 화면 로딩 이후에 삽입되어도 되는 데이터 혹은 HTML 객체 부분은mounted()
훅에 정의함으로써 데이터와 HTML 부분을 로딩하는 타이밍을 적절히 분배하는 것입니다. - 이렇게 함으로써 사용자가 느끼는 화면 로딩 속도를 개선할 수 있습니다.
- app = Vue.createApp(options); app.mount(el)
- Init events & lifecycle
- beforeCreate
- Init injections & reactivity
- created
- Has ‘template' options?
- Compile template into render function * / Compile el's innerHTML as template *
- beforeMount
- Create app.$el and append it to el
- mounted
- Mounted
- when data changes
- beforeUpdate
- Virtual DOM re-rendered and patch
- updated
- when app.unmount() is called
- beforeUnmount
- Unmounted
- unmounted
3.3 데이터 바인딩
- Vue는 Angular와 마찬가지로 양방향 데이터 바인딩(Two-way data binding)을 지원합니다.
- 참고로 React는 단방향 데이터 바인딩만을 지원합니다.
- 여기서 양방향 데이터 바인딩이라는 것은 모델(Model)에서 데이터를 정의한 후 뷰(View)와 연결하면 모델과 뷰 중 어느 한쪽에 변경이 일어났을 때 다른 한쪽에 자동으로 반영되는 것을 의미합니다.
- 실제 프로젝트 내에서 서버로부터 받아온 데이터를 바인딩하는 경우는 다음과 같은 경우들을 생각해볼 수 있습니다.
- 데이터가 html tag 안에 텍스트로 바인딩 되는 경우
- 데이터가 html tag의 속성(attribute)로 바인딩 되는 경우
- 데이터가 html의 Form element의 value에 바인딩 되는 경우
- 다중 데이터가 html의 다중 element를 생성하기 위해서 바인딩 되는 경우
- Vue 컴포넌트에서 데이터를 바인딩하는 방법은 바인딩 되는 유형에 따라 적용하는 방식에 조금씩 차이가 있습니다.
- 지금부터 하나하나씩 차례대로 알아보겠습니다.
- src/views 폴더에 DataBinding.vue 파일을 생성하고, DataBinding.vue 파일에 다음과 같은 코드를 작성합니다.
<template>
<h1>Hello, {{ title }}</h1>
</template>
<script>
export default {
data() {
return {
title: 'World'
}
}
}
</script>
- 코드를 보면
<template>
에는title
,<script>
에는data()
부분에title: 'World'
라는 코드가 있습니다. data
프로퍼티에 정의된title
이template
의title
에 바인됭 되는 구조입니다.- 이렇게 Vue 컴포넌트에서는
data
에 정의되는 데이터를 이중 중괄호를 이용해서html
에 데이터 바인딩할 수 있습니다.
- 그럼 정상적으로 반영이 되는지 확인하기 위해서 생성한 DataBinding.vue 파일을 라우터에 등록하겠습니다.
- DataBinding.vue 파일에 대한 접근이 가능하도록 router/index.js에 다음과 같이 추가합니다.
- 앞으로 나오는 모든 예제는 라우터 router/index.js에 추가하는 과정이 생략되어 있습니다.
- 브라우저에서 실행하기 위해서 반드시 router/index.js에 다음과 같이 추가해서 사용해야 합니다.
// router/index.js
import {
createRouter,
createWebHistory
} from 'vue-router';
import Home from '../views/Home.vue';
import DataBinding from '../views/DataBinding.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/databinding',
name: 'DataBinding',
component: DataBinding
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router;
- App.vue 파일을 열어서 다음과 같이 DataBinding.vue에 대한 링크를 추가합니다.
<template>
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/databinding">Data Binding</router-link>
</div>
</template>
3.3.1 문자열 데이터 바인딩
- 문자열의 경우 앞서 실행한 것처럼, 이중 중괄호를 이용해서 데이터 바인딩을 하면 됩니다.
3.3.2 raw(원시) HTML 데이터 바인딩
- HTML 태그를 바인딩 할 때는 문자열을 바인딩 할 때 사용한 이중 중괄호를 이용하면 안됩니다.
- 이중 중괄호를 이용해서 바인딩 하면
html
태그가 아니라 문자열, 즉, 텍스트로 인식하게 됩니다.
- 실제 HTML로 출력되기 위해서는
v-html
디렉티브를 사용해야 합니다.
- 다음 코드를 보면 이중 중괄호를 사용하는 경우와
v-html
디렉티브를 사용하는 경우의 코드가 있습니다. - 참고로 Vue에서 사용하는 디렉티브는
v-
접두사로 사용합니다.
<template>
<div>
<div>{{ htmlString }}</div>
<div v-html="htmlString"></div>
</div>
</template>
<script>
export default {
data() {
return {
htmlString: '<p style="color:red">This is a red string.</p>'
}
}
}
</script>
- 이 코드를 실행하면 다음 화면처럼
html
코드를 하나는 문자열로, 하나는html
로 랜더링 되는 것을 확인할 수 있습니다.
3.3.3 Form 입력 데이터 바인딩
- 웹 페이지에서 사용자로부터 데이터를 입력받을 수 있는 필드를 Form Element 라고 합니다.
- v-model 디렉티브를 사용하여 양방향 데이터 바인딩을 생성할 수 있습니다.
- 여기서 주의해야 할 점은 v-model은 내부적으로 서로 다른 속성을 사용하고 서로 다른 입력 요소에 대해 서로 다른 이벤트를 전송한다는 것입니다.
3.3.3.1 input type=text
- 사용자로부터 텍스트를 입력받을 수 있는
input type=text
의 경우, 입력받은 텍스트는value
에 저장이 되게 됩니다. input type=text
에서v-model
은 내부적으로input type=text
의value
속성을 사용하게 됩니다.data()
에 정의된 데이터 키명을v-model
에 넣어주면 모델인data
와 뷰인input type=text
의value
속성이 서로 양방향으로 데이터 바인딩 설정됩니다.- 다음 코드를 통해
form element
인input type=text
에 대한 양방향 데이터 바인딩을 이해하겠습니다.
<template>
<div>
<input type="text" v-model="valueModel" />
</div>
</template>
<script>
export default {
data() {
return {
valueModel: 'South Korea'
}
}
}
</script>
- 이 코드를 저장하고 실행해 보면
input type=text
객체의value
에valueModel
의 값인South Korea
에 바인딩 되어서 화면에 나타나는 것을 확인할 수 있습니다. - 모델인
data
에서 뷰인input type=text
객체로 단방향으로 데이터 바인딩 된 것처럼 보이지만, 실제로는 사용자가input type=text
객체의 텍스트를 직접 입력하여 변경하면 변경된 데이터를 가져오는 별도의 코드 작성 없이 모델인data
의valueModel
에 사용자가 입력한 텍스트가 그대로 저장이 되게 됩니다. - 실제 사용자가 입력한 텍스트가 모델에 바로 반영되는 것은 이벤트 바인딩 챕터 부분에서 확인하겠습니다.
3.3.3.2 input type=number
- 사용자로부터 숫자 값을 입력받는 경우가 있습니다.
- 사용자의 입력 값이, 문자가 아닌 숫자로 바로 처리할 수 있도록
v-model.number
디렉티브를 사용할 수 있습니다.
<template>
<div>
<input type="number" v-model.number="numberModel" />
</div>
</template>
<script>
export default {
data() {
return {
numberModel: 3
}
}
}
</script>
- 코드를 보면
data
에numberModel
키로 숫자 3을 할당했습니다. - 그리고
input type=number
객체에v-model.number="numberModel"
로 데이터를 바인딩 했습니다. - 이렇게 하면 나중에 프로그램 코드 내에서 사용자가 입력한 값은 문자가 아니라 숫자로 관리가 됩니다.
3.3.3.3 textarea
textarea
의 경우 지금까지 우리가 사용했던 방법으로는<textarea>텍스트 영역 메시지</textarea>
형태로 사용해왔기 때문에 Vue 컴포넌트에서 역시 이와 같이 사용될거라 생각할 수 있는데,- 실제로는
<textarea v-model="message"></textarea>
로 사용해야 합니다.
<template>
<div>
<textarea v-model="message"></textarea>
</div>
</template>
<script>
export default {
data() {
return {
message: '여러 줄을 입력할 수 있는 textarea 입니다.'
}
}
}
</script>
3.3.3.4 Select
select
객체 역시input type=text
와 동일하게v-model
은 내부적으로select
의value
속성을 사용해서 양방향 데이터 바인딩을 합니다.value
속성을 사용해서 양방향 데이터 바인딩을 합니다.
<template>
<div>
<select v-model="city">
<option value="02">서울</option>
<option value="21">부산</option>
<option value="064">제주</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
city: '064'
}
}
}
</script>
- Data 프로퍼티의 city에 "064"를 할당했습니다.
- Select 객체에는 v-model로 data에서 정의한 city로 맵핑했습니다.
- 이 코드를 실행하면 다음과 같이 select 객체의 초기 값이 "064"로 설정이 되면서 화면에서는 ‘제주'가 선택된 상태로 보이게 됩니다.
3.3.3.5 체크박스(input type=checkbox)
- 체크박스의 경우는
input type=text
,select
와 다르게v-model
은 내부적으로 체크박스의checked
속성을 사용합니다. - 체크박스에서는
v-model
이 체크박스의value
속성이 아닌checked
속성을 사용하기 때문에value
속성에 데이터 바인딩을 하려면v-model
이 아닌v-bind:value
을 사용해야 합니다.
<template>
<div>
<label><input type="checkbox" v-model="checked">{{ checked }}</label>
</div>
</template>
<script>
export default {
data() {
return {
checked: true,
}
}
}
</script>
- 체크박스가 체크 되었을 때의 기본 값은 true이고, 체크가 해제되었을 때 기본 값은 false입니다.
- 다음 코드를 통해 체크/해제 되었을 때의 기본 값을 변경할 수 있습니다.
<template>
<label>
<input type="checkbox" v-model="checked" true-value="yes" false-value="no">
{{ checked }}
</label>
</template>
<script>
export default {
data() {
return {
checked: true,
}
}
}
</script>
- 위와 같이 하면 체크되었을 때 화면에 "yes", 체크 안되어있을 때 "no"가 출력됩니다.
- 여러 개의 체크박스를 사용할 때는 배열을 이용해서 데이터를 바인딩해서 한 번에 처리할 수 있습니다.
<template>
<div>
<label><input type="checkbox" value="서울" v-model="checked">서울</label>
<label><input type="checkbox" value="부산" v-model="checked">부산</label>
<label><input type="checkbox" value="제주" v-model="checked">제주</label>
<br/>
<span>체크한 지역: {{ checked }}</span>
</div>
</template>
<script>
export default {
data() {
return {
checked: [],
}
}
}
</script>
3.3.3.6 라디오 (input type=radio)
- 라디오 역시 체크박스와 마찬가지로
v-model
은 내부적으로checked
속성과 바인딩이 이뤄집니다. - 라디오에서는
v-model
이 라디오의value
속성이 아닌checked
속성을 사용하기 때문에value
속성에 데이터 바인딩을 하려면v-model
이 아닌v-bind:value
을 사용해야 합니다.
- 라디오에서 체크를 하게 되면 체크된
v-bind:value
에 연결됩니다.
<template>
<div>
<label><input type="radio" :value="radioValue1" v-model="picked">서울</label>
<label><input type="radio" :value="radioValue2" v-model="picked">부산</label>
<label><input type="radio" :value="radioValue3" v-model="picked">제주</label>
<br/>
<span>선택한 지역: {{ picked }}</span>
</div>
</template>
<script>
export default {
data() {
return {
picked: '',
radioValue1: '서울',
radioValue2: '부산',
radioValue3: '제주',
}
}
}
</script>
- 앞서 살펴본 것과 같이 Form Element에 속하는 객체들은 v-model 디렉티브를 사용해서 양방향 데이터 바인딩 처리를 할 수 있습니다.
3.3.4 속성 (Attribute)
value
을 제외한HTML
객체의 속성(attribute)에 데이터를 바인딩 하기 위해서v-bind:
디렉티브를 사용합니다.v-bind:
디렉티브는v-bind
을 생략하고:
(콜론)으로 사용할 수도 있습니다.
- 실제 개발에서 가장 많이 사용되는 유형을 통해 속성에 데이터를 바인딩 하는 부분을 이해하겠습니다.
3.3.4.1 Img 객체의 src
- 제품 이미지, 사용자 프로필 사진처럼 이미지의 주소를 img 객체의 src에 바인딩 해야 하는 경우가 있습니다.
- 다음 코드를 통해 확인하겠습니다.
<template>
<div>
<img :src="imgSrc" />
</div>
</template>
<script>
export default {
data() {
return {
imgSrc: "https://kr.vuejs.org/images/logo.png"
}
}
}
</script>
- 코드를 보시면
data
에 정의한imgSrc
를img
객체의src
속성에 바인딩했습니다.
3.3.4.2 button 객체의 disabled
- 일반적으로 버튼 객체의 disabled 속성을 활용하지 않는 경우가 많이 있는데,
- 경험 많은 개발자일수록 버튼의 disabled 속성을 반드시 활용합니다.
- 버튼에서 disabled 속성이 true로 되어 있으면 버튼은 비활성화가 되고, 사용자가 클릭을 해도 이벤트가 발생되지 않습니다.
- 이렇게 버튼에 대한 disabled 속성은 다음과 같은 경우 적용하는 것이 필요합니다.
- 조회하면에서 조회 조건 중 필수 입력 조건이 모두 입력이 되었을 때 버튼을 활성화합니다.
- 권한이 있는 사용자에게만 허용되는 버튼에 대해서 활성화합니다.
- 데이터 바인딩을 통해 button 객체의 disabled 속성을 제어해보겠습니다.
<template>
<div>
<input type="text" v-model="textValue" />
<button type="button" :disabled="textValue === ''">Click</button>
</div>
</template>
<script>
export default {
data() {
return {
textValue: ""
}
}
}
</script>
- 예제 코드에서는
input type=text
에 데이터가 입력되는 순간, 버튼이 활성화가 됩니다. - 즉,
v-model=textValue
가 비어 있는 경우는 버튼이 비활성화 되어 있게 됩니다.
3.3.5 클래스 바인딩
- 클래스에 대한 바인딩 처리 시 특이한 점은 반드시 적용해야 하는 클래스는 기존
html
에 사용하던 방식처럼class
속성에 클래스명을 입력하면 되고, - 조건에 따라 바인딩할 클래스의 경우는
:class
를 이용해서 추가적으로 정의해서 사용할 수 있다는 것입니다.
- 다른 속성의 경우 하나의 속성만을 이용해서 바인딩 해야 하지만 클래스의 경우는 기본 클래스와 데이터 바인딩 처리를 하는 클래스를 공존해서 사용할 수 있다는 것입니다.
<template>
<div class="container" :class="{
active: isActive,
'text-red': hasError,
}">Class Binding</div>
</template>
<script>
export default {
data() {
return {
isActive: true,
hasError: false,
}
}
}
</script>
<style scoped>
container {
width: 100%;
height: 200px;
}
.active {
background-color: yellow;
font-weight: bold;
}
.text-red {
color: red;
}
</style>
- 클래스 바인딩의 경우 오브젝트 형태로 사용하며, 바인딩할 클래스를
Key
로 잡고, 바인딩 여부를true/false
로 지정합니다.
- 다음과 같이 배열을 사용해서 클래스를 바인딩 할 수도 있습니다.
<template>
<div class="container" :class="[activeClass, errorClass]">Class Binding</div>
</template>
<script>
export default {
data() {
return {
activeClass: 'active',
errorClass: 'text-red',
}
}
}
</script>
- 배열을 사용한 예제코드의 결과는
class="container active text-red"
로 적용이 됩니다. - 배열을 사용하는 경우는 특정 조건에 다른 클래스 바인딩 처리를
true/false
로 할 수 없습니다.
3.3.6 인라인 스타일 바인딩
- 인라인 스타일의 경우 데이터를 오브젝트로 선언해서 바인딩할 수 있습니다.
<template>
<div :style="styleObject">인라인 스타일 바인딩</div>
</template>
<script>
export default {
data() {
return {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
}
}
</script>
- 인라인 스타일 바인딩 역시 클래스 바인딩처럼, 배열을 이용해서 바인딩 할 수 있습니다.
<template>
<div :style="[baseStyle, addStyle]">인라인 스타일 바인딩</div>
</template>
<script>
export default {
data() {
return {
baseStyle: 'background-color:yellow; width: 100%; height: 200px;',
addStyle: 'color:red; font-weight: bold;',
}
}
}
</script>
3.4 리스트 랜더링 (v-for)
- 실제 개발하다 보면 다중 데이터를 처리해야 할 일이 자주 발생합니다.
- 주로 많이 사용되는 부분은
select
의option
,table
의tr
데이터 등 반복되는 객체를 처리할 때 입니다. - 여러분이 쇼핑몰에서 보게 되는 제품 리스트처럼 동일한 UI 패턴에 데이터만 다르게 처리되는, 그런 부분들이 다중 데이터를 이용해서 처리되는 부분이라고 생각하시면 됩니다.
- 배열 데이터는
v-for
디렉티브를 이용해서 바인딩할 수 있습니다. - 반복적으로 랜더링 할 때
html
태그에v-for
디렉티브를 사용하면 배열에 있는 데이터 수만큼html
태그를 반복적으로 랜더링하게 됩니다.
- 사용 방법은
v-for="(item, index) in items"
형식으로 사용합니다. - 여기서
items
는 데이터 배열입니다. v-for
를 통해 배열을 하나씩 읽어와서 배열의 각 아이템을item
으로, 배열의 현재index
를index
로 반환해 줍니다.
<template>
<table>
<thead>
<tr>
<th>제품명</th>
<th>가격</th>
<th>카테고리</th>
<th>배송료</th>
</tr>
</thead>
<tbody>
<tr v-for="(product, index) in productList">
<td>{{ product.product_name }}</td>
<td>{{ product.price }}</td>
<td>{{ product.category }}</td>
<td>{{ product.delivery_price }}</td>
</tr>
</tbody>
</table>
</template>
- 전체 코드는 다음과 같습니다.
<template>
<table>
<thead>
<tr>
<th>제품명</th>
<th>가격</th>
<th>카테고리</th>
<th>배송료</th>
</tr>
</thead>
<tbody>
<tr v-for="(product, index) in productList">
<td>{{ product.product_name }}</td>
<td>{{ product.price }}</td>
<td>{{ product.category }}</td>
<td>{{ product.delivery_price }}</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
data() {
return {
productList: [
{"product_name": "기계식 키보드", "price": 25000, "category": "노트북/태블릿", "delivery_price": 5000},
{"product_name": "기계식 키보드", "price": 25000, "category": "노트북/태블릿", "delivery_price": 5000},
{"product_name": "기계식 키보드", "price": 25000, "category": "노트북/태블릿", "delivery_price": 5000},
{"product_name": "기계식 키보드", "price": 25000, "category": "노트북/태블릿", "delivery_price": 5000},
{"product_name": "기계식 키보드", "price": 25000, "category": "노트북/태블릿", "delivery_price": 5000},
]
}
}
}
</script>
<style scoped>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #ddd;
text-align: left;
padding: 8px;
}
</style>
3.5 랜더링 문법 (v-if, v-show)
- Vue 컴포넌트에서조건에 따라 랜더링 하는 방법은
v-if
디렉티브와v-show
디렉티브를 사용하는 방법이 있습니다.
3.5.1 v-if
v-if
디렉티브 표현식은 다음과 같습니다.
<h1 v-if="bRender">bRender가 true이면, h1 블록이 화면에 보이게 됩니다.</h1>
v-if
디렉티브에true
가 리턴이 되면html
블록이 랜더링 됩니다.- 반대로
false
인 경우는 화면에 랜더링되지 않습니다.
v-else
디렉티브를 사용해서else
표현식을 사용할 수 있습니다.
<h1 v-if="bRender">bRender가 true이면, 현재 블록이 화면에 보이게 됩니다.</h1>
<h1 v-else>bRender가 true가 아니면, 현재 블록이 화면에 보이게 됩니다.</h1>
v-else-if
디렉티브를 사용해서else if
표현식을 사용할 수 있습니다.
<template>
<div>
<h1 v-if="type === 'A'">A</h1>
<h1 v-else-if="type === 'B'">B</h1>
<h1 v-else>C</h1>
</div>
</template>
<script>
export default {
data() {
return {
type: 'A'
}
}
}
</script>
3.5.2 v-show
v-show
디렉티브 표현식은 다음과 같습니다.
<h1 v-show="bShow">bShow가 true이면, 현재 블록이 화면에 보이게 됩니다.</h1>
3.5.3 v-if와 v-show의 차이점
v-if
와v-show
는 비슷해 보이지만, 내부적으로 랜더링 되는 방식에 큰 차이가 있습니다.v-if
의 경우 조건을 만족하면 그 순간에html
블록이 생성되고, 조건에 만족하지 않으면html
블록은 삭제가 됩니다.
- 하지만
v-show
의 경우는 조건 만족 여부에 상관없이 무조건html
블록이 생성되며, 조건을 만족하면css
의display
를 이용해서 화면에 보이게 되고, 조건을 만족하지 않으면 화면에서 숨기도록 처리가 됩니다. - 즉, 조건의 만족 여부에 상관없이 무조건 랜더링이 되는 것입니다.
v-if
는 해당 블록에toggle
이 일어날 때,v-show
보다 더 많은 자원을 사용하게 됩니다.- 왜냐하면
v-if
는 실제로 해당 블록 전체를 생성했다가 삭제하기 때문입니다.
v-show
는 조건 만족 여부에 상관없이 일단 무조건 생성된 후 조건에 따라 보였다, 안 보였다 하는 것이기 때문에, 제일 처음에 조건이 만족하지 않더라도html
블록을 무조건 생성한다는 단점이 있습니다.- 즉, 초기에 무조건
html
블록을 생성하는데 자원을 사용하게 되는 것입니다.
Tip
v-if
와 v-show
을 사용할 때는 해당 html
블록이 화면 내에서 자주 toggle
이 일어나면 v-show
을 사용하고, toggle
이 일어나는 빈도가 적다면 v-if
를 사용하는 것이 좋습니다.
3.6 이벤트 처리(v-on)
3.6.1 click 이벤트
v-on:click="메소드명"
@click="메소드명"
@click="메소드명(전달하고싶은파라미터)"
@click="메소드명1(), 메소드명2()"
: 여러 메소드를 호출하고 싶을 때
3.6.2 change 이벤트
change
이벤트가 가장 많이 사용되는HTML
태그는select
- 사용자가
select
에서 옵션을 바꿀 때마다change
이벤트가 발생 @change="메소드명"
3.6.3 Key 이벤트
- 사용자가 키보드 자판을 입력할 때 발생하는 이벤트
- 네이버에 접속을 하면 화면 최상단에 검색창이 있음
- 검색창을 보면 검색조건을 입력하는 입력창이 있고, 그 옆에 조회 버튼이 있음
- 조회조건을 입력한 후 옆에 있는 조회 버튼을 클릭할 수도 있지만, 아마 대다수는 조회 버튼을 클릭하는 것이 아니라, 키보드 자판에서 엔터키를 입력할 것임
- 엔터키를 입력하면 조회 버튼을 클릭한 것과 동일하게 검색이 진행되는 것을 여러분은 아주 잘 알고 있음
- 이때 프로그램 내부적으로 검색조건 입력창에 엔터키가 입력되는지를 계속 감시하다가 엔터키가 입력되면 조회 버튼을 클릭했을 때 호출하는 함수를 동일하게 호출하도록 구현
- 이때 사용되는 것이
key
이벤트
- Vue에서는 사용자로부터 엔터키가 입력되었는지 아주 쉽게 처리할 수 있음
<input type="text" @keyup.enter="submit" />
@keyup.enter
코드만으로Vue
컴포넌트에서는 사용자의 엔터키 입력을 감지할 수 있음- 이외에도
Vue
에서는 다음과 같이 자주 사용되는key
이벤트를 제공함'
.enter
.up
.tab
.down
.delete
(키보드에서 Del키, Backspace키).left
.esc
.right
.space
Control
,Shift
,Alt
키와 같이 다른 키와 같이 사용되는 특수 키에 대해서는 다음과 같이 처리할 수 있음
<!-- Alt + Enter -->
<input type="text" @keyup.alt.enter="clear" />
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
3.7 computed와 watch
computed
와watch
는 둘 다 Vue 인스턴스 내의 정의된 데이터 값에 변경이 일어나는지를 감시하고, 변경될 때마다 정의된 함수가 실행됩니다.- 데이터의 값이 변경이 되었는지를 계속 감시한다는 측면에서
computed
와watch
는 매우 비슷해 보이지만, 사용되는 용도에는 분명 차이가 있습니다.
3.7.1 computed
- 데이터베이스에 사용자 정보를 저장하고 있고, 사용자의 이름을
first name
과last name
으로 구분해서 저장하고 있다고 가정합시다. - 그리고 사용자 정보를 가져와서 보여주는 사용자 프로필 화면을 개발하고 있다고 가정합니다.
- 화면에는 사용자의 이름을
first name
과last name
을 합쳐서 보여줘야 합니다. - 이때 다음과 같은 2가지 방법으로 구현이 가능합니다.
- 첫 번째는 문자열 표현식인 이중 중괄호를 사용해서 2개의 데이터 값을 합쳐서 보여줍니다.
<h1>{{ firstName + ' ' + lastName }}</h1>
<script>
export default {
data() {
return {
firstName: 'Seungwon',
lastName: 'Go',
}
}
}
</script>
- 두 번째는 사용자 이름을 합쳐서 반환하는 함수를 만들고, 함수를 호출해서 보여줍니다.
<h1>{{ getFullName() }}</h1>
<script>
export default {
data() {
return {
firstName: 'Seungwon',
lastName: 'Go',
}
},
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
}
</script>
- 만약에 사용자 이름을 한 곳이 아니라 화면 내 여러 곳에서 보여줘야 한다고 가정하면 첫 번째 방법과 두 번째 방법 모두 데이터 결합과 호출이라는 연산을 화면에 보여주는 수만큼 해야 합니다.
computed
는Vue
인스턴스 내에 정의된 데이터 값과 연관된 또 하나의 데이터를 정의해서 사용할 수 있도록 해줍니다.- 다음 코드를 보겠습니다.
<template>
<h1>Full Name: {{ fullName }}</h1>
</template>
<script>
export default {
data() {
return {
firstName: 'Seungwon',
lastName: 'Go',
}
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
}
</script>
computed
내에fullName
이 정의되어 있고,firstName
과lastName
을 합쳐서 반환해주는 함수로 정의되어 있습니다.- 여기서 함수명인
fullName
은 함수이자 동시에,Vue
인스턴스의 데이터 키 값입니다. - 여러분이
firstName
,lastName
을Vue
인스턴스의 데이터 값으로 사용하는 것과 동일한 데이터 값으로 선언되는 것입니다.
computed
는 데이터 값에 변경이 일어나는지 감시한다고 했습니다.computed
로 정의하면fullName
함수가 실행되어 데이터fullName
에firstName
과lastName
을 합한 값이 할당됩니다.- 그리고
firstName
혹은lastName
값 중 하나라도 변경이 일어나면fullName
함수가 자동으로 실행되고,fullName
값이 갱신됩니다.
- 정리해보면
computed
에 정의된fullName
은 함수이자 동시에Vue
인스턴스의 데이터입니다. computed
에 정의해서 사용하면 화면 내 여러 곳에서fullName
을 사용하더라도 이에 대한 연산은 한번밖에 일어나지 않습니다.- 문자열 표현식인 이중 중괄호 안에서 2개의 데이터 값을 합하는 연산과 함수로 구현된 연산이 화면에 보이는 수만큼 호출되어 연산을 다시 하는 것과 큰 차이가 있습니다.
- 또한, 함수를 이용해서
fullName
을 계산하는 경우는firstName
혹은lastName
에 변경이 일어났을 때를 감지할 수 없습니다.
3.7.2 watch
watch
역시computed
처럼Vue
인스턴스에 정의된 데이터 값이 변경이 일어나는지를 감시하고, 변경이 일어나면 지정된 함수를 실행시킬 수 있습니다.- 하지만
computed
의 경우는 기존에 정의된 데이터 값을 기반으로 새로운 데이터 값을 활용하기 위해서 사용이 된다면 watch
는watch
에 정의된 데이터 값 하나만을 감시하기 위한 용도로 사용됩니다.
- 또한
watch
의 경우는computed
와 다르게 실제 데이터 변경이 일어나기 전까지는 실행되지 않습니다. - 즉, 초기에 지정된 값인
firstName
과lastName
에 값이 있음에도 불구하고fullName
은 여전히 아무런 값도 할당되지 않습니다. firstName
과lastName
의 초기에 할당된 값이 반드시 변경이 일어나야만watch
가 실행됩니다.
Note
watch
의 immediate
옵션이 있잖아?
watch
를 이용해서computed
를 설명할 때 사용한 예제 코드를 구현하겠습니다.
<template>
<h1>Full Name: {{ fullName }}</h1>
</template>
<script>
export default {
data() {
return {
firstName: 'Seungwon',
lastName: 'Go',
fullName: '',
}
},
watch: {
firstName() {
this.fullName = this.firstName + ' ' + this.lastName;
},
lastName() {
this.fullName = this.firstName + ' ' + this.lastName;
},
}
}
</script>
- 위와 같이 작성한 상태에서 코드를 실행하면 화면에
fullName
이 출력되지 않습니다. firstName
또는lastName
값에 변화가 있어야fullName
이 갱신되면서 출력됩니다.
- 이와 같이
computed
는 정의된 데이터 값을 바탕으로 새로운 데이터 값을 생성하고, 새로운 데이터 값에서 참조하고 있는 기존 데이터 값의 변경을 감지합니다. - 그리고 참조하고 있는 데이터 값의 변경과 상관없이 최초에
computed
에 정의된 데이터 함수를 실행합니다.
watch
는 초기에 할당된 값에서 변경이 일어나야watch
에 정의한 함수를 실행하는 차이가 있습니다.
Note
immediate
옵션 사용하면 이를 해결할 수 있어!