VueJs로 만드는 todoList#7 양방향 바인딩, v-model

v-model에 대해 알아봅시다.

작성자

장현일

장현일

RyanJang

v-model이란?

일전에 바인딩에 대해 설명드린 적이 있죠?
다시 한번 설명드리면 Data binding이란 VueJs의 binding expressions을 사용하여 script와 DOM간의 데이터를 주고받는 과정을 의미합니다.

바인딩에는 단방향 바인딩과 양방향 바인딩이 있는데 단방향 바인딩에 대해서만 설명을 드렸었습니다.
그래서 오늘은 양방향 바인딩인 v-model에 대해서 설명을 드리려고 합니다.

단방향 바인딩은 데이터를 화면에 출력해주면 끝이었는데 양방향 바인딩은 화면에서 입력을 받아 데이터로 다시 전달하는 과정이 추가되어 양쪽 방향 모두 바인딩 되는 것을 의미합니다.

그럼 이제 양방향 바인딩을 어떻게 활용하는지 케이스별로 한번 알아보겠습니다.

문자 및 문장

첫번째로 문자 및 문장을 바인딩 해볼게요.
지난번에 이어 계속해서 Main.vue 내용을 수정하며 진행해보도로 하겠습니다.

/Main.vue
...
  <div class="mainTitle" style="padding: 15px;">
      <h2 v-text="title"></h2>
      <p v-text="subTitle"></p>
      <h5>오늘의 할일 가지</h5>
      <div v-show="showBoolean" v-html="mindControl"></div>
      <div v-if="randomNum < 3">3보다 작군요.</div>
      <div v-else-if="randomNum >= 3 && randomNum < 7">3보다 크거나 같고 7보다 작군요.</div>
      <div v-else>7이거나 7보다 크군요.</div>
      <div></div>
      <div v-pre></div>
      <div style="width: 200px; height: 100px;" v-background="customColor">배경색</div>
      <div>
          <input v-model="title" placeholder="제목을 입력해보세요.">
      </div>
      <div>
          <textarea  v-model="subTitle" placeholder="부제목 입력해보세요."></textarea>
      </div>
  </div>
...
  data () {
    return {
      todoList: [],
      goal: 0,
      title: 'VueJs로 만드는 todoList'
    }
  }
...
  • (문자) input 태그에 v-model을 사용하고 그 안에 title 데이터를 넣어주었습니다.
    화면에서 타이틀을 직접 입력해보세요.
    타이틀이 변경되는 걸 확인하셨나요?
  • (문장) textarea 태그에서도 마찬가지로 subtitle을 넣어주었으며 똑같은 방식으로 바인딩 되는 걸 확인할 수 있습니다.

체크박스

이번엔 체크박스를 통해 우리가 어떤 데이터를 선택했는지 알아보겠습니다.
todoList.vue 파일에서 todoList 데이터를 활용해볼게요.

/TodoList.vue
<template>
    <div>
        <div style="height: 50px; width: 100%">
            <button class="btn-dark float-right" style="margin-right: 150px;" @click="changeList">변경</button>
        </div>
        <div class="mainTable">
            <div></div>
            <table class="table">
                <thead>
                <tr>
                    <th>선택</th>
                    <th>인덱스</th>
                    <th>순번</th>
                    <th>오늘의 할일</th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(item, index) in todoList" :key="item.id">
                    <td><input type="checkbox" :value="index" v-model="selectedGroup"></td>
                    <td v-if="index === 0" @click="routeDetailPage">인덱스가 0이면 출력</td>
                    <td v-else-if="index ===1">인덱스가 1이면 출력</td>
                    <td v-else>인덱스가 0도 1도 아니면 출력</td>
                    <td></td>
                    <td></td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script>
  export default {
    name: 'TodoList',
    props: {
      todoList: Array,
      changeList: Function
    },
    data () {
      return {
        selectedGroup: []
      }
    },
    methods: {
      routeDetailPage () {
        this.$router.push({path: '/detail', query: {id: 1}})
      }
    }
  }
</script>

table 태그 내 input 태그로 체크박스를 삽입했죠?
그리고 input 태그에 리스트중 해당 인덱스를 value값으로 바인딩 해주었으며, v-model로 새롭게 정의한 selectedGroup을 사용하였습니다.
그리고 이 데이터를 확인하기 위해 간단히 table 태그 위에 selectedGroup을 출력해주었습니다.
이제 체크박스를 클릭하면서 selectedGroup이 어떤게 변경되는지 확인해보세요!
선택한 리스트의 인덱스가 표현되는걸 확인하실 수 있습니다.

