본문 바로가기

데일리 공부 기록

hands on vue3 - 주어진 소스를 갖고 custom component에 v-model 적용하기

728x90

[목표]

: custom component에 v-model를 적용할 경우 자식 컴포넌트에서

props, emit메소드명에 대한 이해와 익숙함을 만들자. 

 

저번 포스팅에서 

TheForm 컴포넌트 안에

RatingControl 컴포넌트를 생성했다.

 

RatingControl 컴포넌트 내의 data에 

평가에 대한 데이터인

Poor, Average, Great가 있었다. 

 

이번 포스팅에서는

다음의 조건을 만족시키는 기능을 구현해보자

 

먼저 알아야 할 것은

TheForm(부모) -> RatingControl(자식) 관계이다.

RatingControl은 사용자가 평가를 선택하는 컴포넌트다.

 

첫째) TheForm 안에 선택한 평가 데이터를 추가하기 (Poor, Average, Great)

둘째) 평가데이터를 TheForm 컴포넌트 내부의 RatingControl 컴포넌트에 v-model로 넣어주기

셋째) 부모에서 보낸 데이터를 사용자가 선택한 평가데이터에 따라 emit해주기 

 

 

제일 아래에

설명있음


수정 전 소스

[App.vue]

<template>
  <the-form></the-form>
</template>

<script>
import TheForm from './components/TheForm.vue';

export default {
  components: {
    TheForm
  }  
}
</script>

<style>
* {
  box-sizing: border-box;
}

html {
  font-family: sans-serif;
}

body {
  margin: 0;
  background-color: #292929;
}
</style>

[TheForm.vue]

<template>
  <form @submit.prevent="submitData">
    <div class="form-control" :class="{invalid:userInputName == 'invalid'}">
      <label for="user-name">Your Name</label>
      <input id="user-name" name="user-name" type="text" v-model="inputName" @blur="blurInputName"/>
      <p v-if="userInputName == 'invalid'">Please Check Your Name Input</p>
    </div>
    <div class="form-control" :class="{invalid:userInputAge == 'invalid'}">
      <label for="age">Your Age (Years)</label>
      <input id="age" name="age" type="number" v-model="inputAge" @blur="blurInputAge"/>
      <p v-if="userInputAge == 'invalid'">Please input your age</p>
    </div>
    <div class="form-control">
      <label for="referrer">How did you hear about us?</label>
      <select id="referrer" name="referrer">
        <option value="google">Google</option>
        <option value="wom">Word of mouth</option>
        <option value="newspaper">Newspaper</option>
      </select>
    </div>
    <div class="form-control">
      <h2>What are you interested in?</h2>
      <div>
        <input id="interest-news" name="interest" type="checkbox" value="News" v-model="interst" />
        <label for="interest-news">News</label>
      </div>
      <div>
        <input id="interest-tutorials" name="interest" type="checkbox" value="Tutorials" v-model="interst" />
        <label for="interest-tutorials">Tutorials</label>
      </div>
      <div>
        <input id="interest-nothing" name="interest" type="checkbox" value="Nothing" v-model="interest"/>
        <label for="interest-nothing">Nothing</label>
      </div>
    </div>
    <div class="form-control">
      <h2>How do you learn?</h2>
      <div>
        <input id="how-video" name="how" type="radio" value="Video" v-model="how" />
        <label for="how-video">Video Courses</label>
      </div>
      <div>
        <input id="how-blogs" name="how" type="radio" value="Blogs" v-model="how"/>
        <label for="how-blogs">Blogs</label>
      </div>
      <div>
        <input id="how-other" name="how" type="radio" value="Other" v-model="how" />
        <label for="how-other">Other</label>
      </div>
    </div>
    <div>
      <input type="checkbox" v-model="agreement">
      <p>Do you agree with that we can use your private information?</p>
    </div>
    <div>
      <button>Save Data</button>
    </div>
  </form>
