30일차 - Vue: Rendering, Event Handling, Form Input Bindings

변승훈·2022년 5월 9일
0

1. Rendering

1-1. 조건부 렌더링

1-1-1. v-if

v-if디렉티브는 조건에 따라 블록을 렌더링 할 때 사용한다. 블록은 디렉티브의 표현식이 true 값을 반환할 때만 렌더링 된다.
v-if는 전환 비용이 높으며 런타임 시 조건이 변경되지 않는다면 사용하는 것이 좋다.

<h1 v-if="awesome">Vue is awesome!</h1>

v-else와 함께 “else 블록”을 추가하는 것도 가능하다.

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

다음은 v-if, v-else-if, v-else로 구성된 예시이다.
버튼을 눌렀을 때 true, false, count의 숫자 증가에 따른 조건의 예시이며 이해를 하는데 참고해 보자!

<template>
  <button @click="handler">
    Click me!
  </button>
  <h1 v-if="isShow">
    Hello?!
  </h1>
  <h1 v-else-if="count > 3">
    Count > 3
  </h1>
  <h1 v-else>
    Good~
  </h1>
</template>
<script>
export default {
  data() {
    return {
      isShow: true,
      count: 0
    }
  },
  methods: {
    handler() {
      this.isShow = !this.isShow
      this.count += 1
    }
  }
}
</script>

또 다른 예시로 template를 사용했을 때 이다.
최상위 template 아래의 templatev-if를 사용하게 되면 template는 화면에 렌더링 되지 않는다!

<template>
  <button @click="handler">
    Click me!
  </button>
  <template v-if="isShow">
    <h1>Title</h1>
    <p>Paragraph1</p>
    <p>Paragraph2</p>
  </template>
</template>
<script>
export default {
  data() {
    return {
      isShow: true,
      count: 0
    }
  },
  methods: {
    handler() {
      this.isShow = !this.isShow
      this.count += 1
    }
  }
}
</script>

1-1-2. v-show

엘리먼트를 조건에 따라 표시하기 위한 또 다른 방법으로 v-show디렉티브가 있다.
사용법은 v-if와 거의 동일하다.
차이점은 v-show가 있는 엘리먼트는 조건에 상관 없이 항상 렌더링 되고 DOM(html)에 남아있다는 점이다. v-show는 단순히 엘리먼트에 display CSS 속성을 토글한다.
v-show는 초기 렌더링 비용이 높으며 무언가를 자주 전환해야 할 때 사용하는 것이 좋다.

<h1 v-show="ok">안녕하세요!</h1>

다음의 예시는 버튼을 눌렀을 때 v-show를 이용하여 display 속성의 값이 토글 되는 것을 확인할 수 있는 예제이다.

<template>
  <button @click="handler">
    Click me!
  </button>
  <h1 v-show="isShow">
    Hello?!
  </h1>
</template>
<script>
export default {
  data() {
    return {
      isShow: false,
      count: 0
    }
  },
  methods: {
    handler() {
      this.isShow = !this.isShow
      this.count += 1
    }
  }
}
</script>

1-2. 리스트 렌더링

1-2-1. v-for

v-for디렉티브는 객체의 속성을 반복할 때 사용한다.

다음 예시는 fruits라는 배열을 이용한 예시이다.
숫자 데이터 필요 시(index) 반복될 item을 소괄호로 묶고 뒤에',' 를 적고 변수를 선언하여 사용할 수 있다.

<template>
  <ul>
    <li
      v-for="(f, i) in fruits"
      :key="f">
      {{ f }}-{{ i + 1 }}
    </li>
  </ul>
</template>
<script>
export default {
  data() {
    return {
      fruits: ['Apple', 'Banana', 'Cherry']
    }
  }
}
</script>

또 다른 예시이다.
v-for로 배열 출력 시 배열의 아이템들을 고유하게 구분할 수 있는 특정 속성이 있어야 한다.
이 예시에서는 npm i -D shortid라는 각각의 id를 고유하게 만들어주는 패키지를 설치하여 진행했다.
또한, 버튼을 눌러 'Orange'를 배열에 추가시키는 메소드도 사용했다.