라디오

다음은 라디오 버튼에 대해 알아보겠습니다.

...
<div class="mainTable">
    <div></div>
    <table class="table">
        <thead>
        <tr>
            <th>선택</th>
            <th>인덱스</th>
            <th>순번</th>
            <th>오늘의 할일</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="(item, index) in todoList" :key="item.id">
            <td><input type="checkbox" :value="index" v-model="selectedGroup"></td>
            <td v-if="index === 0" @click="routeDetailPage">인덱스가 0이면 출력</td>
            <td v-else-if="index ===1">인덱스가 1이면 출력</td>
            <td v-else>인덱스가 0도 1도 아니면 출력</td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td>
                <input type="radio" id="one" value="사과" v-model="breakfast">
                <label for="one">ONE</label>
            </td>
            <td>
                <input type="radio" id="two" value="배" v-model="breakfast">
                <label for="two">TWO</label>
            </td>
            <td>
                <input type="radio" id="three" value="소세지" v-model="breakfast">
                <label for="three">THREE</label>
            </td>
            <td>
                <input type="radio" id="four" value="귤" v-model="breakfast">
                <label for="four">FOUR</label>
            </td>
        </tr>
        </tbody>
    </table>
</div>
...
data () {
  return {
    selectedGroup: [],
    breakfast: ''
  }
},
...

기존의 table 마지막 로우에 라디오 버튼을 삽입해 보았습니다.
각각의 라벨에 따른 input 태그에 value를 정의하고 선택된 내용을 출력하게 했는데요.
선택하면 table 상단에 선택된 value값이 보이시나요?

셀렉트 박스

이번엔 셀렉트 박스입니다.
함께 보실까요?

...
<div class="mainTable">
    <div></div>
    <table class="table">
        <thead>
        <tr>
            <th>선택</th>
            <th>인덱스</th>
            <th>순번</th>
            <th>오늘의 할일</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="(item, index) in todoList" :key="item.id">
            <td><input type="checkbox" :value="index" v-model="selectedGroup"></td>
            <td v-if="index === 0" @click="routeDetailPage">인덱스가 0이면 출력</td>
            <td v-else-if="index ===1">인덱스가 1이면 출력</td>
            <td v-else>인덱스가 0도 1도 아니면 출력</td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td>
                <select v-model="breakfast">
                    <option>사과</option>
                    <option></option>
                    <option>소세지</option>
                    <option></option>
                </select>
            </td>
        </tr>
        </tbody>
    </table>
</div>
...
data () {
  return {
    selectedGroup: [],
    breakfast: ''
  }
},
...

정말 간단하죠?
select 태그 내에 multiple 속성을 추가하면 다중으로도 선택 가능합니다!

v-model.lazy

이 속성은 input 태그의 text속성에서 원하는 데이터를 입력하였을 때 바로 바인딩이 되는 것이 아니라 input박스에서 벗어나는 순간 또는 엔터로 입력을 완성하는 순간에 바인딩 됩니다.
html <div> <input type="text" v-model.lazy="todo"> </div> <div></div>

v-model.number & v-model.trim

  • number 속성은 입력받은 데이터를 숫자로 변환해줍니다.
  • trim 속성은 잘 아시죠? 공백을 제거해주는 역할을 합니다.

todoList 추가, 삭제

이제 배운 속성들로 todoList를 추가하거나 삭제를 해보도록 합시다.

// TodoList.vue
<template>
    <div>
        <div style="height: 50px; width: 100%">
            <button class="btn-dark float-right" style="margin-right: 150px;" @click="changeList">변경</button>
        </div>
        <div class="mainTable">
            <div>
                <input type="text" v-model="todo">
                <button class="btn btn-dark" @click="addList(todo)" style="margin: 5px 10px">추가</button>
                <button class="btn btn-danger" @click="deleteList(selectedGroup)">삭제</button>
            </div>
            <table class="table">
                <thead>
                    <tr>
                        <th>선택</th>
                        <th>인덱스</th>
                        <th>순번</th>
                        <th>오늘의 할일</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(item, index) in todoList" :key="item.id">
                        <td><input type="checkbox" :value="index" v-model="selectedGroup"></td>
                        <td v-if="index === 0" @click="routeDetailPage">인덱스가 0이면 출력</td>
                        <td v-else-if="index ===1">인덱스가 1이면 출력</td>
                        <td v-else>인덱스가 0도 1도 아니면 출력</td>
                        <td></td>
                        <td></td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script>
  export default {
    name: 'TodoList',
    props: {
      todoList: Array,
      changeList: Function,
      addList: Function,
      deleteList: Function
    },
    data () {
      return {
        selectedGroup: [],
        todo: ''
      }
    },
    methods: {
      routeDetailPage () {
        this.$router.push({path: '/detail', query: {id: 1}})
      }
    }
  }
