[Vue 빠른시작] Template Refs(템플릿 참조) 예제 추가

[Vue 빠른시작] Template Refs(템플릿 참조) 예제 추가

예제 코드가 Options API ver, Composition API ver 순서대로 등장한다. 본문에 Composition API ver인 경우 추가 설명이 있으니 잘 봐야함



Vue의 선언적 렌더링 모델은 개발자가 직접 DOM에 접근해야 하는 대부분을 추상화하지만, 개발자가 DOM 엘리먼트에 직접 접근해야 하는 경우가 여전히 있을 수 있다. 이러한 필요성을 충족시키기 위해 ref라는 특별한 속성을 사용할 수 있다.

<input ref="input">

ref는 v-for장에서 언급한 key 속성과 유사한 특수 속성이다. 이를 통해 마운트된 특정 DOM 엘리먼트 또는 자식 컴포넌트 인스턴스에 직접적인 참조를 얻을 수 있다. 예를 들어 컴포넌트 마운트의 인풋에 초점을 맞추거나 엘리먼트에서 타사 라이브러리를 초기화하려는 경우, 프로그래밍 방식으로 조작하기 편리하다.



1. Accessing the Refs

// Options API ver
// ref 값은 this.$refs에 노출된다.
<script>
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
</script>

<template>
  <input ref="input">
</template>
// Composition API ver
// <script setup> 버전
<script setup>
import { ref, onMounted } from 'vue'

// 엘리먼트 참조를 위해 ref를 선언해야한다.
// 이름은 템플릿 ref 값과 일치해야 한다.
const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input">
</template>
// Composition API ver
// setup() 버전
<script>
import { ref, onMounted } from 'vue'

export default {
  setup() {
    const input = ref(null)

    onMounted(() => {
      input.value.focus()
    })

    return {
      input // 참조를 반환해야 한다
    }
  }
}
</script>

<template>
  <input ref="input">
</template>

컴포넌트가 마운트된 후에만 ref에 접근할 수 있다. ($refs.input / input) 이 템플릿 표현식에 있으면 첫 렌더링 시 null이다. 이는 첫 렌더링이 끝날 때까지 엘리먼트가 존재하지 않기 때문이다.



1-1. Composition API ver >> watch/watchEffect null 체크 필요!

템플릿 ref의 변경 사항을 감시하려는 경우 ref에 null 값이 있는 경우를 고려해야 한다.

watchEffect(() => {
  if (input.value) {
    input.value.focus()
  } else {
    // v-if에 의해 아직 마운트 되지 않았거나, 마운트 해제된 경우
  }
})



2. Refs inside v-for

v3.2.25 이상이어야 한다.

ref가 v-for내부에서 사용되면 ref값은 해당 엘리먼트를 포함하는 배열이 된다.

// Options API ver
<script>
export default {
  data() {
    return {
      list: [ '체다', '비비', '뚱땡이', '악귀' ]
    }
  },
  mounted() {
    console.log(this.$refs.items);
  }
}
</script>

<template>
  <ul>
    <li v-for="item in list" ref="items" :key="item">
      {{ item }}
    </li>
  </ul>
</template>
// Composition API ver
<script setup>
import { ref, onMounted } from 'vue'

const list = ref(['체다', '비비', '뚱땡이', '악귀' ])
const itemRefs = ref([])

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs" :key="item">
      {{ item }}
    </li>
  </ul>
</template>

ref 배열은 소스 배열과 동일한 순서를 보장하지 않는다.



3. Function Refs

문자열 키 대신 ref 속성을 함수에 바인딩할 수도 있다. 이 함수는 각 컴포넌트 업데이트 시 호출되며, 엘리먼트 참조를 저장할 위치에 대한 완전한 유연성을 제공한다. 이 함수는 첫번째 인자로 엘리먼트 참조를 받는다.

<input :ref="(el) => { /* el을 속성이나 ref에 할당 */ }">

:ref와 같이 동적 바인딩으로 사용하는 경우에는 ref의 이름 대신 함수를 전달할 수 있다. 엘리먼트가 마운트 해제되는 경우 인자는 null이다. 또 인라인 함수 대신 메서드를 사용할 수 있다.

// Options API ver
<script>
export default {
  methods: {
    handleRef(el) {
      console.log('엘리먼트 참조: ', el);

    }
  }
}
</script>

<template>
  <input :ref="handleRef">
</template>
// Composition API ver
<script setup>
import { ref, onMounted } from 'vue'