<template>
  <button @click="handler">
    Click me!
  </button>
  <ul>
    <li
      v-for="{id, name} in newFruits"
      :key="id">
      {{ name }}-{{ id }}
    </li>
  </ul>
</template>
<script>
import shortid from 'shortid'

export default {
  data() {
    return {
      fruits: ['Apple', 'Banana', 'Cherry']
    }
  },
  computed: {
    newFruits() {
      return this.fruits.map(fruit => ({
          id: shortid.generate(),
          name: fruit
      }))
    }
  },
  methods: {
    handler() {
      this.fruits.push('Orange')
    }
  }
}
</script>

1-2-2. 배열 변경 감지

  1. 변이 메소드
    Vue는 감시중인 배열의 변이 메소드를 래핑하여 뷰 갱신을 트리거힌다.
    배열의 데이터를 바꿔 놓으면 화면이 갱신되서 출력이 될 수 있는 반응성을 가지고 있다는 말과 같다.

래핑된 메소드는 다음과 같다.

  • push(): 배열데이터 가장 뒤쪽에 data를 밀어 넣는다.
  • unshift(): 배열데이터 가장 앞쪽에 data를 밀어 넣는다.
  • pop(): 배열데이터 가장 뒤쪽의 data를 반환해준다.
  • shift(): 배열데이터 가장 앞쪽의 data를 반환해준다.
  • splice(): index를 이용해서 데이터를 넣거나 빼거나 삭제할 때 사용한다.
  • sort(): 배열을 정렬해준다.
  • reverse(): 배열을 뒤집어 정렬해준다.

콘솔을 열고 이전 예제의 items 배열로 변이 메소드를 호출하여 재생할 수 있다.

example1.items.push({ message: 'Baz' })
  1. 배열 교체
    이름에서 알 수 있듯 변이 메소드는 호출된 원본 배열을 변형한다. 이와 비교하여 변형을 하지 않는 방법도 있다. 바로 filter(), concat() 와 slice() 이다. 이 방법을 사용하면 원본 배열을 변형하지 않고 항상 새 배열을 반환한다. 변형이 없는 방법으로 작업할 때 이전 배열을 새 배열로 바꿀 수 있다.
example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

이렇게 하면 Vue가 기존 DOM을 버리고 전체 목록을 다시 렌더링 한다고 생각할 수 있다. 하지만 그렇지는 않다. Vue는 DOM 요소 재사용을 극대화하기 위해 몇가지 똑똑한 구현을 하므로 배열을 겹치는 객체가 포함된 다른 배열로 대체하여 효율적이다.

2. Event Handling

2-1. 이벤트 청취

v-on 디렉티브는 @로 줄여서 사용할 수 있고, DOM 이벤트를 듣고 트리거 될 때 JavaScript를 실행할 수 있다.

<div id="example-1">
  <button @click="counter += 1">Add 1</button>
  <p>위 버튼을 클릭한 횟수는 {{ counter }} 번 입니다.</p>
</div>
Vue.createApp({
  data() {
    return {
      counter: 0
  	}
  }
}).mouont('#example-1')

2-2. 메소드 이벤트 핸들러

많은 이벤트 핸들러의 로직은 더 복잡할 것이므로, JavaScript를 v-on 속성 값으로 보관하는 것은 간단하지 않다. 이 때문에 v-on이 호출하고자 하는 메소드의 이름을 받는 이유이다.

<div id="example-2">
  <!-- `greet`는 메소드 이름으로 아래에 정의되어 있습니다 -->
  <button @click="greet">Greet</button>
</div>
Vue.createApp({
	data() {
      return {
        name: 'Vue.js'
      }
    },
  	methods: {
      greet(e) {
        // 메소드 안에서 사용하는 'this'는 Vue 인스턴스를 가리킨다.
        console.log('Hello' + this.name + '!')
        // 'event'는 네이티브 DOM 이벤트
        if(e) {
          console.log(e.target.tagName)
        }
      }
    }
}).mount('#example-2')

예시를 보자
첫 번째 예시에서는 메소드 호출 시 인수를 넣어주지 않으면 event객체가, 인수를 넣으면 인수가 매개변수로 받아지는 예시이다.
인수 뒤쪽에 ','를 적어주고 $event객체를 직접적으로 명시가 가능하다.

