상세 컨텐츠

본문 제목

Vue 기초 : 부모-자식 컴포넌트끼리 데이터 주고받기

프레임워크+라이브러리/Vue

by moonionn 2021. 12. 12. 22:53

본문

 

2,3 번 과정에 대한 내용입니다. 백엔드 관련 내용 1도 없습니다.

 

예시 상황 : 점심 메뉴 투표화면 만들기

부모 컴포넌트 : vote-view

자식 컴포넌트 :

        선택지 보여주는 option-selector

        결과 보여주는 vote-result-viewer

 


부모 컴포넌트 : vote-view

백엔드로부터 받아왔다고 가정할 정보

      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" />

 

v-model

여기에 하나 더, 자식 컴포넌트에서 어떤 값이 선택되었는지 부모 컴포넌트가 알 수 있게 해줘야 합니다.

그래서 추가적으로 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;
    },
  },

 

vote-view 전체 코드

투표 선택지 컴포넌트와 투표 버튼에 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

이제 자식 컴포넌트인 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라는 이름으로 받을 수 있습니다.

 

$emit

value를 바로 사용하거나 변형하려 하면 immutable 에러가 발생합니다. select라는 이름으로 한 번 감싸서 사용하도록 하겠습니다.

  computed: {
    select: {
      get() {
        return this.value;
      },
      set(value) {
        this.$emit("input", value);
      },
    },
  },

select는 getter를 이용해 value와 똑같은 값을 가집니다.

그리고 값이 변경되면(input 이벤트) $emit을 통해 부모 컴포넌트로 변경된 값을 전달할 수 있습니다.

 

v-model

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>

 

option-selector 전체 코드

<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>

 


자식 : vote-result-viewer

이 컴포넌트는 투표 결과 확인을 위해 존재합니다.

<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' 카테고리의 다른 글

Vue 기초 : 리스트 렌더링  (0) 2021.12.12

관련글 더보기

댓글 영역