</script>
// Main.vue
<template>
    <div id="app">
        <div class="mainTitle" style="padding: 15px;">
            <h2 v-text="title"></h2>
            <p v-text="subTitle"></p>
            <h5>오늘의 할일 가지</h5>
            <div v-show="showBoolean" v-html="mindControl"></div>
            <div v-if="randomNum < 3">3보다 작군요.</div>
            <div v-else-if="randomNum >= 3 && randomNum < 7">3보다 크거나 같고 7보다 작군요.</div>
            <div v-else>7이거나 7보다 크군요.</div>
            <div></div>
            <div v-pre></div>
            <div style="width: 200px; height: 100px;" v-background="customColor">배경색</div>
            <div>
                <input v-model="title" placeholder="제목을 입력해보세요.">
            </div>
            <div>
                <textarea  v-model="subTitle" placeholder="부제목 입력해보세요."></textarea>
            </div>
        </div>
        <todo-list :todoList="todoList" :changeList="changeList" :addList="addList" :deleteList="deleteList"></todo-list>
        <div class="bottomTitle" style="padding: 15px;">
            <h2>VueJs로 만드는 todoList</h2>
            <h5>이번주 목표 가지</h5>
        </div>
    </div>
</template>

<script>

import TodoList from './comp/TodoList'
import {background} from '../../directive'

export default {
  name: 'app',
  directives: {
    background
  },
  components: {
    TodoList
  },
  data () {
    return {
      todoList: [],
      goal: 0,
      title: 'VueJs로 만드는 todoList',
      subTitle: 'VueJs로 만드는 todoList',
      mindControl: `<h6 style="color: red">오늘  일을 내일로 미루지 말자</h6>`,
      showBoolean: false,
      randomNum: Math.floor(Math.random() * 10),
      customColor: 'red'
    }
  },
  watch: {
    todoList (val, oldVal) {
      console.log(val, oldVal)
      this.goal = val.length * 7
    }
  },
  methods: {
    changeList () {
      this.todoList = this.todoList.splice(0, this.todoList.length - 1)
    },
    addList (v) {
      this.todoList.push({id: this.todoList.length + 1, todo: v})
    },
    deleteList (selectedGroup) {
      selectedGroup.forEach(v => {
        this.todoList.splice(v, 1)
      })
    }
  },
  mounted () {
    this.todoList = [
      {id: 1, todo: '아침 먹기'},
      {id: 2, todo: '점심 먹기'},
      {id: 3, todo: '저녁 먹기'}
    ]
  }
}
</script>

<style>
    .mainTitle {
        padding: 15px;
    }
    .bottomTitle {
        padding: 15px;
        float: right
    }
</style>

먼저 코드를 보여드렸습니다.
어떠신가요?
제가 어떻게 추가 및 삭제 기능을 구현했는지 바로 이해가 되셨나요?

우선 TodoList.vue에서 추가할 데이터를 v-model로 받고 addList에 파라미터로 넘겨 주었습니다.
addList는 부모로 부터 전달받은 메소드죠?
다시 Main.vue로 가서 전달받은 데이터를 push로 넣어주고 TodoList.vue에서 전달받은 메소드를 @click 이벤트로 실행시키면 끝입니다.
화면에서 실행해 보셨나요?

추가하는게 이해되셨다면 삭제하는 것도 바로 이해하실수 있으실 겁니다.
체크박스로 선택한 로우의 인덱스를 저장하고 이것을 파라미터로 넘겨 선택한 인덱스로 리스트에서 제거해주었습니다.

정리

양방향 바인딩은 v-model로 너무나도 다양하게 사용할 수 있음을 확인하셨을 겁니다.
어플리케이션은 보통 사용자로부터 어떠한 값을 넘겨받아 작업하는게 일반적인데요.
오늘 소개드린 속성들로 todoList 추가, 삭제하기 처럼 응용하여 잘 활용할 수 있기를 바랍니다!

Tags : javascript vuejs 

comments powered by Disqus