</template>
<script>
  export default{
    data(){
      return{
        inputName: '',
        inputAge: 0,
        interest: [],
        how:'',
        agreement: null,
        userInputName: 'pending',
        userInputAge: 'pending',
      };
    },
    methods: {
      blurInputAge(){
        if(this.inputAge == ''){
          this.userInputAge = 'invalid';
        }
        if(this.inputAge != ''){
          this.userInputAge = 'valid';
        }
      },
      blurInputName(){
        if(this.inputName == ''){
          console.log("hello");
          this.userInputName = 'invalid';
        }

        if(this.inputName.trim() != ''){
          this.userInputName = 'valid';
        }
      },
      submitData(){
        console.log(this.inputName);
        console.log(this.inputAge + 10);
        console.log(this.$refs.userAge.value + 10);

        console.log(this.interest);
        console.log(this.how);
        console.log(this.agreement);

        this.inputAge = '';
        this.interest = '';
        this.how = '';
        this.inputName = '';
      }
    }
  }
</script>
<style scoped>
form {
  margin: 2rem auto;
  max-width: 40rem;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
  padding: 2rem;
  background-color: #ffffff;
}

.form-control {
  margin: 0.5rem 0;
}
.form-control.invalid label{
  color: red;
}
.form-control.invalid input{
  border-color: red;
}
.form-control.invalid p{
  color:red;
}

label {
  font-weight: bold;
}

h2 {
  font-size: 1rem;
  margin: 0.5rem 0;
}

input,
select {
  display: block;
  width: 100%;
  font: inherit;
  margin-top: 0.5rem;
}

select {
  width: auto;
}

input[type='checkbox'],
input[type='radio'] {
  display: inline-block;
  width: auto;
  margin-right: 1rem;
}

input[type='checkbox'] + label,
input[type='radio'] + label {
  font-weight: normal;
}

button {
  font: inherit;
  border: 1px solid #0076bb;
  background-color: #0076bb;
  color: white;
  cursor: pointer;
  padding: 0.75rem 2rem;
  border-radius: 30px;
}

button:hover,
button:active {
  border-color: #002350;
  background-color: #002350;
}

</style>

 

[RatingControl.vue]

<template>
  <ul>
    <li :class="{active: activeOption=='poor'}">
        <button type="button" @click="activeOption ='poor'">Poor</button> 
    </li>
    <li :class="{active: activeOption=='average'}">
        <button type="button" @click="activeOption ='average'">Average</button> 
    </li>
    <li :class="{active: activeOption=='great'}">
        <button type="button" @click="activeOption ='great'">Great</button> 
    </li>
  </ul>
</template>

<script>
export default {
    data(){
        return{
            active: '',
            activeOption: '',
        };
    }
}
</script>

<style scoped>
ul{
    list-style: none;
    margin:0.5rem 0;
    padding:0px;
    display:flex;
}
li{
    border:1px solid #ccc;
    justify-content: center;
    align-items: center;
    margin:0 1rem;
}
button{
    border:none;
    background-color:transparent;
    font:inherit;
    cursor:pointer;
}
.active{
    border-color:#a00078;
}
.active button{
    border-color:#a00078;
}
</style>

수정 후 소스

[RatingControl.vue]

<template>
  <ul>
    <li :class="{active: modelValue=='poor'}">
        <button type="button" @click="sendUp('poor')">Poor</button> 
    </li>
    <li :class="{active: modelValue=='average'}">
        <button type="button" @click="sendUp('average')">Average</button> 
    </li>
    <li :class="{active: modelValue=='great'}">
        <button type="button" @click="sendUp('great')">Great</button> 
    </li>
  </ul>
</template>

<script>
export default {
    props: ['modelValue'],
    emits: ['update:modelValue'],
    data(){
        return{
            active: '',
            activeOption: '',
        };
    },
    methods: {
        sendUp(val){
            this.$emit('update:modelValue', val);
        }
    }
}
</script>

<style scoped>
ul{
    list-style: none;
    margin:0.5rem 0;
    padding:0px;
    display:flex;
}
li{
    border:1px solid #ccc;
    justify-content: center;
    align-items: center;
    margin:0 1rem;
}
button{
    border:none;
    background-color:transparent;
    font:inherit;
    cursor:pointer;
}
.active{
    border-color:#a00078;
}
.active button{
    border-color:#a00078;
}
</style>

 

[TheForm.vue]