const handleRef = (el) => {
  console.log('엘리먼트 참조: ', el);
}
</script>

<template>
  <input :ref="handleRef">
</template>



4. Ref on Component

ref는 자식 컴포넌트에 사용할 수도 있다. 이 경우 ref는 컴포넌트 인스턴스를 참조한다.

// Options API ver
<script>
import Child from './HelloWorld.vue'

export default {
  components: {
    Child
  },
  mounted() {
    // this.$refs.child는 <Child />의 인스턴스를 가진다.
    console.log(this.$refs.child);
    // HelloWorld.vue 버튼에 ref="button"을 추가했다.
    console.log(this.$refs.child.$refs.button.innerText);
  }
}
</script>

<template>
  <Child ref="child" />
</template>
// Composition API ver
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

const child = ref(null)

onMounted(() => {
  console.log(child.value);
})
</script>

<template>
  <Child ref="child" />
</template>

(자식 컴포넌트가 옵션 API를 사용하거나 <'script setup>을 사용하지 않는 경우) 참조된 인스턴스는 자식 컴포넌트의 this와 동일하다. 즉, 부모 컴포넌트는 자식 컴포넌트의 모든 속성과 메서드에 대한 접근 권한을 갖는다. 이렇게 하면 상.하위(부모.자식) 간에 밀접하게 결합한 구현 세부 정보를 쉽게 만들 수 있으므로, 컴포넌트 참조는 반드시 필요할 때만 사용해야 한다. 대부분의 경우 우선적으로 표준 props를 사용하여 상.하위 상호 작용을 구현하고 인터페이스를 내보내야 한다.



4-1. Composition API ver >> 예외 체크!

<'script setup>을 사용하는 컴포넌트가 기본적으로 비공개라는 예외가 있다. <'script setup>을 사용하는 자식 컴포넌트를 부모 컴포넌트에서 참조하려는 경우, 자식 컴포넌트가 defineExpose 컴파일 매크로를 사용하여 선택한 인터페이스를 노출하지 않는 한 그 무엇에도 접근할 수 없다.(컴파일러 매크로이기 때문에 개발 환경 설정에 따라 lint 에러나 경고가 나올수 있다.)

// Composition API ver
// Child.vue
<script setup>
import { ref, defineExpose } from 'vue'
const a = 1
const b = ref(2)
const c = ref('하히후헤호')

defineExpose({
    a, b
})
</script>
<template>
</template>
// Composition API ver
// Parent.vue
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

const child = ref(null)

onMounted(() => {
  /*
    상위에서 템플릿 참조를 통해 컴포넌트의 인스턴스를 가져오면
    검색된 인스턴스는 ``{ a: number, b: number }``와 같다
    (일반 인스턴스에서처럼 ref는 자동으로 래핑 해제됨.)
  */
  console.log(child.value.a); // 출력 1
  console.log(child.value.b); // 출룍 2
  console.log(child.value.c); // undefined
})
</script>

<template>
  <Child ref="child" />
</template>
// Options API ver
// Child.vue
<script>
export default {
    // expose 옵션을 사용하여 하위 인스턴스에 대한 접근을 제한 할 수 있다.
    expose: ['publicData', 'publicMethod'],
    data() {
        return {
            publicData: 'foo',
            privateData: 'bar'
        }
    },
    methods: {
        publicMethod(location) {
            console.log(location, ' >> publicMethod');
        },
        privateMethod(location) {
            console.log(location, ' >> privateMethod');
        }
    }
}
</script>
<template>

</template>
// Options API ver
// Parent.vue
<script>
import Child from './Child.vue'

export default {
  components: {
    Child
  },
  mounted() {
    console.log('Parent >> publicData: ', this.$refs.child.publicData.value);
    //console.log('Parent >> privateData: ', this.$refs.child.privateData.value);
    this.$refs.child.publicMethod('Parent')
    //this.$refs.child.privateMethod('Parent')
  }
}
</script>

<template>
  <Child ref="child" />
</template>

실행화면



🔺 expose: ['publicData', 'publicMethod']에서 허용한 변수와 메서드에는 문제없이 접근 가능한 것을 확인 할 수 있다.


- [Vue warn]: Unhandled error during execution of mounted hook
- Uncaught TypeError: Cannot read properties of undefined (reading 'value')

🔺 허용하지 않은 privateData, privateMethod에 접근하려 하면 위와 같은 오류가 발생한다.




참조

Vue Docs