하다보니

Vue 기초 - 인스턴스, 데이터 바인딩, 조건부 렌더링, 이벤트 핸들링 본문

프로그래밍 언어/Vue

Vue 기초 - 인스턴스, 데이터 바인딩, 조건부 렌더링, 이벤트 핸들링

claire 2023. 4. 10. 17:35

뷰 인스턴스 생성하기

모든 vue 앱은 Vue 함수로 새 vue 인스턴스를 만드는 것부터 시작한다. vue 인스턴스를 생성할 때는 options 객체를 전달하여야 한다. 

vue 앱은 new Vue를 통해 만들어진 루트 Vue 인스턴스로 구성되며 선택적으로 중첩이 가능하고 재사용이 가능한 컴포넌트 트리로 구성된다. 

new Vue({
    el:'#app',
    data:{
    	number:1,
        show:false
    },
    methods:{
    	increaseNumber(){
        	this.number++
        },
        toggle(){
        	this.show=!this.show
        },
    }
})

뷰 데이터와 메소드

vue 인스턴스가 생성될 때 data 객체에 있는 모든 속성이 vue의 반응형 시스템에 추가된다. 각 속성값이 변경될 때 뷰가 반응하여 새로운 값과 일치하도록 업데이트 된다. 

//데이터 객체
let data={a:1}


//Vue 인스턴스에 데이터 객체를 추가한다. 
let vm=new Vue({
  data: data
})

//인스턴스에 있는 속성은 원본 데이터에 있는 값을 반환한다. 
vm.a===data.a  //=> true

//인스턴스에 있는 속성값을 변경하면 원본 데이터에도 영향을 미친다. 
vm.a=2
data.a. //=>2로 변경

//반대도 마찬가지이다. 
data.a=3
vm.a. //=> 3

데이터가 변경되면 화면은 다시 렌더링 됨. 주의할 점은 data에 있는 속성들은 인스턴스가 생성될 때 존재한 것들만 반응형이라는 것이다. 

만약 vm.b='hello'와 같이 새 속성을 추가하면 b가 변경되어도 화면이 갱신되지 않는다. 

 

따라서 어떤 속성이 나중에 필요하다는 것을 안다면 초기값을 지정할 필요가 있다. 

data: {
  newTodoText: '',
  visitCount: 0,
  hideCompletedTodos: false,
  todos: [],
  error: null
}

( Object.feeze() 를 사용하면 기존 속성이 변경되는 것을 막아 반응형 시스템이 추적할 수 없다. )

아래 obj는 변경 불가. 

let obj={
  foo:'bar'
}

Object.freeze(obj)

new Vue({
  el:'#app',
  data:obj
})

Vue 인스턴스는 데이터 속성 외에도 유용한 인스턴스 속성 및 메소드를 제공한다. 

 

options 속성이나 콜백에 created:()=>console.log(this.a) 와 같은 화살표 함수를 지양해라. 화살표 함수는 this를 가지지 않아 화살표 함수에서의 this는 다른 변수로 취급되거나 렉시컬하게 호출한 변수를 발견할 때까지 부모 스코프에서 해당 변수를 찾는다. 따라서 이 때문에 Uncaught TypeError: Cannot read property of undefined 또는 Uncaught TypeError: this.myMethod is not a function와 같은 오류가 발생하게 된다. 

 

화살표 함수의 this 바인딩 
JavaScript에서는 어떤 식별자를 찾을 때 현재 환경에서 그 변수가 없으면 바로 상위 환경을 검색한다. 그렇게 점점 상위 환경으로 타고 올라가다 변수를 찾거나 가장 상위 환경에 도달하면 그만두게 되는 것.. 
화살표 함수에는 this라는 변수 자체가 존재하지 않기 때문에 그 상위 환경에서의 this를 참조하게 된다. 
화살표 함수는 선언될 시점에서의 상위 스코프가 this로 바인딩된다. 

 

데이터 바인딩

데이터 바인딩의 가장 기본 형태는 이중 중괄호를 사용한 텍스트 보간이다. 

<span>메시지: {{ msg }}</span>

이중 중괄호는 html 속성에서 사용할 수 없다. 대신 v-bind 디렉티브를 사용해라. 디렉티브는 v- 접두사가 있는 속성을 말한다. 인스턴스의 값의 변경에 따라 반응적으로 dom에 적용한다. v-bind는 :로 약식 표기가 가능하다. 

<div v-bind:id="dynamicId"></div>

isButtonDisabled 가 null, undefined, false값을 가지면 disabled 속성은 렌더링된 button 엘리먼트에 포함되지 않는다. 

