v-for
v-for는 VueJS에서 배열 데이터에 화면을 연동하여 반복 처리할 수 있도록 하는 지시자이다.
N회차 반복 또는 특정 저장소에 대한 Iteration이 가능하다.
사용 예제 : 10회 반복
Copy <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue for loop</title>
</head>
<body>
<div id="app">
<h1 v-for="n in 10">Vue for loop {{n}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data(){
return {
};
}
});
app.mount("#app");
</script>
</body>
</html>
위 예제는 특정 태그를 원하는 횟수(10회) 만큼 반복하는 예제이다.
Copy <h1 v-for="n in 10">Vue for loop {{n}}</h1>
태그에 v-for 지시자를 작성하고 n in 10
이라고 작성하면 1부터 10까지 반복이 이루어진다. 현재 반복의 n값은 내부에서 출력이 가능하다.
사용 예제 : 배열에 대한 Iteration
Copy <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue for loop</title>
</head>
<body>
<div id="app">
<input type="text" v-for="data in dataList" v-model="data">
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data(){
return {
dataList:[10, 20, 30, 40, 50],
};
}
});
app.mount("#app");
</script>
</body>
</html>
위 예제를 실행하면 다섯 개의 입력창이 나오고 데이터가 잘 설정된다. 하지만 Vue에서 권장하는 형태의 v-for 사용법은 아니다. v-for를 사용할 때는 다음에 유의해야 한다.
반복 요소에 대한 분리
v-for를 사용한 태그에는 반복과 관련된 요소 외의 항목들을 사용하지 않아야 한다.
예제에서 문제가 되는 부분은 다음과 같다.
Copy <input type="text" v-for="data in dataList" v-model="data">
input 태그에 v-for와 v-model이 같이 사용되고 있다. 그 외에도 v-if, v-show, v-bind 등 다양한 항목들이 v-for와 함께 사용되는 경우가 있는데 실행 순서에 따라 오작동이 발생할 수 있으므로 사용하지 않는 것이 좋다. 다음과 같이 구분되어 사용하는 것이 바람직하다.
Copy <div v-for="data in dataList">
<input type="text" v-model="data">
</div>
index 사용
v-for 구문 작성 시 제공되는 index를 사용할 수 있다. index는 다음과 같이 데이터 뒤에 작성하면 자동으로 할당되며 0부터 시작하여 1씩 증가한다. 배열과 관련된 다양한 처리에서 활용할 수 있다.
Copy <div v-for="(data, index) in dataList">
{{index}} : <input type="text" v-model="data">
</div>
배열 내부에서의 v-model 사용
Copy <div v-for="(data, index)in dataList">
<input type="text" v-model="data">
</div>
배열 내부에 입력창이 존재하며 배열의 요소와 v-model로 연결해야 할 경우가 발생한다면 다음을 유의해야 한다.
iteration된 항목인 data를 model로 사용하면 단방향 연결되어 입력창 수정 시 데이터에 반영되지 않는다.
index를 사용하여 원본 데이터인 dataList[i]를 model로 설정하면 양방향 연결되어 입력창 수정 시 데이터에 반영된다.
입력창을 수정할 때 원본인 dataList가 변경되게 하고 싶다면 다음과 같이 v-model을 사용해야 한다.
Copy <div v-for="(data, index) in dataList">
{{index}} : <input type="text" v-model="dataList[index]">
</div>
v-bind:key의 사용
Vue에서 개별 DOM 노드들을 추적하고 기존 요소들을 재사용하기 위해서는 항목들에 고유한 key를 설정해야 한다. 가급적 고유한 항목으로 설정해야 하며, 배열이나 객체로 설정하지 말아야 한다. 설정하지 않을 경우 Vue에서 제공하는 기본 성능에 의존하게 된다. v-bind:key
또는 약식 명령인 :key
로 설정한다.
key로 설정할 항목이 애매하다면 제공되는 index를 key로 사용하면 된다
key가 적용될 경우 다음과 같이 변화한다.
Copy <div v-for="(data, index) in dataList" v-bind:key="index">
{{index}} : <input type="text" v-model="dataList[index]">
</div>
Vue cli 환경에서는 v-for에 :key가 설정되지 않을 경우 구문 에러가 발생하므로 가급적 모든 경우에 작성하도록 해야한다.
배열 변경
v-for에 연결된 데이터가 변할 경우 화면도 변한다. 따라서 배열에 데이터가 늘어나거나 줄어들게 되면 연결된 화면의 컴포넌트 개수에도 변화가 생긴다.
Vue에서는 배열의 변이 메소드를 사용하면 자동으로 모든 연결이 갱신되도록 처리한다. 다음 메소드 중에서 사용이 가능하다.
데이터 추가
데이터를 추가하기 위한 추가 입력창과 추가 데이터를 구현해야 한다.
html
Copy <input v-model="inputData">
<button v-on:click="addData">추가</button>
vue
Copy const app = Vue .createApp ({
data (){
return {
inputData : "" , //이 부분을 추
dataList : [ 10 , 20 , 30 , 40 , 50 ] ,
};
} ,
methods : {
//등록 메소드
addData (){
if ( ! this .inputData) return ;
this . dataList .push ( this .inputData);
this .inputData = "" ;
} ,
} ,
});
app .mount ( "#app" );
데이터를 작성하고 추가 버튼을 누르면 데이터가 존재한다는 가정 하에 배열에 데이터가 추가되며 입력창의 변화가 발생하는 것을 확인할 수 있다.
데이터 삭제
데이터를 삭제하려면 해당 데이터의 index가 필요하다.
index를 알고 있을 경우 다음 명령을 통해 삭제가 가능하다.
Copy array .splice (index , range);
한 개만 지울 예정이므로 range는 1이 되어 다음과 같은 코드가 된다.
Copy array .splice (index , 1 );
html
반복 내부의 입력창 우측에 삭제 버튼을 추가한다.
Copy <div v-for="(data, index) in dataList" v-bind:key="index">
{{index}} : <input type="text" v-model="dataList[index]">
<button v-on:click="removeData(index);">삭제</button>
</div>
vue
methods에 삭제 기능인 removeData를 추가한다. 등록과 다르게 index가 전달되므로 매개변수에 선언해야 한다.
Copy const app = Vue .createApp ({
data (){
return {
inputData : "" ,
dataList : [ 10 , 20 , 30 , 40 , 50 ] ,
};
} ,
methods : {
//등록 메소드
addData (){
if ( ! this .inputData) return ;
this . dataList .push ( this .inputData);
this .inputData = "" ;
} ,
//삭제 메소
removeData (index){
this . dataList .splice (index , 1 );
} ,
} ,
});
app .mount ( "#app" );
실행 후 삭제 버튼을 눌러 정상적으로 삭제되는 것을 확인할 수 있다.
데이터 정렬
데이터 정렬은 sort 메소드를 사용한다. sort 메소드 내부에 comparator를 정의하여 정렬방식을 지정할 수 있다.
html
반복 영역 하단에 정렬 버튼을 추가한다.
Copy <button v-on:click="sortData">정렬</button>
vue
버튼을 클릭하면 실행할 정렬 메소드를 만들고 sort 명령을 호출한다.
필요한 방식에 맞게 명령을 사용하여 원하는 결과가 나올 수 있도록 한다.
Copy const app = Vue .createApp ({
data (){
return {
inputData : "" ,
dataList : [ 10 , 20 , 30 , 40 , 50 ] ,
};
} ,
methods : {
//등록 메소드
addData (){
if ( ! this .inputData) return ;
this . dataList .push ( this .inputData);
this .inputData = "" ;
} ,
//삭제 메소드
removeData (index){
this . dataList .splice (index , 1 );
} ,
//정렬 메소드
sortData (){
// this.dataList.sort();//오름차순(기본)
this . dataList .sort ((a , b) => b - a); //내림차순
} ,
} ,
});
app .mount ( "#app" );
숫자 배열 최종 코드
Copy <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue for loop</title>
</head>
<body>
<div id="app">
<input v-model="inputData">
<button v-on:click="addData">추가</button>
<div v-for="(data, index) in dataList" v-bind:key="index">
{{index}} : <input type="text" v-model="dataList[index]">
<button v-on:click="removeData(index);">삭제</button>
</div>
<button v-on:click="sortData">정렬</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data(){
return {
inputData:"",
dataList:[10, 20, 30, 40, 50],
};
},
methods:{
//등록 메소드
addData(){
if(!this.inputData) return;
this.dataList.push(this.inputData);
this.inputData = "";
},
//삭제 메소드
removeData(index){
this.dataList.splice(index, 1);
},
//정렬 메소드
sortData(){
// this.dataList.sort();//오름차순(기본)
this.dataList.sort((a,b)=>b-a);//내림차순
},
},
});
app.mount("#app");
</script>
</body>
</html>
객체 배열의 v-for
객체 배열을 이용하여 이전 예제와 동일한 작업을 수행한 결과 코드는 다음과 같다.
동일하게 등록, 삭제, 정렬 기능이 존재한다.
Copy <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue for loop</title>
</head>
<body>
<div id="app">
<input type="text" v-model="inputData.name">
<input type="number" v-model.number="inputData.score">
<button v-on:click="addData">등록</button>
<div v-for="(data, index) in dataList" v-bind:key="index">
순서 : {{index}},
이름 : <input type="text" v-model="data.name">,
점수 : <input type="number" v-model.number="data.score">점
<button v-on:click="removeData(index);">삭제</button>
</div>
<button v-on:click="sortDataAsc">오름차순</button>
<button v-on:click="sortDataDesc">내림차순</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data(){
return {
inputData:{
name:"",
score:0,
},
dataList:[
{name:"피카츄", score:80},
{name:"라이츄", score:70},
{name:"파이리", score:60},
{name:"꼬부기", score:80},
{name:"버터플", score:65}
],
};
},
methods:{
addData(){
if(this.inputData.name && this.inputData.score >= 0 && this.inputData.score <= 100) {
this.dataList.push(Object.assign({}, this.inputData));
this.inputData.name = "";
this.inputData.score = 0;
}
},
removeData(index){
this.dataList.splice(index, 1);
},
sortDataAsc(){
this.dataList.sort((a,b)=>a.score-b.score);
},
sortDataDesc(){
this.dataList.sort((a,b)=>b.score-a.score);
},
},
});
app.mount("#app");
</script>
</body>
</html>
객체 기능 추가
객체에 메소드를 더 추가하여 구현한 코드는 다음과 같다.
Copy <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue for loop</title>
</head>
<body>
<div id="app">
<input type="text" v-model="inputData.name">
<input type="number" v-model.number="inputData.score">
<button v-on:click="addData">등록</button>
<div v-for="(data, index) in dataList" v-bind:key="index">
순서 : {{index}},
이름 : <input type="text" v-model="data.name">,
점수 : <input type="number" v-model.number="data.score">점
<button v-on:click="removeData(index);">삭제</button>
</div>
<button v-on:click="sortDataAsc">오름차순</button>
<button v-on:click="sortDataDesc">내림차순</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data(){
return {
inputData:{
name:"",
score:0,
clear(){
this.name = "";
this.score = 0;
},
get isNameValid(){
return this.name.length > 0;
},
get isScoreValid(){
return this.score >= 0 && this.score <= 100;
},
get isDataValid(){
return this.isNameValid && this.isScoreValid;
},
get copy(){
return Object.assign({}, this);
},
},
dataList:[
{name:"피카츄", score:80},
{name:"라이츄", score:70},
{name:"파이리", score:60},
{name:"꼬부기", score:80},
{name:"버터플", score:65}
],
};
},
methods:{
addData(){
if(this.inputData.isDataValid) {
this.dataList.push(this.inputData.copy);
this.inputData.clear();
}
},
removeData(index){
this.dataList.splice(index, 1);
},
sortDataAsc(){
this.dataList.sort((a,b)=>a.score-b.score);
},
sortDataDesc(){
this.dataList.sort((a,b)=>b.score-a.score);
},
},
});
app.mount("#app");
</script>
</body>
</html>