<template>
<button @click="handler">
    Click 1
  </button>
  <button @click="handler('hi', $event)">
    Click 2
  </button>
</template>
<script>
export default {
  methods: {
    handler(msg, e) {
      // 인수를 넣어주지 않으면 event객체가, 인수를 넣으면 인수가 매개변수로 받아진다.
      console.log(msg)
      console.log(e)
    }
  }
}
</script>

두 번째 예시는 ','를 통해 여러개의 함수나 메소드를 동시에 실행할 수 있는 것을 보여주는 예시이다. 이때 ()는 꼭 써줘야 정상적으로 동작한다.

<template>
  <button @click="handlerA(), handlerB()">
    Click me!
  </button>
</template>
<script>
export default {
  methods: {
    handlerA() {
     console.log('A')
    },
    handlerB() {
     console.log('B')
    }
  }
}
</script>

2-3. 이벤트 수식어

이벤트 핸들러 내부에서 event.preventDefault() 또는 event.stopPropagation()를 호출하는 것은 매우 보편적인 일이다.
메소드 내에서 쉽게 이 작업을 할 수 있지만, DOM 이벤트 세부 사항을 처리하는 대신 데이터 로직에 대한 메소드만 사용할 수 있으면 더 좋을 것이다.

이 문제를 해결하기 위해, Vue는 v-on 이벤트에 이벤트 수식어를 제공하며 수식어는 점으로 표시된 접미사 이다.

  • .stop: 이벤트 버블링(특정 요소 선택 시 그 요소를 포함하고 있는 부모 요소에도 이벤트가 전파되는 것)을 방지하기 위해 사용한다.(stopPropagation())
  • .capture: 이벤트 캡쳐링(특정 요소 선택 시 그 요소를 포함하고 있는 자식 요소에도 이벤트가 전파되는 것)
  • .prevent: 특정 이벤트 발생 시 기본 동작을 방지한다. (preventDefault())
  • .once: 특정 이벤트 발생 시 해당하는 메소드를 단 한번만 실행한다.
  • .self: 정확한 영역(노출된 부분을)을 선택해야 이벤트가 발생한다.
  • .passive: 로직과 화면에 변화를 독립 시켜 부하를 줄여준다.
<!-- 클릭 이벤트 전파가 중단됩니다 -->
<a @click.stop="doThis"></a>

<!-- 제출 이벤트가 페이지를 다시 로드 하지 않습니다 -->
<form @submit.prevent="onSubmit"></form>

<!-- 수식어는 체이닝 가능합니다 -->
<a @click.stop.prevent="doThat"></a>

<!-- 단순히 수식어만 사용할 수 있습니다 -->
<form @submit.prevent></form>

<!-- 이벤트 리스너를 추가할 때 캡처모드를 사용합니다 -->
<!-- 즉, 내부 엘리먼트를 대상으로 하는 이벤트가 해당 엘리먼트에서 처리되기 전에 여기서 처리합니다. -->
<div @click.capture="doThis">...</div>


<!-- event.target이 엘리먼트 자체인 경우에만 트리거를 처리합니다 -->
<!-- 자식 엘리먼트에서는 안됩니다 -->
<div @:click.self="doThat">...</div>
  1. once와 prevent에 대한 예시이다.
<template>
  <a
    href="https://naver.com"
    target="_blank"
    @click.once.prevent="handler">
    NAVER
  </a>
</template>
<script>
export default {
  methods: {
    handler(e) {
      console.log('ABC!')
    }
  }
}
</script>
  1. stop과 capture에 대한 예시이다.
<template>
  <div
    class="parent"
    @click="handlerA"
    >
  <!-- <div
    class="parent"
    @click.capture="handlerA"
    > -->
    <div
      class="child"
      @click.stop="handlerB"></div>
    <!-- <div
      class="child"
      @click="handlerB"></div> -->
  </div>
</template>
<script>
export default {
  methods: {
    handlerA() {
      console.log('A')
    },
    handlerB() {
      console.log('B')
    }
  }
}
</script>
<style scoped lang="scss">
.parent {
  width: 200px;
  height: 100px;
  background-color: royalblue;
  margin: 10px;
  padding: 10px;
  .child {
    width: 100px;
    height: 100px;
    background-color: orange;
  }
}
</style>
  1. self와 passive에 대한 예시이다.
