
Vue.js에서는 ref를 사용하여 DOM 요소나 컴포넌트 인스턴스에 직접 접근할 수 있습니다.
ref는 특정 요소나 컴포넌트를 참조하기 위해 Vue의 템플릿에서 사용되는 속성입니다.
아래에서 ref의 사용법에 대해 자세히 설명하겠습니다.
ref 기본 개념
ref란?
ref는 Vue.js에서 DOM 요소나 자식 컴포넌트에 직접 접근하기 위한 메커니즘입니다. JavaScript의 document.getElementById()나 querySelector()와 유사한 역할을 하지만, Vue의 반응성 시스템과 통합되어 더 안전하고 효율적으로 사용할 수 있습니다.
언제 ref를 사용해야 할까?
- 포커스 관리: 입력 필드에 포커스 설정
- 스크롤 제어: 특정 요소로 스크롤 이동
- 애니메이션: DOM 요소에 직접 애니메이션 적용
- 외부 라이브러리 통합: 서드파티 라이브러리와 연동
- 자식 컴포넌트 메서드 호출: 부모에서 자식 컴포넌트 제어
Options API에서 ref 사용하기
기본 사용법
ref를 사용하여 DOM 요소에 접근하는 기본적인 방법입니다. ref 속성을 지정한 후, Vue 인스턴스에서 해당 참조를 사용할 수 있습니다.
<template>
<div>
<input ref="myInput" type="text" placeholder="여기에 입력하세요" />
<button @click="focusInput">Input에 포커스</button>
<button @click="clearInput">입력 내용 지우기</button>
<button @click="getInputValue">입력 값 가져오기</button>
</div>
</template>
<script>
export default {
methods: {
focusInput() {
this.$refs.myInput.focus(); // myInput에 포커스 설정
},
clearInput() {
this.$refs.myInput.value = ''; // 입력 내용 지우기
},
getInputValue() {
const value = this.$refs.myInput.value;
alert(`입력된 값: ${value}`);
}
},
mounted() {
// 컴포넌트가 마운트된 후에만 $refs에 접근 가능
console.log('Input 요소:', this.$refs.myInput);
}
};
</script>생명주기와 ref 접근 시점
<template>
<div>
<p ref="paragraph">이것은 단락입니다.</p>
</div>
</template>
<script>
export default {
beforeCreate() {
console.log('beforeCreate:', this.$refs); // undefined
},
created() {
console.log('created:', this.$refs); // undefined
},
beforeMount() {
console.log('beforeMount:', this.$refs); // undefined
},
mounted() {
console.log('mounted:', this.$refs.paragraph); // DOM 요소 접근 가능
}
};
</script>v-for 내부에서 ref 사용하기
v-for를 사용하여 반복적으로 생성된 요소에 ref를 설정할 수 있습니다. 이 경우, ref는 배열 형태로 저장됩니다.
<template>
<div>
<div v-for="(item, index) in items" :key="index">
<input
ref="inputRefs"
type="text"
:placeholder="item.placeholder"
v-model="item.value"
/>
</div>
<button @click="focusFirstInput">첫 번째 입력에 포커스</button>
<button @click="focusAllInputs">모든 입력에 순차적으로 포커스</button>
<button @click="getAllValues">모든 값 출력</button>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ placeholder: '첫 번째 입력', value: '' },
{ placeholder: '두 번째 입력', value: '' },
{ placeholder: '세 번째 입력', value: '' }
]
};
},
methods: {
focusFirstInput() {
if (this.$refs.inputRefs && this.$refs.inputRefs.length > 0) {
this.$refs.inputRefs[0].focus();
}
},
async focusAllInputs() {
for (let i = 0; i < this.$refs.inputRefs.length; i++) {
this.$refs.inputRefs[i].focus();
await new Promise(resolve => setTimeout(resolve, 1000)); // 1초 간격
}
},
getAllValues() {
const values = this.$refs.inputRefs.map(input => input.value);
console.log('모든 입력 값:', values);
}
}
};
</script>Composition API에서 ref 사용하기
기본 사용법
<template>
<div>
<input ref="inputRef" type="text" v-model="inputValue" />
<button @click="focusInput">포커스</button>
<button @click="selectAll">전체 선택</button>
<p>입력된 값: {{ inputValue }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 반응형 데이터
const inputValue = ref('')
// DOM 요소 참조 (null로 초기화)
const inputRef = ref(null)
const focusInput = () => {
inputRef.value?.focus()
}
const selectAll = () => {
inputRef.value?.select()
}
// 컴포넌트 마운트 후 DOM 요소에 접근 가능
onMounted(() => {
console.log('Input 요소:', inputRef.value)
// 자동으로 포커스 설정
inputRef.value?.focus()
})
</script>동적 ref 배열 관리
<template>
<div>
<button @click="addItem">아이템 추가</button>
<div v-for="(item, index) in items" :key="item.id">
<input
:ref="el => setItemRef(el, index)"
type="text"
:placeholder="`아이템 ${item.id}`"
v-model="item.value"
/>
<button @click="removeItem(index)">삭제</button>
</div>
<button @click="focusLastItem">마지막 아이템에 포커스</button>
</div>
</template>
<script setup>
import { ref, onBeforeUpdate } from 'vue'
const items = ref([
{ id: 1, value: '' },
{ id: 2, value: '' }
])
const itemRefs = ref([])
// 업데이트 전에 refs 배열 초기화
onBeforeUpdate(() => {
itemRefs.value = []
})
const setItemRef = (el, index) => {
if (el) {
itemRefs.value[index] = el
}
}
const addItem = () => {
const newId = Math.max(...items.value.map(item => item.id)) + 1
items.value.push({ id: newId, value: '' })
}
const removeItem = (index) => {
items.value.splice(index, 1)
}
const focusLastItem = () => {
const lastIndex = itemRefs.value.length - 1
if (lastIndex >= 0 && itemRefs.value[lastIndex]) {
itemRefs.value[lastIndex].focus()
}
}
</script>함수로 참조하기
ref를 함수로 설정하면, 해당 함수가 호출될 때마다 참조를 받을 수 있습니다. 이 방법은 동적 요소를 처리할 때 유용합니다.
<template>
<div>
<button @click="addInput">입력 추가</button>
<div v-for="(input, index) in inputs" :key="index">
<input
:ref="setInputRef"
type="text"
:placeholder="`입력 ${index + 1}`"
@keyup.enter="focusNext(index)"
/>
<button @click="removeInput(index)">삭제</button>
</div>
<button @click="focusLastInput">마지막 입력에 포커스</button>
<button @click="clearAllInputs">모든 입력 지우기</button>
</div>
</template>
<script>
export default {
data() {
return {
inputs: [''],
inputRefs: []
};
},
methods: {
addInput() {
this.inputs.push('');
},
removeInput(index) {
this.inputs.splice(index, 1);
this.inputRefs.splice(index, 1);
},
setInputRef(el) {
if (el) {
// 중복 방지
if (!this.inputRefs.includes(el)) {
this.inputRefs.push(el);
}
}
},
focusNext(currentIndex) {
const nextIndex = currentIndex + 1;
if (nextIndex < this.inputRefs.length) {
this.inputRefs[nextIndex].focus();
}
},
focusLastInput() {
if (this.inputRefs.length > 0) {
this.inputRefs[this.inputRefs.length - 1].focus();
}
},
clearAllInputs() {
this.inputRefs.forEach(input => {
input.value = '';
});
}
},
beforeUpdate() {
// 업데이트 전에 refs 배열 정리
this.inputRefs = [];
}
};
</script>컴포넌트에 ref 사용하기
ref를 사용하여 자식 컴포넌트에 접근할 수도 있습니다. 이를 통해 자식 컴포넌트의 메서드나 데이터를 호출할 수 있습니다.
자식 컴포넌트 정의
<!-- ChildComponent.vue -->
<template>
<div class="child-component">
<h3>자식 컴포넌트</h3>
<p>카운터: {{ counter }}</p>
<p>메시지: {{ message }}</p>
<button @click="increment">증가</button>
<input ref="childInput" type="text" v-model="message" />
</div>
</template>
<script>
export default {
name: 'ChildComponent',
data() {
return {
counter: 0,
message: '안녕하세요!'
};
},
methods: {
increment() {
this.counter++;
},
reset() {
this.counter = 0;
this.message = '';
},
focusInput() {
this.$refs.childInput.focus();
},
setMessage(newMessage) {
this.message = newMessage;
}
}
};
</script>부모 컴포넌트에서 자식 컴포넌트 제어
<template>
<div>
<ChildComponent ref="child" />
<div class="controls">
<button @click="callChildMethod">자식 컴포넌트 증가</button>
<button @click="resetChild">자식 컴포넌트 리셋</button>
<button @click="focusChildInput">자식 입력에 포커스</button>
<button @click="setChildMessage">자식 메시지 설정</button>
<button @click="getChildData">자식 데이터 가져오기</button>
</div>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
methods: {
callChildMethod() {
this.$refs.child.increment();
},
resetChild() {
this.$refs.child.reset();
},
focusChildInput() {
this.$refs.child.focusInput();
},
setChildMessage() {
this.$refs.child.setMessage('부모에서 설정한 메시지');
},
getChildData() {
const childData = {
counter: this.$refs.child.counter,
message: this.$refs.child.message
};
console.log('자식 컴포넌트 데이터:', childData);
alert(`카운터: ${childData.counter}, 메시지: ${childData.message}`);
}
}
};
</script>Composition API에서 컴포넌트 ref
<template>
<div>
<ChildComponent ref="childRef" />
<button @click="interactWithChild">자식과 상호작용</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = ref(null)
const interactWithChild = () => {
if (childRef.value) {
childRef.value.increment()
childRef.value.setMessage('Composition API에서 호출')
}
}
onMounted(() => {
// 자식 컴포넌트가 마운트된 후 접근 가능
console.log('자식 컴포넌트:', childRef.value)
})
</script>고급 ref 사용 패턴
조건부 ref
<template>
<div>
<button @click="toggleInput">입력 토글</button>
<input
v-if="showInput"
ref="conditionalInput"
type="text"
placeholder="조건부 입력"
/>
<button @click="focusConditionalInput">조건부 입력에 포커스</button>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
const showInput = ref(false)
const conditionalInput = ref(null)
const toggleInput = async () => {
showInput.value = !showInput.value
if (showInput.value) {
// DOM 업데이트를 기다린 후 포커스
await nextTick()
conditionalInput.value?.focus()
}
}
const focusConditionalInput = () => {
if (showInput.value && conditionalInput.value) {
conditionalInput.value.focus()
} else {
alert('입력 필드가 표시되지 않았습니다')
}
}
</script>ref와 외부 라이브러리 통합
<template>
<div>
<div ref="chartContainer" style="width: 400px; height: 300px;"></div>
<button @click="updateChart">차트 업데이트</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const chartContainer = ref(null)
let chartInstance = null
onMounted(() => {
// 외부 차트 라이브러리 초기화 (예: Chart.js, D3.js 등)
initializeChart()
})
onUnmounted(() => {
// 차트 인스턴스 정리
if (chartInstance) {
chartInstance.destroy()
}
})
const initializeChart = () => {
// 실제 차트 라이브러리 초기화 코드
console.log('차트 초기화:', chartContainer.value)
// chartInstance = new Chart(chartContainer.value, config)
}
const updateChart = () => {
if (chartInstance) {
// 차트 데이터 업데이트
console.log('차트 업데이트')
}
}
</script>ref 사용 시 주의사항
성능 고려사항
<script setup>
import { ref, watch } from 'vue'
const inputRef = ref(null)
const inputValue = ref('')
// 비효율적: ref를 통한 직접 DOM 조작
watch(inputValue, (newValue) => {
if (inputRef.value) {
inputRef.value.style.color = newValue.length > 10 ? 'red' : 'black'
}
})
// 효율적: Vue의 반응성 시스템 활용
const inputStyle = computed(() => ({
color: inputValue.value.length > 10 ? 'red' : 'black'
}))
</script>
<template>
<input
ref="inputRef"
v-model="inputValue"
:style="inputStyle"
/>
</template>메모리 누수 방지
<script setup>
import { ref, onUnmounted } from 'vue'
const elementRef = ref(null)
let resizeObserver = null
onMounted(() => {
if (elementRef.value) {
resizeObserver = new ResizeObserver(entries => {
console.log('요소 크기 변경:', entries)
})
resizeObserver.observe(elementRef.value)
}
})
onUnmounted(() => {
// 리소스 정리
if (resizeObserver) {
resizeObserver.disconnect()
resizeObserver = null
}
})
</script>결론
Vue.js의 ref는 DOM 요소와 컴포넌트 인스턴스에 직접 접근할 수 있는 유용한 기능입니다. 이를 통해 동적으로 생성된 요소를 관리하거나, 자식 컴포넌트의 메서드에 접근하는 등 다양한 작업을 수행할 수 있습니다. ref를 적절히 활용하면 Vue 애플리케이션의 인터랙션을 보다 효율적으로 관리할 수 있습니다.
핵심 포인트:
- mounted 이후에만 접근 가능: 생명주기를 고려한 ref 사용
- v-for에서 배열로 관리: 동적 요소들의 효율적 제어
- Composition API의 ref(null): 템플릿 ref와 반응형 ref 구분
- 컴포넌트 간 통신: 부모-자식 컴포넌트 직접 제어
- 성능 최적화: Vue의 반응성 시스템과 조화로운 사용
ref는 Vue의 선언적 패러다임을 벗어나는 명령형 접근 방식이므로, 꼭 필요한 경우에만 사용하고 가능한 한 Vue의 반응성 시스템을 활용하는 것이 좋습니다.
'IT기술 > 뷰.js (vue.js)' 카테고리의 다른 글
| Vue.js 컴포넌트 완벽 가이드: 재사용 가능한 UI 구성 요소 만들기 (8) | 2025.07.08 |
|---|---|
| Vue.js 감시자(Watchers) 완벽 가이드: 데이터 변화 감지와 반응형 프로그래밍 (0) | 2025.07.05 |
| Vue.js 생명주기 완벽 가이드: 컴포넌트 라이프사이클과 훅 활용법 (4) | 2025.07.04 |
| Vue 폼 바인딩 완벽 가이드 – v-model로 쉽고 강력하게 폼 데이터 관리하기 (0) | 2025.04.30 |
| [Vue] 뷰 이벤트 핸들링 완벽 가이드 (0) | 2025.04.28 |