부모 컴포넌트 : vote-view
자식 컴포넌트 :
선택지 보여주는 option-selector
결과 보여주는 vote-result-viewer
백엔드로부터 받아왔다고 가정할 정보
title: "점심 골라줘",
opts: [
{
id: 1,
value: "국밥",
votes: 1,
},
{
id: 2,
value: "자장면",
votes: 2,
},
{
id: 3,
value: "돈가스",
votes: 3,
},
{
id: 4,
value: "햄버거",
votes: 4,
},
],
선택지를 렌더링할 option-selector에 위 정보를 넘겨줍시다.
<option-selector :opts="opts" :title="title" />
결과를 렌더링할 vote-result-viewer에도 넘겨줍니다. 투표를 했을시에만 결과를 보여주고 싶으니 isVoted라는 boolean 값을 새로 만들고, v-if 디렉티브에 적용합니다.
<vote-result-viewer v-if="isVoted" :opts="opts" />
여기에 하나 더, 자식 컴포넌트에서 어떤 값이 선택되었는지 부모 컴포넌트가 알 수 있게 해줘야 합니다.
그래서 추가적으로 votedId 라는 값을 선언하도록 하겠습니다.
votedId: 0,
양방향 바인딩을 위한 v-model 을 사용해 votedId를 전달해줍니다.
<option-selector v-model="votedId" :opts="opts" :title="title" />
투표하기 버튼도 넣어줘야겠죠? 투표하기 버튼을 눌렀을 때 투표한 선택지의 투표수에 1을 더하도록 하고, 결과창을 띄우기 위해 isVoted를 true로 바꾸도록 하겠습니다.
<button type="button" @click="onVote">투표하기</button>
methods: {
onVote() {
if (this.votedId === 0) {
return alert("선택 필수임");
}
const voteTarget = this.opts.find((opt) => opt.id === this.votedId);
voteTarget.votes++;
this.isVoted = true;
return;
},
},
투표 선택지 컴포넌트와 투표 버튼에 v-if="!isVoted" 를 넣고 투표 결과 컴포넌트에는 v-else를 적용시켰습니다.
<template>
<div>
<h1>{{ title }}</h1>
<form v-if="!isVoted">
<option-selector v-model="votedId" :opts="opts" :title="title" />
<button type="button" @click="onVote">투표하기</button>
</form>
<vote-result-viewer v-else :opts="opts" />
</div>
</template>
<script>
import OptionSelector from "../components/option-selector.vue";
import VoteResultViewer from "../components/vote-result-viewer.vue";
export default {
name: "vote-view",
components: {
OptionSelector,
VoteResultViewer,
},
data() {
return {
title: "점심 골라줘",
opts: [
{
id: 1,
value: "국밥",
votes: 1,
},
{
id: 2,
value: "자장면",
votes: 2,
},
{
id: 3,
value: "돈가스",
votes: 3,
},
{
id: 4,
value: "햄버거",
votes: 4,
},
],
isVoted: false,
votedId: 0,
};
},
methods: {
onVote() {
if (this.votedId === 0) {
return alert("선택 필수임");
}
const voteTarget = this.opts.find((opt) => opt.id === this.votedId);
voteTarget.votes++;
this.isVoted = true;
return;
},
},
};
</script>
이제 자식 컴포넌트인 option-selector로 가봅시다.
이 컴포넌트의 템플릿 기본 구성은 https://fierycoding.tistory.com/87 여기에 있습니다.
위 링크의 option-selector와 아래 이 포스팅에서 사용할 option-selector의 다른 점으로는,
opts가 object들의 array가 되었다는 것
key 어트리뷰트에 index가 아닌 id가 사용되었다는 점입니다.
템플릿
<template>
<div class="form-group">
<div v-for="{ id, value } in opts" :key="`opt-${id}`">
<label>{{ value }}</label>
<input type="radio" :name="title" :value="id" />
</div>
</div>
</template>
부모가 바인드해준 값을 props로 받아옵니다.
props: ["value", "title", "opts"],
title, opts는 전달해준 그대로인데... votedId는 어디로 가고 value가 남아있습니다.
왜냐하면 votedId는 다른 값들과 다르게 부모가 v-model로 전달해주었기 때문인데요, 따라서 value라는 이름으로 받을 수 있습니다.
이 value를 바로 사용하거나 변형하려 하면 immutable 에러가 발생합니다. select라는 이름으로 한 번 감싸서 사용하도록 하겠습니다.
computed: {
select: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
},
},
},
select는 getter를 이용해 value와 똑같은 값을 가집니다.
그리고 값이 변경되면(input 이벤트) $emit을 통해 부모 컴포넌트로 변경된 값을 전달할 수 있습니다.
이 select를 제대로 사용하려면 자식 컴포넌트에도 v-model을 사용해야 합니다.
<template>
<div class="form-group">
<div v-for="({ id, value }, index) in opts" :key="`opt-${index}`">
<label>{{ value }}</label>
<input type="radio" :name="title" :value="id" v-model="select" />
</div>
</div>
</template>
<template>
<div class="form-group">
<div v-for="({ id, value }, index) in opts" :key="`opt-${index}`">
<label>{{ value }}</label>
<input type="radio" :name="title" :value="id" v-model="select" />
</div>
</div>
</template>
<script>
export default {
name: "option-selector",
props: ["value", "title", "opts"],
computed: {
select: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
},
},
},
};
</script>
이 컴포넌트는 투표 결과 확인을 위해 존재합니다.
<template>
<div class="form-group">
<div
v-for="{ id, value, votes } in opts"
:key="`${value}-result-${id}`"
>
{{ value }} : {{ votes }} 표
</div>
</div>
</template>
<script>
export default {
name: "vote-result-viewer",
props: ["opts"],
};
</script>
결과물
Vue 기초 : 리스트 렌더링 (0) | 2021.12.12 |
---|
댓글 영역