v-model 을 이용해서 체크박스를 만들어보는 과제를 받았다. v-model 에 대한 개념을 잘 파악한 뒤, 우여곡절 끝에 체크박스를 구현하는데 성공했다. 그리고 여기서 그치지 않고 전체로 체크되는 체크박스도 한 번 구현해보았다.
props
를, 자식에서 부모 컴포넌트로 데이터를 보낼땐 $emit
을 사용한다.method
는 부모가 아닌 자식 컴포넌트에서 실행되도록 하고 자식 컴포넌트는 부모에 데이터만 건네준다. (부모쪽에서 실행하면 자식 컴포넌트 하나를 부모에 달때마다 method
를 부모에 하나씩 계속 추가해줘야하기 때문에 비효율적임) 즉 자식 컴포넌트에서 부모 컴포넌트에 이벤트까지 넘겨주는 것은 지양한다.v-model
은 부모 컴포넌트의 value 로 들어감v-for
문을 돌려 여러 개로 생성// index.vue
<template>
<div id="checkbox">
<h1>Checkbox</h1>
// 전체 체크박스
<checkboxAll v-model="checkedNames" label="전체 선택" :name-list="nameList" />
// 일반 체크박스(list)
<ul id="checkbox">
<li v-for="(name, index) in nameList" :key="name">
<Checkbox
v-model="checkedNames"
label="체크박스"
:name="name"
:index="index" />
// checkName() 이 실행되고 v-model 을 통해 checkName() 의 결과값인 arr 배열데이터가 checkedNames 에 들어감
</li>
</ul>
<span>{{ checkedNames }}</span>
</div>
</template>
<script>
import checkbox from '../components/checkbox.vue'
export default {
data() {
return {
nameList: ['Jack','John','Mike'],
// label 은 보통 한글로 쓰이기 때문에(그거야 한국이니까...)
// 저렇게 문자열로만 적는 것이 아닌, 한글로 표기될 label 용 따로,
// 서버로 실제로 옮겨지는 데이터 value 값용 따로 설정해서 배열의
// 요소를 하나의 객체데이터로 만들어놓는 것이 좋다.
// nameList: [
// {label: '잭', value: 'Jack'},
// {label: '존', value: 'John'},
// {label: '마이크', value: 'Mike'},
// ],
checkedNames: []
}
},
component: {
checkbox
}
}
</script>
@input
이 아닌 @change
로 하여 change 이벤트를 통해 요소 변경이 끝나면 이벤트가 발생하도록 함text
: 포커스를 잃을 때 이벤트가 발생select
, checkbox
, radio
: 선택 값이 변경된 직후에 이벤트가 발생isChecked
가 true 값이 되며, 이러한 체크 여부 변수를 통해 클래스 바인딩 실행 혹은 checked 유무를 표현할 수 있다.// checkbox.vue
<template>
<label :for="name">
<input
:id="name"
:class="{'active': isChecked}"
:checked="isChecked"
type="checkbox"
:value="name"
@change="checkName" />
// true 나 false 를 반환해서 클래스 바인딩을 통해 체크된(active) input 디자인 표현 -->
// <em></em>{{ checkedNames.some(e => e === name) }}-->
// checkedNames.some(e => e === name) : boolean 반환, class 유무가 정해짐-->
<em></em>
<span>{{ label }}</span>
<span>{{ index + 1 }}</span>
<span>{{ name }}</span>
</label>
</template>
<script>
export default {
name: 'Checkbox',
// v-model 은 value 값을 받는다
model: {
prop: 'checkedNames',
event: 'changeForm'
},
props: {
// prop 으로 받은 것은 props 옵션에도 따로 기재해야함
checkedNames: {
type: Array
},
label: {
type: String,
default: '',
},
name: {
type: String,
required: true,
},
index: {
type: Number,
required: true,
}
},
computed: {
isChecked() {
return this.checkedNames.includes(this.name)
}
},
methods: {
checkName(e) {
let arr = [...this.checkedNames]
// 자식 컴포넌트에서 데이터를 함부로 직접 바꾸면 안되기 때문에 전개연산자로
// 얕은 복사를 한 뒤 변수에 담고 해당 데이터를 가공한 뒤 다시 $emit 으로 전달
if (arr.includes(e.target.value)) {
arr = arr.filter(name => name !== e.target.value)
} else {
arr.push(e.target.value)
}
// 가공된 데이터 다시 전달
this.$emit('changeForm', arr)
}
}
}
</script>
<style>
...
</style>
자식 컴포넌트에서 value 는 그냥 value 이며, 반복문으로 생성된 여러 input 의 value 들 중 event.target
을 통해 추출된 value 값은 [checkedNames
를 얕게 복사한 arr 라는 배열]에 추가 혹은 삭제된 후 새로 수정된 해당 배열(arr)이 다시 부모 컴포넌트의 v-model
(value)로 전달됨
변경된 데이터가 부모에 반영되고, 해당 데이터가 또 다시 자식으로 넘어가 수정 반복 ⇒ 양방향 데이터 바인딩
// checkboxAll.vue
<template>
<label for="chkAll">
<input
id="chkAll"
:class="{'active': isChecked}"
:checked="isChecked"
type="checkbox"
value="chkAll"
@change="checkFormAll"
>
<em></em>
<span>{{ label }}</span>
</label>
</template>
<script>
export default {
name: 'CheckboxAll',
model: {
prop: 'checkedNames',
event: 'changeFormAll',
},
props: {
checkedNames: {
type: Array,
},
label: {
type: String,
default: '',
},
nameList: {
type: Array,
}
},
data() {
return {
}
},
computed: {
isChecked() {
return this.checkedNames.length === this.nameList.length
}
},
methods: {
// 이미 모든 요소가 체크되어 있을 때: 체크된 모든 요소 해제
// 모든 요소가 체크되어있지 않을 때: 모든 요소 체크
checkFormAll() {
let arr1 = [...this.checkedNames]
const arr2 = [...this.nameList]
let union = []
if (arr1.length === arr2.length) {
arr1 = []
union = []
} else if (arr2.length > arr1.length) {
const sum = arr1.concat(arr2)
union = sum.filter((item, index) => sum.indexOf(item) === index)
}
this.$emit('changeFormAll', union)
}
},
}
</script>
결과