Dandy Now!
  • [개발자의품격][부트캠프][1기][18차시] Vue.js #2 | 데이터바인딩
    2022년 03월 05일 18시 14분 27초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    데이터 바인딩

    문자열

    문자열 바인딩은 단방향 데이터 바인딩이다.

    // src/views/DataBindingHtmlView.vue
    <template>
      <div>
        <div>{{ htmlString }}</div> // {{}}는 문자열을 바인딩한다.
        <div v-html="htmlString"></div> // v-html 디렉티브(Directive)를 사용하면 태그로 바인딩한다.
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          htmlString: '<p style="color:red;">빨간색 문자</p>'
        }
      }
    }
    </script>

     

     

    [그림 1] 문자열, v-html 바인딩

     


     

    input

    v-model을 이용하여 사용자의 입력 값, 자바스크립트 함숫값의 양방향 데이터 바인딩이 가능하다. Vue의 막강한 기능이다.

    <!-- src/iews/_databinding/DataBindingInput.vue -->
    <template>
      <div>
        <!-- v-model 디렉티브를 이용 양방향 데이터바인딩 가능하다. -->
        <input type="text" name="" id="" v-model="userId" />
        <button @click="myFunction">클릭</button>
        <button @click="changeData">변경</button>
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          userId: 'jeremy'
        }
      },
      methods: {
        myFunction() {
          console.log(this.userId)
        },
        changeData() {
          this.userId = 'gildong'
        }
      }
    }
    </script>

     

    [그림 2] "클릭 > 변경 > 클릭"한 결과

     

    input 태그에서의 v-model은 value 속성과 양방향으로 연결되어 있다. [그림 3]은 아래 코드의 실행 결과이다. 아래 코드에서 v-model.trim과 text, number 타입의 용법을 유의해서 보자!

    <!-- src/views/1_databinding/DataBindingInput.vue -->
    <template>
      <div>
        <!-- .trim은 입력값의 앞뒤 공백을 제거해 준다. -->
        <input type="text" name="" id="" v-model.trim="userId" />
        <button @click="myFunction">클릭</button>
        <button @click="changeData">변경</button>
        <br />
        <!-- number 타입 -->
        <input type="number" name="" id="" v-model="num1" />+
        <input type="number" name="" id="" v-model="num2" />=
        <span>{{ num1 + num2 }}</span>
        <br />
        <!-- text 타입이라 문자열로 결합 -->
        <input type="text" name="" id="" v-model="num3" />+
        <input type="text" name="" id="" v-model="num4" />=
        <span>{{ num3 + num4 }}</span>
        <br />
        <!-- text 타입에서 v.model.number로 정수형으로 치환 -->
        <input type="text" name="" id="" v-model.number="num5" />+
        <input type="text" name="" id="" v-model.number="num6" />=
        <span>{{ num5 + num6 }}</span>
    
        <!-- 버튼과 calculate 함수를 이용한 sum 출력 -->
        <!-- <input type="number" name="" id="" v-model="sum" /> -->
        <!-- <button @click="calculate">계산</button> -->
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          userId: 'jeremy',
          num1: 0,
          num2: 0,
          num3: 0,
          num4: 0,
          num5: 0,
          num6: 0,
          sum: 0
        }
      },
      methods: {
        myFunction() {
          console.log(this.userId)
        },
        changeData() {
          this.userId = 'gildong'
        },
        calculate() {
          this.sum = this.num1 + this.num2
        }
      }
    }
    </script>

     

    [그림 3] Input 값으로 6개 모두 5를 입력하였다.

     


     

    select

    select 태그에서의 v-model은 value 속성과 양방향으로 연결되어 있다.

    // src/views/1_databinding/DataBindingSelectView.vue
    <template>
      <div>
        <select name="" id="" v-model="selectedCity">
          <option value="02">서울</option>
          <option value="051">부산</option>
          <option value="064">제주</option>
        </select>
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          // 기본값 "서울"
          selectedCity: '02'
        }
      }
    }
    </script>

     

    [그림 4] select 박스

     


     

    checkbox

    checkbox 타입에서 v-model은 checked 속성과 양방향으로 바인딩되어 있다. value는 이미 값이 세팅되어 있다.

    <!-- src/views/1_databinding/DataBindingCheckboxView.vue -->
    <template>
      <div>
        <div>
          <!-- Vue에서는 name이 아닌 v-model로 checkbox 그룹을 만든다. -->
          <input
            type="checkbox"
            name=""
            id="html"
            value="HTML"
            v-model="favoriteLang"
          />
          <label for="html">HTML</label>
        </div>
        <div>
          <input
            type="checkbox"
            name=""
            id="css"
            value="CSS"
            v-model="favoriteLang"
          />
          <label for="css">CSS</label>
        </div>
        <div>
          <input
            type="checkbox"
            name=""
            id="js"
            value="JS"
            v-model="favoriteLang"
          />
          <label for="js">JS</label>
        </div>
        <div>선택한 언어: {{ favoriteLang }}</div>
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          favoriteLang: [] // checkbox는 여러 값을 선택할 수 있기 때문에 배열을 만든다.
        }
      }
    }
    </script>

     

    [그림 5]를 통해 볼 때 Vue에서 체크박스는 체크한 순서대로 배열에 값이 담긴다. 만약 자바스크립트에서 이 같은 기능을 구현하고자 한다면 복잡한 코드가 필요하다. 사용자가 어떤 항목을 우선적으로 생각하는지에 대한 정보를 얻을 수 있다.

    [그림 5] CSS > JS > HTML 순으로 체크하였다.

     


     

    radio

    <!-- src/views/1_databinding/DataBindingRadioView.vue -->
    <template>
      <div>
        <div>
          <input
            type="radio"
            name=""
            id="html"
            value="HTML"
            v-model="favoriteLang"
          />
          <label for="html">HTML</label>
        </div>
        <div>
          <input type="radio" name="" id="css" value="CSS" v-model="favoriteLang" />
          <label for="css">CSS</label>
        </div>
        <div>
          <input type="radio" name="" id="js" value="JS" v-model="favoriteLang" />
          <label for="js">JS</label>
        </div>
        <div>선택한 언어: {{ favoriteLang }}</div>
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          favoriteLang: 'HTML' // 값이 하나이기 때문에 문자열, 기본값 'HTML'로 설정
        }
      }
    }
    </script>

     

    [그림 6] radio 버튼

     


     

    attribute

    "v-bind:"을 이용해 속성을 단방향 바인딩할 수 있다.

    <!-- src/views/1_databinding/DataBindingAttributeView.vue -->
    <template>
      <div>
        <!-- 속성의 단방향 설정은 v-bind: 을 붙이면 된다. -->
        <input type="text" name="" id="" v-bind:value="userId" readonly />
        <!-- v-bind는 생략 가능하며 실무에서는 생략하여 쓴다. -->
        <input type="text" name="" id="" :value="userId" readonly />
        <br />
        <!-- src 속성을 단방향으로 바인딩하였다. -->
        <img :src="imgSrc" alt="" style="width: 100px; height: auto" />
        <br />
        <!-- input:searh는 양방향 바인딩함. -->
        <input type="search" name="" id="" v-model="txt1" />
        <!-- button은 단방향 바인딩 처리함. 단방향의 경우 "txt1 === ''"와 같은 표현식을 쓸 수 있다. -->
        <button :disabled="txt1 === ''">조회</button>
        <br />
        <input
          type="checkbox"
          name="chkEmail"
          id=""
          value=""
          v-model="agreeEmail"
        />
        <label for="">이메일 보내기 동의</label>
        <br />
        <!-- v-show가 true일때 input:email을 보여준다. -->
        <input type="email" name="" id="" v-show="agreeEmail.length > 0" />
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          userId: 'jeremy',
          txt1: '',
          agreeEmail: [], // checkbox는 하나이더라도 배열을 선언한다.
          imgSrc: 'https://kr.vuejs.org/images/logo.png'
        }
      }
    }
    </script>

     

    [그림 7] attribute의 단방향 바인딩 예

     


     

    style

    Vue에서 style은 오브젝트를 바인딩하는 것이 유리하다. 바인딩한다는 것은 프로그램적으로 변경 가능성이 있는 경우를 고려하는 것이다. 그렇지 않을 경우에는 직접 입력하면 되기 때문에 굳이 바인딩할 필요가 없다. 이런 관점에서 style과 class는 object를 바인딩하는 방식을 사용한다.

    <!-- src/views/1_databinding/DataBindingStyleView.vue -->
    <template>
      <!-- div에 직접 적용 -->
      <div style="color: red; font-size: 30px">
        스타일 바인딩: 글씨는 red, 폰트 크기: 30px
      </div>
      <!-- 문자열 바인딩 -->
      <div :style="style1">스타일 바인딩: 글씨는 red, 폰트 크기: 30px</div>
      <!-- object 이용. 이 방식을 주로 사용한다. -->
      <div :style="style2">스타일 바인딩: 글씨는 red, 폰트 크기: 30px</div>
      <button @click="style2.color = 'blue'">색상바꾸기</button>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          style1: 'color: red; font-size: 30px', // 문자열 바인딩이 가능하지만 특정 속성만 변경하고자 할때 어려울 수 있다.
          // style 바인딩은 object로 하는 것이 가장 좋다. 키는 카멜 표현식을 사용한다.
          style2: {
            color: 'red',
            fontSize: '30px'
          }
        }
      }
    }
    </script>

     

    [그림 8] "색상바꾸기" 버튼 클릭 시 red에서 blue로 변경된 style

     


     

    class

    <!-- src/views/1_databinding/DataBindingClassView.vue -->
    <template>
      <div>
        <div :class="class1">클래스 바인딩1</div>
        <!-- class는 배열로 바인딩 처리 할 수 있지만 object의 불리언 값을 이용하는 것이 유리하다. 따라서 실무에서는 "클래스 바인딩2"와 같은 방식을 사용한다. -->
        <!-- 키가 두단어 이상일때는 ''로 묶어 줘야 한다. 한다어 일때는 그럴 필요가 없다. -->
        <div :class="{ active: isActive, 'text-red': hasError }">
          클래스 바인딩2
        </div>
      </div>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          class1: ['active', 'text-red'],
          // isActive가 true라서 .active가 적용된다.
          isActive: true,
          // hasError가 false라서 .text-red가 적용되지 않는다.
          hasError: false
        }
      }
    }
    </script>
    <!-- scoped 속성을 주지 않으면 global로 적용된다. 현재 vue 파일내에서만 사용하기
    위해 scoped를 붙인다. -->
    <style scoped>
    .active {
      background-color: green;
      font-weight: bold;
    }
    .text-red {
      color: red;
    }
    </style>

     

    [그림 9] class 속성의 바인딩

     


     

    list

    <!-- src/views/1_databinding/DataBindingListView.vue -->
    <template>
      <div>
        <select name="" id="">
          <option :value="city.code" :key="city.code" v-for="city in cities">
            {{ city.title }}
          </option>
        </select>
      </div>
      <dir>
        <br />
        <button @click="doSearch">조회</button>
        <table>
          <thead>
            <tr>
              <th>제품번호</th>
              <th>제품명</th>
              <th>가격</th>
              <th>재고</th>
            </tr>
          </thead>
          <tbody>
            <!-- <tr :key="drink.drinkId" v-for="drink in drinkList"> -->
            <!-- key로 삼을 유일한 값이 없을 경우 인덱스 번호 i를 넣어 줄 수 있다. -->
            <tr :key="i" v-for="(drink, i) in drinkList">
              <td>{{ drink.drinkId }}</td>
              <td>{{ drink.drinkName }}</td>
              <td>{{ drink.price }}</td>
              <td>{{ drink.qty }}</td>
            </tr>
          </tbody>
        </table>
      </dir>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          cities: [
            { title: '서울', code: '02' },
            { title: '부산', code: '051' },
            { title: '제주', code: '064' }
          ],
          drinkList: []
        }
      },
      setup() {},
      created() {},
      mounted() {},
      unmounted() {},
      methods: {
        doSearch() {
          this.drinkList = [
            {
              drinkId: '1',
              drinkName: '코카콜라',
              price: 700,
              qty: 1
            },
            {
              drinkId: '2',
              drinkName: '오렌지주스',
              price: 1200,
              qty: 1
            },
            {
              drinkId: '3',
              drinkName: '커피',
              price: 500,
              qty: 1
            },
            {
              drinkId: '4',
              drinkName: '물',
              price: 700,
              qty: 1
            },
            {
              drinkId: '5',
              drinkName: '보리차',
              price: 1200,
              qty: 1
            },
            {
              drinkId: '6',
              drinkName: '포카리',
              price: 1000,
              qty: 1
            },
            {
              drinkId: '7',
              drinkName: '뽀로로',
              price: 1300,
              qty: 1
            }
          ]
        }
      }
    }
    </script>
    <style scoped>
    table {
      width: 100%;
      border-collapse: collapse;
    }
    
    tr,
    th,
    td {
      border: 1px solid black;
      border-collapse: collapse;
    }
    </style>

     

    [그림 10] "조회" 버튼 클릭시 table에 데이터가 그려진다.

     

    json-server로부터 데이터를 가져와 table을 그린다.

    <template>
      <dir>
        <button @click="doSearch2">조회</button>
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Company</th>
              <th>Gender</th>
              <th>Email</th>
              <th>Phone</th>
              <th>Addres</th>
            </tr>
          </thead>
          <tbody>
            <tr :key="customer.id" v-for="customer in customerList">
              <td>{{ customer.name }}</td>
              <td>{{ customer.company }}</td>
              <td>{{ customer.gender }}</td>
              <td>{{ customer.email }}</td>
              <td>{{ customer.phone }}</td>
              <td>{{ customer.address }}</td>
            </tr>
          </tbody>
        </table>
      </dir>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          customerList: []
        }
      },
      methods: {
        async doSearch2() {
          const resource = 'http://localhost:3000/customers'
          const res = await fetch(resource)
          const resJson = await res.json()
          console.log(resJson)
          this.customerList = resJson
        }
      }
    }
    </script>
    <style scoped>
    table {
      width: 100%;
      border-collapse: collapse;
    }
    
    tr,
    th,
    td {
      border: 1px solid black;
      border-collapse: collapse;
    }
    </style>

     

    [그림 11] "조회" 버튼 클릭시 json-server로 부터 데이터를 가져온다.

     

    v-for를 통해서 html 태그 안에서 for문을 돌리면서 html 요소를 핸들링할 수 있다. 양방향 데이터 바인딩이 되어 있기 때문에 다시 렌더링 하는 화면을 짤 필요 없이 데이터 변경이 일어나면  바로 반영이 된다.

    <template>
      <dir>
        <table>
          <thead>
            <tr>
              <th>제품번호</th>
              <th>제품명</th>
              <th>가격</th>
              <th>주문수량</th>
              <th>합계</th>
            </tr>
          </thead>
          <tbody>
            <tr :key="i" v-for="(drink, i) in drinkList">
              <td>{{ drink.drinkId }}</td>
              <td>{{ drink.drinkName }}</td>
              <td>{{ drink.price }}</td>
              <td><input type="number" v-model="drink.qty" /></td>
              <td>{{ drink.price * drink.qty }}</td>
            </tr>
          </tbody>
        </table>
      </dir>
    </template>
    <script>
    export default {
      components: {},
      data() {
        return {
          drinkList: [
            {
              drinkId: '1',
              drinkName: '코카콜라',
              price: 700,
              qty: 1
            },
            {
              drinkId: '2',
              drinkName: '오렌지주스',
              price: 1200,
              qty: 1
            },
            {
              drinkId: '3',
              drinkName: '커피',
              price: 500,
              qty: 1
            },
            {
              drinkId: '4',
              drinkName: '물',
              price: 700,
              qty: 1
            },
            {
              drinkId: '5',
              drinkName: '보리차',
              price: 1200,
              qty: 1
            },
            {
              drinkId: '6',
              drinkName: '포카리',
              price: 1000,
              qty: 1
            },
            {
              drinkId: '7',
              drinkName: '뽀로로',
              price: 1300,
              qty: 1
            }
          ]
        }
      },
      methods: {}
    }
    </script>
    <style scoped>
    table {
      width: 100%;
      border-collapse: collapse;
    }
    
    tr,
    th,
    td {
      border: 1px solid black;
      border-collapse: collapse;
    }
    </style>

     

    [그림 12] 양방향 데이터바인딩에 의해 주문수량 입력시 합계가 변경된다.

     

    728x90
    반응형
    댓글