<template>
  <!-- <div
    class="parent"
    @click.self="handlerA"
    > -->
  <div
    class="parent"
    @wheel.passive="handler"
    >
    <div
      class="child"></div>
  </div>
</template>
<script>
export default {
  methods: {
    handler(e) {
      for(let i = 0 ; i < 10000 ; i += 1)
      console.log(e)
    },
    handlerA() {
      console.log('A')
    },
    handlerB() {
      console.log('B')
    }
  }
}
</script>
<style scoped lang="scss">
.parent {
  width: 200px;
  height: 100px;
  background-color: royalblue;
  margin: 10px;
  padding: 10px;
  overflow: auto;
  .child {
    width: 100px;
    height: 2000px;
    background-color: orange;
  }
}
</style>

2-4. 키 수식어

키보드 이벤트를 청취할 때, 종종 공통 키 코드를 확인해야 한다. Vue는 키 이벤트를 수신할 때v-on에 대한 키 수식어를 추가할 수 있다.

<input @keyup.enter="submit">

다음 수식어들을 사용하여 해당 수식어 키가 눌러진 경우에만 마우스 또는 키보드 이벤트 리스너를 트리거 할 수 있다.

  • .enter
  • .tab
  • .delete (“Delete” 와 “Backspace” 키 모두를 캡처한다.)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
  • .ctrl
  • .alt
  • .shift

다음의 예시에서 간단히 사용해보았다.

<template>
  <input 
    type="text"
    @keydown.ctrl.shift.a="handler" />
    <!-- @keydown.enter="handler" /> -->
</template>
<script>
export default {
  methods: {
    handler() {
      console.log('!!')
    }
  }
}
</script>

3. Form Input Bindings

v-model 디렉티브를(:) 사용하여 폼 input과 textarea 엘리먼트에 양방향 데이터 바인딩을 생성할 수 있다.

  1. 첫 번째 예시는 v-model디렉티브를 사용하지 않은 단방향 데이터 바인딩이다.
    이때, 데이터는 갱신이 되지 않는다.
<template>
  <h1>{{ msg }}</h1>
  <input 
    type="text" 
    :value="msg" />
</template>
<script>
export default {
  data() {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>
  1. 두 번째 예시는 v-model디렉티브를 이용하지 않은 양방향 데이터 바인딩이다.
<template>
  <h1>{{ msg }}</h1>
  <input 
    type="text" 
    :value="msg"
    @input="handler" />
</template>
<script>
export default {
  data() {
    return {
      msg: 'Hello world!'
    }
  },
  methods: {
    handler(e) {
      // 입력된 데이터가 출력
      console.log(e.target.value)
      this.msg = e.target.value
    }
  }
}
</script>
  1. 세 번째 예시는 v-model디렉티브를 이용한 양방향 데이터 바인딩이다.
<template>
  <h1>{{ msg }}</h1>
  <input 
    type="text" 
    v-model="msg" />
  <h1>{{ checked }}</h1>
  <input 
    type="checkbox" 
    v-model="checked" />
</template>
<script>
export default {
  data() {
    return {
      msg: 'Hello world!',
      checked: false
    }
  }
}
</script>

이때 v-model사용 시 한글을 작성하면 하나의 글자가 작성될 때 까지 나타나지 않는다.
한글을 사용하려면 위의 v-model="msg" 대신
:value="msg" @input="msg = $event.target.value"을 사용해야 한다.

v-model의 수식어

  • .lazy: 다 입력되고 나서야 갱신이 되게 할 때 사용한다.(change, focus가 해제 되어야 갱신)
  • .number: 숫자 데이터로 유지되어야 할 때 사용한다.
  • .trim: 양 쪽 공백을 제거할 때 사용한다.
<!-- v-model.number="msg" 
    v-model.trim="msg"  -->
<template>
  <h1>{{ msg }}</h1>
  <input 
    type="text" 
    v-model.lazy="msg" 
    />
</template>
<script>
export default {
  data() {
    return {
      msg: 'Hello world!'
      // msg: '123'
    },
    watch: {
      msg() {
        console.log(this.msg)
      }
    }
  }
}
</script>
profile
잘 할 수 있는 개발자가 되기 위하여

0개의 댓글