[Vue.js] 새로운 v-model 살펴보기#4 ($attrs, $listeners)

하수도키

·

2021. 2. 7. 16:18

728x90
반응형
SMALL

2021/01/22 - [개발일기/Vue.js] - [Vue.js] 새로운 v-model 살펴보기#1 (vue3에서 바뀐 점)

2021/01/28 - [개발일기/Vue.js] - [Vue.js] 새로운 v-model 살펴보기#2 (다수의 v-model 사용하기)

2021/01/31 - [개발일기/Vue.js] - [Vue.js] 새로운 v-model 살펴보기#3 (v-model custom modifiers)

  • 이번 포스팅에서는 $attrs 가 vue3에서 어떻게 변했는지 살펴보자.
  • vue2에서는 HTML attributes들만 포함했지만 vue3에서는 HTML attributes뿐 아니라 listeners, classes, styles까지 다 포함한다.
  • 이러한 개념을 배우면서 부모에서 자식 컴포넌트로 event, attributes들이 어떻게 전달되는지도 알아보자.

Vue 2, Vue 3에서 $attrs 비교하기(Differences between Vue 2 and Vue 3 in $attrs)

  • Vue 2에서는 $attrs은 부모가 class, style를 제외하고 모든 속성들을 자식에게 넘겨주는 컴포넌트 데이터의 속성이다. 또한 자식에서 부모에게 전달되는 이벤트를 모아놓은 $listeners 객체도 있다.
// Vue 2
<BaseInput 
  @click="handleClick" 
  @custom="handleCustom" 
  v-model="value"
  type="button"
  class="btn"
/>

// Inside BaseInput.vue
$listeners = {
  click: handleClick,
  custom: handleCustom,
  input: () => {}
}

$attrs = {
  type: 'button'
}
  • Vue 3에서는 $listeners 가 없어졌고 $attrs 안으로 이벤트들이 존재한다.
// Vue 3
<BaseInput 
  @click="handleClick" 
  @custom="handleCustom"
  v-model="value" 
  type="button"
  class="btn"
/>

// Inside BaseInput.vue
$attrs = {
  class: 'btn'
  type: 'button',
  onClick: handleClick,
  onCustom: handleCustom,
  'onUpdate:modelValue': () => { value = payload },
}
  • Vue 2와는 달리 on prefix가 이벤트에 붙었다. 그리고 Vue 3 v-model 로직인 onUpdate:modelValue가 Vue 2와는 다르다.
  • 그리고 여기는 HTML 모든 속성들이 담겨진다. (class, style, id, aria, col, row, type, src 등)

컴포넌트에 $attrs 연결하기(Binding $attrs to a component)

  • BaseInput 이라는 divlabel, input을 감싸는 컴포넌트를 만들면서 살펴보자.(예전 포스팅에서 만든 예제이다.)
// BaseInput.vue
<template>
  <div>
    <label>{{ label }}</label>
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>

<script>
export default {
  props: {
    modelValue: {
      type: [String, Number],
      default: ''
    },
    label: {
      type: String,
      default: null
    }
  }
}
</script>
  • 위에 만든 BaseInput 컴포넌트를 App.vue에 바로 사용해보자.
// App.vue
<template>
  <div id="app">
    <BaseInput
      v-model="email"
      label="Email:"
      type="email"
    />

    <pre>{{ email }}</pre>
  </div>
</template>

<script>
import { ref } from 'vue'
import BaseInput from './components/BaseInput'

export default {
  name: 'App',
  components: {
    BaseInput
  },
  setup () {
    const email = ref('')

    return {
      email
    }
  }
}
</script>
  • BaseInput에 input type을 email로 지정하기위해 type을 내려준다.
  • 아래 이미지를 보게되면 input 태그에 선언되지 않고 부모 div 태그에 선언된 것을 볼 수 있다. Vue 2랑 같은 현상이다.

  • class도 마찬가지다.
<BaseInput
v-model="email"
label="Email:"
type="email"
class="thicc"
/>

  • 이제 $attrs를 사용해서 제대로 input 태그에 선언해보자.
// BaseInput.vue
<template>
  <div>
    <label>{{ label }}</label>
    <input
      v-bind="$attrs"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    />

    <pre>{{ $attrs }}</pre>
  </div>
</template>
  • v-bind="$attrs" 코드를 추가하면 input 태그에 속성들이 선언된다.

  • 위 이미지와 같이 typeclass 둘다 input 에 선언된다.
  • Vue 2에서는 inputclassstyle은 선언되지 않지만 Vue 3에서는 input에 즉, $attrs를 사용하는 곳에 선언된다.
  • 하지만, 아직도 부모 div에도 같이 적용되고 있으니 이걸 없애보자.
export default {
  inheritAttrs: false,
  props: {
    modelValue: {
      type: [String, Number],
      default: ''
    },
    label: {
      type: String,
      default: null
    }
  }
}
  • inheritAttrs: false 이걸 추가하면 부모가 $attrs를 상속받지 않는다.

$attrs를 이용해 listeners를 연결하자.(Binding listeners thorugh our $attrs)

  • 지금까지 $attrs를 사용해 속성들을 바인딩했다. 이제 listeners를 바인딩해보자.
  • 현재 input event는 input 엘리먼트에 잘 연결되어있지만 만약 다른 native input event를 바인딩하고 싶은 경우 살펴보자.
  • 예를 들면 아래과 같이 @blur 이벤트를 바인딩 하고 싶을 경우
// App.vue
<BaseInput
  v-model="email"
  @blur="email = 'blurrr@its.cold'"
  label="Email:"
  type="email"
  class="thicc"
/>

  • Vue 2에서는 v-on="$listeners"를 사용해 이벤트를 바인딩해줬찌만 Vue 3에선느 위에서 사용한 $attrs 에 모두 포함되어있으므로 별도로 처리할 필요가 없다.

BaseInput 리팩토링(input 이벤트 리팩토링)

  • $attrs에 모든 이벤트들이 있지만 input 만 따로 선언되어 사용중이다. inputonInput으로 리팩토링해보자.
  • $attrsObject이므로 스프레드 연산자 사용이 가능하므로 아래와 같이 수정해보자.
// BaseInput.vue
<input
  v-bind="{
    ...$attrs,
    onInput: (event) => $emit('update:modelValue', event.target.value)
  }"
  :value="modelValue"
/>
  • 코드가 더 명확하고 깔끔해졌고, input과 같은 특정이벤트가 부모 컴포넌트에 의해 다시 덮어씌여질 위험도 없어졌다.(부모가 @input 이런걸 사용하지 않아도 되니까??)

결론

  • $attrs, $listeners 에 대한 개념이 부족해서 설명이 매끄럽지 못했지만, 자주 보면서 좀 더 공부하고 수정해 나가야 될것 같다.
  • Vue 2보다 확실히 Vue 3가 많이 편해진 것 같다.
728x90
반응형
LIST