<button v-bind:disabled="isButtonDisabled">Button</button>

실제로 vue.js는 모든 데이터 바인딩 내에서 javascript 표현식의 모든 기능을 지원한다. 하지만 각 바인딩에 하나의 단일 표현식만 포함될 수 있으므로 조건문과 같은 형태는 작동하지 않는다. 

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

//조건문은 작동하지 않는다. 삼항 연산자를 사용해야 한다. 
{{ if (ok) { return message } }}

 

이벤트 핸들링

v-on 디렉티브를 사용하여 dom 이벤트를 듣고 트리거 될 때 javascript를 실행할 수 있다. 

<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>위 버튼을 클릭한 횟수는 {{ counter }} 번 입니다.</p>
</div>
var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
})

보통은 메소드 이벤트 핸들러 형태로 사용. v-on이 호출하고자 하는 메소드의 이름을 받는다. 

 

이벤트 수식어

  • .stop : 클릭 이벤트 전파 중단
  • .prevent : submit 시 페이지 다시 로드 하지 않음
  • .capture : 내부 엘리먼트를 대상으로 하는 이벤트가 해당 엘리먼트에서 처리되기 전에 여기서 처리
  • .self : 자식 엘리먼트에선 트리거 처리 안함
  • .once : 클릭 이벤트는 최대 한번만 트리거 됨
  • .passive : 스크롤의 기본 이벤트를 취소할 수 없음 -> 이 이벤트가 기본 동작을 멈추지 않는다는 것을 브라우저에 알림

키 수식어

<input v-on:keyup.enter="submit">
<input v-on:keyup.enter="submit">
<input v-on:keyup.13="submit">

실습)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 기초 익히기</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{year}}<br />
      {{nextYear('안녕하세요')}}
      <input :type="type" :value="inputData" />
      <a :href="getKossieCoderLink('kossiecoder')">코지 코더 채널</a>
      <button v-on:click="alert">Click Me</button>
      <button v-on:click="plus">더하기</button>
      <button v-on:click="minus">빼기</button>
      <!-- 이벤트 수식어 -->
      <form v-on:submit.prevent="submit">
        <input type="text" /><br />
        <button type="submit">Submit</button>
      </form>
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          year: 2018,
          person: {
            name: "코지 코더",
            age: 34,
          },
          inputData: "hello",
          type: "text",
          link: "https://www.youtube.com/",
        },
        methods: {
          alert() {
            alert("hi 코지 코더");
          },
          getKossieCoderLink(channel) {
            return this.link + channel;
          },
          nextYear(greeting) {
            return (
              greeting +
              "! " +
              this.person.name +
              "는 내년에 " +
              (this.person.age + 1) +
              "살 입니다. "
            );
          },
          otherMethod() {
            this.nextYear();
          },
          plus() {
            this.year++;
          },
          minus() {
            this.year--;
          },
          submit() {
            alert("submitted");
            console.log("hellooo");
          },
        },
      });
    </script>
  </body>
</html>

양방향 바인딩 v-model

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 기초 익히기</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <form v-on:submit.prevent="submit">
        <!-- 양방향 바인딩이 필요할 땐 v-model 사용 -->
        <!-- <input type="text" :value="text" @keyup="updateText" /><br /> -->
        <input type="text" v-model="text" /><br />
        {{text}}<br />
        <button type="submit">Submit</button>
      </form>
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          year: 2018,
          text: "text",
        },
        methods: {
          plus() {
            this.year++;
          },
          minus() {
            this.year--;
          },
          submit() {
            alert("submitted");
            console.log("hellooo");
          },
          // updateText(event) {
          //   this.text = event.target.value;
          // },
        },
      });
    </script>
  </body>
</html>

computed와 watch

  • computed 속성
    템플릿 내에 표현식을 넣으면 편리하지만 간단한 연산일 때만 사용하는 것이 좋다. 너무 많은 연산을 템플릿 안에서 하면 코드가 비대해지고 유지보수가 어렵다. 
<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

위의 코드는 더이상 간단 명료하지 않다. 이는 computed 속성을 사용하여 해결할 수 있다. 

메소드를 호출해서 같은 결과를 얻을 수 있지만 computed 속성은 캐싱되어 해당 속성이 종속된 대상이 변경될 때만 함수를 실행한다. 즉, 아래에서 message가 변경되지 않는 한 computed 속성을 여러 번 요청해도 다시 계산하지 않고 계산되어 있던 결과를 즉시 반환한다. Date.now()와 같이 아무 곳에도 의존하지 않는 computed 속성의 경우는 절대로 업데이트 되지 않는다는 뜻이다. 

 

