View, Data, Code의 세트
컴포넌트는 재사용이 가능 → 다른 컴포넌트에 import 해서 사용
src/components
src/views
두가지 모두 vue 입장에서는 컴포넌트이고 내부적으로 동일한 구조를 가지나, 프로젝트 관리 차원에서 물리적으로 분리한다
vue 코드의 기본 구조를 만들고, snippet로 등록한다
vue 컴포넌트에는 name, components, data, computed
같은 기본 프로퍼티 외에도 라이프사이클 훅에 해당하는 메소드 등이 있다
이 중 자주 사용하게 되는 기본 코드 구조를 snippet에 등록한다
<template>
<div></div>
</template>
<script>
export default {
name: "",
components: {},
data() {
return {
sampleData: "",
};
},
setup() {},
created() {},
mounted() {},
unmounted() {},
methods() {},
};
</script>
<template>
: View에 해당하는 html 코드 작성name
: 컴포넌트 이름components
: 외부 컴포넌트를 사용하게 되면 해당 컴포넌트를 import 한 후, 이곳에 배열로 등록data
:setup
: 컴포지션 API 구현created
: 컴포넌트 생성 시 실행mounted
: 템플릿에 작성한 HTML 코드가 랜더링 된 후 실행unmounted
: 컴포넌트를 빠져나갈 때 실행methods
: 컴포넌트 내에서 사용할 메소드, this를 통해서 접근vue.json : Code → Preference → User Snippets → vue(Vue) 선택
{
// Place your snippets for vue here. Each snippet is defined under a snippet name and has a prefix, body and
// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
// same ids are connected.
// Example:
// "Print to console": {
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Generate Basic Vue Code": {
"prefix": "vue-start",
"body": [
"<template>\n<div></div>\n</template>\n\n<script>\nexport default{ \n\tname:'',\n\tcomponents:{},\n\tdata(){\n\t\treturn{\n\t\t\tsampleData:''\n\t\t};\n\t},\n\tsetup(){},\n\tcreated(){},\n\tmounted(){},\n\tunmounted(){},\n\tmethods:{}\n}\n</script>"
],
"description": "Generate Basic Vue Code"
}
}
에디터에 vue-start
를 쳐서 템플릿이 생성되는지 확인한다
모든 컴포넌트는 생성될 때 초기화 단계를 거친다
라이프사이클 훅에 따라 프로그램을 적절히 배치하면 화면 로딩시간을 개선하고 사용자 체감 속도를 높일 수 있다
사용자가 특정 화면 접속 시, 가장 먼저 보여줘야하는 데이터 영역은 created()
에, 화면 로딩 이후에 삽입되어도 되는 데이터는 mounted()
훅에 정의해 타이밍을 적절히 분배한다
단방향 데이터만 지원하는 React와 다르게, Vue는 Angular와 같이 양방향 데이터 바인딩(Two-way data binding)을 지원한다
실무에서 서버로부터 받아온 데이터를 바인딩하는 경우는 다음과 같다
Vue 컴포넌트에서 데이터가 바인딩 되는 유형에 따라 적용 방식에 차이가 있다. 지금부터 하나하나 알아보자!
src/views/DataBinding.vue
<template>
<h1>Hello, {{ title }}</h1>
</template>
<script>
export default {
data() {
return {
title: "World",
};
},
};
</script>
{{ title }}
: Vue 컴포넌트에서 data에 정의되는 데이터를 이중 중괄호를 이용해 html에 바인딩src/router/index.js
{
path: "/databinding",
name: "DataBinding",
component: DataBinding,
},
routes에 위 루트를 추가한다
src/App.vue
<router-link to="/">Home</router-link> |
<router-link to="/databinding">DataBinding</router-link>
문자열은 앞서 실행한 것처럼 이중 중과라호를 이용해 데이터를 바인딩한다
<h1>Hello, {{ title }}</h1>
HTML 태그를 바인딩 할때 이중 중괄호를 쓰면 텍스트로 취급하므로 v-html
디렉티브를 이용한다
src/views/DataBindingHtml.vue
<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>
웹 페이지에서 사용자로부터 데이터를 입력받을 수 있는 필드를 Form Element라고 한다
v-model
디렉티브를 사용하여 양방향 데이터 바인딩을 생성할 수 있다
사용자로부터 텍스트를 입력받는 input type=text의 경우, 입력받은 텍스트는 value에 저장이 된다
v-model
은 내부적으로 value 속성을 사용한다
data()
에 정의된 데이터 키 명을 v-model에 넣어주면 data(Model)와 input의 value 속성(View) 사이에 양방향 데이터 바인딩이 설정된다
src/views/DataBindingInputText.vue
<template>
<div>
<input type="text" v-model="valueModel" />
</div>
</template>
<script>
export default {
data() {
return {
valueModel: "South Korea",
};
},
};
</script>
data
에서 뷰인 input 객체
로 단방향 데이터 바인딩이 된 것처럼 보이지만, 실제로는 input의 데이터가 변경되면 data의 valueModel의 값도 변경된다사용자의 입력 값이 문자가 아닌 숫자로 바로 처리되도록 v-model.number
디렉티브를 사용할 수 있다
src/views/DataBindingInputNumber.vue
<template>
<div>
<input type="number" v-model.number="numberModel" />
</div>
</template>
<script>
export default {
data() {
return {
numberModel: 1,
};
},
};
</script>
<textarea> v-model=""</textarea>
src/views/DataBindingTextarea.vue
<template>
<div>
<textarea v-model="message"></textarea>
</div>
</template>
<script>
export default {
data() {
return {
message: "여러 줄을 입력할 수 있는 textarea 입니다",
};
},
};
</script>
input type=text와 마찬가기로 v-model
은 내부적으로 value 속성을 사용하여 양방향 데이터 바인딩을 한다
src/views/DataBindingSelect.vue
<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: "02",
};
},
};
</script>
input type=text, select와는 다르게 v-model
은 checked 속성 사용
value 속성에 바인딩 하려면 v-bind:value
를 사용
src/views/DataBindingCheckbox.vue
<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: "02",
};
},
};
</script>
여러개의 체크박스를 사용할 경우 배열을 사용해 데이터를 바인딩하여 한번에 처리할 수 있다
src/views/DataBindingCheckbox2.vue
<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>
라디오 역시 체크박스와 마찬가지로 v-model
은 내부적으로 checked 속성과 바인딩된다
value 속성에 바인딩 하려면 v-bind:value
를 사용
라디오에서 체크를 하게 되면 체크된 v-bind:value
에 연결된다
src/views/DataBindingRadio.vue
<template>
<div>
<label
><input type="radio" v-bind:value="radioValue1" v-model="picked" />서울
</label>
<label
><input type="radio" v-bind:value="radioValue2" v-model="picked" />부산
</label>
<label
><input type="radio" v-bind:value="radioValue3" v-model="picked" />제주
</label>
<br />
<span>선택한 지역 : {{ picked }}</span>
</div>
</template>
<script>
export default {
data() {
return {
picked: "",
radioValue1: "서울",
radioValue2: "부산",
radioValue3: "제주",
};
},
};
</script>
value를 제외한 HTML 객체의 속성에 데이터를 바인딩하기 위해선 v-bind:
디렉티브를 사용한다 (v-bind
를 생략하고 그냥 :
로 사용 가능)
이미지 주소를 img 객체의 src 속성에 바인딩한다
src/views/DataBindingAttribute.vue
<template>
<div>
<img v-bind:src="imgSrc" />
</div>
</template>
<script>
export default {
data() {
return {
imgSrc: "https://kr.vuejs.org/images/logo.png",
};
},
};
</script>
버튼에서 disabled 속성에 따라 활성화/비활성화 여부가 결정된다
화면에서 조회 조건 중 필수 입력 조건이 모두 입력이 되었을 때 등 disabled 속성을 이용하면 더 좋은 UX를 제공할 수 있다
src/views/DataBindingButton.vue
<template>
<div>
<input type="text" v-model="textValue" />
<button type="button" v-bind:disabled="textValue == ''">Click</button>
</div>
</template>
<script>
export default {
data() {
return {
textValue: "",
};
},
};
</script>
v-bind:disabled="textValue == ''"
: textValue가 비어있을 경우엔 비활성화반드시 적용해야하는 클래스는 기존 html에서 사용하던 방식대로 class
속성에 클래스명을 입력하면 된다
조건에 따라 바인딩할 클래스는 v-bind:class
를 이용해서 추가적으로 정의해서 사용한다
즉, 다른 속성의 경우 하나의 속성만을 이용해서 바인딩 해야하지만 클래스의 경우는 기본 클래스와 바인딩 처리를 하는 클래스를 공존해서 사용할 수 있다
바인딩할 클래스를 Key, 바인딩 여부를 true/false의 Value로 오브젝트 형태를 사용한다
src/views/DataBindingClass.vue
<template>
<div
class="container"
v-bind: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>
배열을 사용해 클래스를 바인딩할 경우 특정 조건에 따른 바인딩 처리를 true/false로 할 수는 없다
src/views/DataBindingClass2.vue
<template>
<div class="container" v-bind:class="[activeClass, errorClass]">
Class Binding
</div>
</template>
<script>
export default {
data() {
return {
activeClass: "active",
errorClass: "text-red",
};
},
};
</script>
<style scoped>
container {
width: 100%;
height: 200px;
}
.active {
background-color: yellow;
font-weight: bold;
}
.text-red {
color: red;
}
</style>
인라인 스타일은 데이터를 오브젝트로 선언해서 바인딩한다
src/views/DataBindingStyle.vue
<template>
<div v-bind:style="styleObject">인라인 스타일 바인딩</div>
</template>
<script>
export default {
data() {
return {
styleObject: {
color: "red",
fontSize: "13px",
},
};
},
};
</script>
클래스 바인딩과 마찬가지로 배열을 이용해서 바인딩 할 수 있다
src/views/DataBindingStyle2.vue
<template>
<div v-bind: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>
서비스 개발 시 다중 데이터를 처리해야 할 일이 자주 발생한다
배열 데이터는 v-for
디렉티브를 이용해서 바인딩한다
반복적으로 랜더링 할 html 태그에 v-for 디렉티브를 사용하면 배열에 있는 데이터 수 만큼 html 태그를 반복적으로 랜더링 한다
v-for="(item, index) in items"
src/views/DataBindingList.vue
<template>
<div>
<table>
<thead>
<tr>
<th>제품명</th>
<th>가격</th>
<th>카테고리</th>
<th>배송료</th>
</tr>
</thead>
<tbody>
<tr v-bind:key="i" v-for="(product, i) in productList">
<td>{{ product.product_name }}</td>
<td>{{ product.price }}</td>
<td>{{ product.category }}</td>
<td>{{ product.delivery_price }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data() {
return {
productList: [
{
product_name: "기계식키보드",
price: 25000,
category: "노트북/태블릿",
delivery_price: 5000,
},
{
product_name: "무선마우스",
price: 12000,
category: "노트북/태블릿",
delivery_price: 5000,
},
{
product_name: "아이패드",
price: 725000,
category: "노트북/태블릿",
delivery_price: 5000,
},
{
product_name: "태블릿거치대",
price: 32000,
category: "노트북/태블릿",
delivery_price: 5000,
},
{
product_name: "무선충전기",
price: 42000,
category: "노트북/태블릿",
delivery_price: 5000,
},
],
};
},
};
</script>
<style scoped>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
</style>
Vue 컴포넌트에서 조건에 따라 랜더링을 하는 방법은 v-if, v-show
디렉티브를 사용하는 것이다
v-if, v-else
를 사용해 조건에 따라 html 블록을 생성한다
<div v-if="isRender">isRender가 true이면 html 블록이 생성됩니다</div>
<div v-else>isRender가 false이면 html 블록이 생성됩니다</div>
src/views/RenderingVIf.vue
<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>
v-show
를 사용해 해당 html 블록의 보임/숨김 처리를 할 수 있다
<div v-show="isShow">isShow가 true이면 현재 블록이 화면에 보입니다.</div>
v-if
와 v-show
는 비슷해 보이지만, 내부적으로 랜더링 되는 방식에는 큰 차이가 있다
v-if
: 조건을 만족하면 그 순간에 html 블록이 생성되고, 조건에 만족하지 않으면 html 블록은 삭제된다v-show
: 조건 만족 여부에 상관없이 무조건 html 블록이 생성되고 조건을 만족하면 css의 display를 이용해서 보임/숨김 처리만 된다html 블록에 토글이 자주 일어난다면 v-show가 더 적합하나, 조건에 관계없이 처음에 무조건 html 블록이 생성된다는 단점이 있다
Vue 컴포넌트에서 이벤트를 처리할 때는 v-on
을 사용하며 심볼 @
로 사용도 가능하다
src/views/EventClick.vue
<template>
<div>
<button type="button" @click="increaseCounter">Add</button>
<p>The counter is : {{ counter }}</p>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
};
},
methods: {
increaseCounter: function () {
this.counter += 1;
},
},
};
</script>
클릭 이벤트를 통해 지정된 함수로 파라미터를 전달할 수 있다
<template>
<div>
<button type="button" @click="increaseCounter(3)">Add</button>
<p>The counter is : {{ counter }}</p>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
};
},
methods: {
increaseCounter: function (counter) {
this.counter = counter;
},
},
};
</script>
클릭 이벤트 발생 시 여러 함수를 호출할 수 있다
<template>
<div>
<button type="button" @click="one(), two()">Alert</button>
</div>
</template>
<script>
export default {
methods: {
one: function () {
alert("one");
},
two: function () {
alert("two");
},
},
};
</script>
select 태그에서 사용자가 옵션을 바꿀 때 change 이벤트가 발생한다
src/views/EventChange.vue
<template>
<div>
<select v-model="selectedValue" @change="changeSelect">
<option value="서울">서울</option>
<option value="부산">부산</option>
<option value="제주">제주</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
selectedValue: "",
};
},
methods: {
changeSelect: function () {
alert(this.selectedValue);
},
},
};
</script>
사용자의 키보드 입력 이벤트를 다룰땐 v-on:keyup.xxx=""
를 사용한다
검색창에 검색어를 입력한 후 엔터를 쳤을때 submit을 하고 싶다면 아래와 같이 사용한다
<input @keyup.enter="submit" />
enter
와 같이 Vue에서는 자주 사용되는 Kye 이벤트를 제공한다
.enter
.tab
.delete
.esc
.spcae
.up
.down
.left
.right
control, shift, alt와 같이 다른 키와 함께 사용되는 특수 키에 대해선 다음과 같이 처리할 수 있다
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Click + Ctrl-->
<input @click.ctrl="doSomeThing" />
computed
와 watch
는 둘 다 Vue 인스턴스 내에 정의된 데이터 값에 변경이 일어나는지를 감시하고, 변경될 때마다 정의된 함수를 실행한다
DB에서 사용자의 이름을 first name과 last name으로 구분해서 저장하고 있고, 화면에서 이를 합쳐서 보여줘야 한다면 다음 두가지 방법을 사용할 수 있다
<template>
<div>
<h1>{{ firstName + " " + lastName }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
firstName: "Jungmin",
lastName: "Kim",
};
},
};
</script>
<template>
<div>
<h1>{{ getFullName() }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
firstName: "Jungmin",
lastName: "Kim",
};
},
methods: {
getFullName: function () {
return this.firstName + " " + this.lastName;
},
},
};
</script>
만약 사용자 이름을 여러 곳에서 보여줘야 한다면 이런 연산은 매번 일어날 것이다
computed
는 Vue 인스턴스 내에 정의된 데이터 값과 연관된 또 하나의 데이터를 정의해서 사용할 수 있도록 해준다
src/views/Computed.vue
<template>
<div>
<h1>Full Name: {{ fullName }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
firstName: "Jungmin",
lastName: "Kim",
};
},
computed: {
fullName() {
return this.firstName + "" + this.lastName;
},
},
};
</script>
computed는 데이터 값에 변경이 일어나는지 감시하고, firstName 또는 lastName 값 중 하나라도 변경이 일어나면 fullName 함수를 자동으로 실행하고, fullName 값이 갱신된다
computed에 정의된 fullName은 함수이자 동시에 Vue 인스턴스의 데이터이다
화면 내 여러곳에서 fullName을 사용해도 이에 대한 연산은 한 번밖에 일어나지 않는다
watch
역시 computed
처럼 Vue 인스턴스에 정의된 데이터 값의 변경을 감시하고, 변경이 일어나면 지정된 함수를 실행하지만 아래와 같은 차이점이 있다
computed
: 기존에 정의된 데이터 값을 기반으로 새로운 데이터 값을 활용하기 위해서 사용watch
: watch에 정의된 데이터 값 하나만을 감시하기 위한 용도이다또한 watch의 경우 computed와 다르게 실제 데이터 변경이 일어나기 전까지는 실행되지 않는다
즉, 초기에 지정된 값인 firstName, lastName에 값이 있음에도 불구하고 아직 한번의 변경도 일어나지 않았기 때문에 fullName에는 아무런 값이 할당되지 않는다
src/views/Watch.vue
<template>
<div>
<h1>Full Name: {{ fullName }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
firstName: "Jungmin",
lastName: "Kim",
fullName: "",
};
},
watch: {
firstName() {
this.fullName = this.firstName + " " + this.lastName;
},
lastName() {
this.fullName = this.firstName + " " + this.lastName;
},
},
};
</script>
정리하자면,
computed
watch
출처: 고승원 저, 『Vue.js 프로젝트 투입 일주일 전』, 비제이퍼블릭(2021)