<template>
  <form @submit.prevent="submitData">
    <div class="form-control" :class="{invalid:userInputName == 'invalid'}">
      <label for="user-name">Your Name</label>
      <input id="user-name" name="user-name" type="text" v-model="inputName" @blur="blurInputName"/>
      <p v-if="userInputName == 'invalid'">Please Check Your Name Input</p>
    </div>
    <div class="form-control" :class="{invalid:userInputAge == 'invalid'}">
      <label for="age">Your Age (Years)</label>
      <input id="age" name="age" type="number" v-model="inputAge" @blur="blurInputAge"/>
      <p v-if="userInputAge == 'invalid'">Please input your age</p>
    </div>
    <div class="form-control"><template>
  <form @submit.prevent="submitData">
    <div class="form-control" :class="{invalid:userInputName == 'invalid'}">
      <label for="user-name">Your Name</label>
      <input id="user-name" name="user-name" type="text" v-model="inputName" @blur="blurInputName"/>
      <p v-if="userInputName == 'invalid'">Please Check Your Name Input</p>
    </div>
    <div class="form-control" :class="{invalid:userInputAge == 'invalid'}">
      <label for="age">Your Age (Years)</label>
      <input id="age" name="age" type="number" v-model="inputAge" @blur="blurInputAge"/>
      <p v-if="userInputAge == 'invalid'">Please input your age</p>
    </div>
    <div class="form-control">
      <label for="referrer">How did you hear about us?</label>
      <select id="referrer" name="referrer">
        <option value="google">Google</option>
        <option value="wom">Word of mouth</option>
        <option value="newspaper">Newspaper</option>
      </select>
    </div>
    <div class="form-control">
      <h2>What are you interested in?</h2>
      <div>
        <input id="interest-news" name="interest" type="checkbox" value="News" v-model="interest" />
        <label for="interest-news">News</label>
      </div>
      <div>
        <input id="interest-tutorials" name="interest" type="checkbox" value="Tutorials" v-model="interest"/>
        <label for="interest-tutorials">Tutorials</label>
      </div>
      <div>
        <input id="interest-nothing" name="interest" type="checkbox" value="Nothing" v-model="interest"/>
        <label for="interest-nothing">Nothing</label>
      </div>
    </div>
    <div class="form-control">
      <h2>How do you learn?</h2>
      <div>
        <input id="how-video" name="how" type="radio" value="Video" v-model="how" />
        <label for="how-video">Video Courses</label>
      </div>
      <div>
        <input id="how-blogs" name="how" type="radio" value="Blogs" v-model="how"/>
        <label for="how-blogs">Blogs</label>
      </div>
      <div>
        <input id="how-other" name="how" type="radio" value="Other" v-model="how" />
        <label for="how-other">Other</label>
      </div>
    </div>
    <div class="form-control">
      <rating-control v-model="activeOption"></rating-control>
    </div>
    <div>
      <input type="checkbox" v-model="agreement">
      <p>Do you agree with that we can use your private information?</p>
    </div>
    <div>
      <button>Save Data</button>
    </div>
  </form>
</template>
<script>
import RatingControl from './RatingControl.vue';

  export default{
    components:{
      RatingControl,
    },
    data(){
      return{
        inputName: '',
        inputAge: 0,
        interest: [],
        how:'',
        agreement: null,
        userInputName: 'pending',
        userInputAge: 'pending',
        activeOption: '',
      };
    },
    methods: {
      blurInputAge(){
        if(this.inputAge == ''){
          this.userInputAge = 'invalid';
        }
        if(this.inputAge != ''){
          this.userInputAge = 'valid';
        }
      },
      blurInputName(){
        if(this.inputName == ''){
          console.log("hello");
          this.userInputName = 'invalid';
        }

        if(this.inputName.trim() != ''){
          this.userInputName = 'valid';
        }
      },
      submitData(){
        console.log(this.activeOption);
        this.inputName = '';
        this.inputAge = 0;
        this.activeOption = '';
        this.interest = '';
        this.how = '';
      }
    }
  }
</script>
<style scoped>
form {
  margin: 2rem auto;
  max-width: 40rem;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
  padding: 2rem;
  background-color: #ffffff;
}

.form-control {
  margin: 0.5rem 0;
}
.form-control.invalid label{
  color: red;
}
.form-control.invalid input{
  border-color: red;
}
.form-control.invalid p{
  color:red;
}

label {
  font-weight: bold;
}

h2 {
  font-size: 1rem;
  margin: 0.5rem 0;
}

input,
select {
  display: block;
  width: 100%;
  font: inherit;
  margin-top: 0.5rem;
}

select {
  width: auto;
}

input[type='checkbox'],
input[type='radio'] {
  display: inline-block;
  width: auto;
  margin-right: 1rem;
}

input[type='checkbox'] + label,
input[type='radio'] + label {
  font-weight: normal;
}