method는 렌더링 할 때마다 항상 실행. 

<body>
    <div id="app">
      <button @click="changeMessage">Click</button>
      {{reversedMessage}}
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          message: "helllooooo",
        },
        // 함수와 computed 차이는? computed 속성은 캐싱한다. 함수는 캐싱하지 않는다. computed 속성은 처음에 한번만 계산된다.
        methods: {
          // reversedMessage() {
          //   return this.message.split("").reverse().join("");
          // },
          changeMessage() {
            this.message = "승연입니다";
          },
        },

        computed: {
          reversedMessage() {
            return this.message.split("").reverse().join("");
          },
        },
      });
    </script>
  </body>

 

computed 속성 vs watch 속성

vue는 vue 인스턴스의 데이터 변경을 관찰하고 이에 반응하는 보다 일반적인 watch 속성을 제공한다. 하지만 명령적인 watch 콜백보다 computed 속성을 사용하는 것이 더 좋다.
watch 속성은 감시할 데이터를 지정하고 그 데이터가 바뀌면 이런 함수를 실행하라는 방식인 명령형 프로그래밍 방식
computed 는 계산해야 하는 목표 데이터를 정의하는 방식인 선언형 프로그래밍 방식

 

  • watch 속성
    대부분 computed 속성이 더 적합하지만 사용자가 만든 감시자가 필요한 경우가 있다. watch 옵션은 데이터 변경에 반응하는 보다 일반적인 방법으로 비동기식 또는 시간이 많이 소요되는 조작을 수행하려는 경우에 유용하다. 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 기초 익히기</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{message}}
      <button @click="changeMessage">Click</button>
      {{updated}}
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          message: "헬로우",
          updated: "아니요",
        },

        methods: {
          changeMessage() {
            this.message = "승연입니다";
          },
        },

        computed: {
          reversedMessage() {
            return this.message.split("").reverse().join("");
          },
        },
        watch: {
          // message라는 변수를 지켜보고 있다
          message(newVal, oldVal) {
            console.log(newVal, oldVal);
            this.updated = "네";
          },
        },
      });
    </script>
  </body>
</html>

클래스, 스타일 바인딩

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 기초 익히기</title>
    <style>
      .red {
        color: red;
      }
      .font-bold {
        font-weight: bold;
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- isRed가 false면 class에 안 들어가고 true이면 들어간다.  -->
      <!-- -가 있을 때는 ''로 묶어줘야 한다.  -->
      <div :class="{red: isRed,'font-bold':isBold}">hello</div>
      <!-- 스타일 바인딩 -->
      <div :style="{color:red,fontSize:size}">Hello</div>
      <button @click="update">click</button>
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          isRed: false,
          isBold: false,
          red: "red",
          size: "30px",
        },

        methods: {
          update() {
            this.isRed = !this.isRed;
            this.isBold = !this.isBold;
          },
        },
      });
    </script>
  </body>
</html>

조건부 렌더링

  • v-if 와 v-show
    이 둘의 차이는 v-show가 있는 엘리먼트는 항상 렌더링 되고 dom에 남아있다. v-show는 단순히 엘리먼트에 display css 속성을 토글한다. 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 기초 익히기</title>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- v-if가 true면 렌더링이 되고 false면 렌더링이 되지 않는다.  -->

      <!-- <div v-if="show">Yes</div> -->
      <!-- <div v-else>No</div> -->
      <!-- 만약 v-if 안에 여러개의 태그를 넣고 싶다면 template 사용 -->
      <!-- <template v-if="number===1">
        <div>1</div>
        <div>2</div>
        <div>3</div>
      </template>
      <div v-else-if="number===2">Hi</div>
      <div v-else>No</div> -->

      <!-- v-if와 v-show의 차이 : v-if는 토글 비용이 높고 v-show는 초기 렌더링 비용이 높다. 자주 바꾸기를 원하면 show-->
      <div v-show="show">Yes</div>
      <br />
      <button @click="increaseNumber">Increase</button>{{number}}
      <button @click="toggle">Toggle</button>
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          number: 1,
          show: false,
        },
        methods: {
          increaseNumber() {
            this.number++;
          },
          toggle() {
            this.show = !this.show;
          },
        },
      });
    </script>
  </body>
</html>

'프로그래밍 언어 > Vue' 카테고리의 다른 글

vue 중급 강의  (0) 2023.04.18
Vue 기초 - v-for, 컴포넌트, props, emit, slot  (0) 2023.04.11