button {
  font: inherit;
  border: 1px solid #0076bb;
  background-color: #0076bb;
  color: white;
  cursor: pointer;
  padding: 0.75rem 2rem;
  border-radius: 30px;
}

button:hover,
button:active {
  border-color: #002350;
  background-color: #002350;
}

</style>
      <label for="referrer">How did you hear about us?</label>
      <select id="referrer" name="referrer">
        <option value="google">Google</option>
        <option value="wom">Word of mouth</option>
        <option value="newspaper">Newspaper</option>
      </select>
    </div>
    <div class="form-control">
      <h2>What are you interested in?</h2>
      <div>
        <input id="interest-news" name="interest" type="checkbox" value="News" v-model="interest" />
        <label for="interest-news">News</label>
      </div>
      <div>
        <input id="interest-tutorials" name="interest" type="checkbox" value="Tutorials" v-model="interest"/>
        <label for="interest-tutorials">Tutorials</label>
      </div>
      <div>
        <input id="interest-nothing" name="interest" type="checkbox" value="Nothing" v-model="interest"/>
        <label for="interest-nothing">Nothing</label>
      </div>
    </div>
    <div class="form-control">
      <h2>How do you learn?</h2>
      <div>
        <input id="how-video" name="how" type="radio" value="Video" v-model="how" />
        <label for="how-video">Video Courses</label>
      </div>
      <div>
        <input id="how-blogs" name="how" type="radio" value="Blogs" v-model="how"/>
        <label for="how-blogs">Blogs</label>
      </div>
      <div>
        <input id="how-other" name="how" type="radio" value="Other" v-model="how" />
        <label for="how-other">Other</label>
      </div>
    </div>
    <div class="form-control">
      <rating-control v-model="activeOption"></rating-control>
    </div>
    <div>
      <input type="checkbox" v-model="agreement">
      <p>Do you agree with that we can use your private information?</p>
    </div>
    <div>
      <button>Save Data</button>
    </div>
  </form>
</template>
<script>
import RatingControl from './RatingControl.vue';

  export default{
    components:{
      RatingControl,
    },
    data(){
      return{
        inputName: '',
        inputAge: 0,
        interest: [],
        how:'',
        agreement: null,
        userInputName: 'pending',
        userInputAge: 'pending',
        activeOption: '',
      };
    },
    methods: {
      blurInputAge(){
        if(this.inputAge == ''){
          this.userInputAge = 'invalid';
        }
        if(this.inputAge != ''){
          this.userInputAge = 'valid';
        }
      },
      blurInputName(){
        if(this.inputName == ''){
          console.log("hello");
          this.userInputName = 'invalid';
        }

        if(this.inputName.trim() != ''){
          this.userInputName = 'valid';
        }
      },
      submitData(){
        console.log(this.activeOption);
        this.inputName = '';
        this.inputAge = 0;
        this.activeOption = '';
        this.interest = '';
        this.how = '';
      }
    }
  }
</script>
<style scoped>
form {
  margin: 2rem auto;
  max-width: 40rem;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
  padding: 2rem;
  background-color: #ffffff;
}

.form-control {
  margin: 0.5rem 0;
}
.form-control.invalid label{
  color: red;
}
.form-control.invalid input{
  border-color: red;
}
.form-control.invalid p{
  color:red;
}

label {
  font-weight: bold;
}

h2 {
  font-size: 1rem;
  margin: 0.5rem 0;
}

input,
select {
  display: block;
  width: 100%;
  font: inherit;
  margin-top: 0.5rem;
}

select {
  width: auto;
}

input[type='checkbox'],
input[type='radio'] {
  display: inline-block;
  width: auto;
  margin-right: 1rem;
}

input[type='checkbox'] + label,
input[type='radio'] + label {
  font-weight: normal;
}

button {
  font: inherit;
  border: 1px solid #0076bb;
  background-color: #0076bb;
  color: white;
  cursor: pointer;
  padding: 0.75rem 2rem;
  border-radius: 30px;
}

button:hover,
button:active {
  border-color: #002350;
  background-color: #002350;
}

</style>

자식에서

props를 왜 modelValue 라는 변수명으로 받을까 궁금했었다.

 

그냥 vue만든 사람이 그렇게 되도록 설정해놨다.

 

emit도 마찬가지다.

update:modelValue라는 메소드명으로 emit하여 부모에게 보낸다.