[VUE3 신기능 시리즈#2] Prop의 진화된 버전 : Provide(제공) / Inject(주입)
안녕하세요, 서비스개발팀 Yohan입니다. 🫡 우리가 Vue.js를 활용하여 웹페이지를 만들 때 각 컴포넌트로 값을 전달하려면 props
를 주로 사용해왔는데요, props
는 사용하기 간편한 장점이 있지만 때로는 불편할 때가 있습니다. 이를 해결하기 위해 Vue.js 3버전에서는 Provide
와 Inject
의 개념이 등장했는데요. 이번 주제는 Provide
와 Inject
가 무엇인지, 어떤 상황에 사용하면 유용한지에 대해 알아보는 시간을 가져보겠습니다.
Prop 드릴링
예를 들어 상위 컴포넌트 Root
아래 하위 컴포넌트 Footer
가 있고, 또 그 하위 컴포넌트 Footer
를 상위 컴포넌트로 가지는 하위 컴포넌트 DeepChild
가 있다고 했을 때, Root
에서 DeepChild
로 값을 전달하려면 기존 사용하던 Props
체계에서는 전체 부모체인에 동일한 prop
을 전달해야 했습니다.
위와 같은 경우, Footer
컴포넌트에서는 prop
이 전혀 필요하지 않을 수 있는 상황이지만, 하위 컴포넌트인 DeepChild
에게 값을 전달하기 위해 어쩔 수 없이 부모 컴포넌트로부터 prop
을 전달받아 다시 아래로 전달해야 합니다. 이러한 비효율적인 구조를 개선하기 위해 나온 개념이 provide
와 inject
입니다. ✨
Provide
컴포넌트의 하위 항목에 데이터를 제공하려면 provide()
함수를 사용하면 됩니다.
<script setup>
import { provide } from 'vue'
provide(/* 키 */ 'message', /* 값 */ '안녕!')
</script>
provide()
함수는 두 개의 파라미터를 허용합니다. 첫 번째 파라미터는 주입 키로 문자열 이나 Symbol
이 될 수 있습니다. 이 주입 키는 자식 컴포넌트에서 필요한 값을 조회할 때 사용됩니다. 두 번째 파라미터는 제공되는 값입니다. 값은 refs
와 같은 반응형 상태를 포함하여 모든 값이 될 수 있습니다.
ex)
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)
반응형 값을 provide
로 제공하면, 자식 컴포넌트가 반응형 연결을 설정할 수 있습니다.
Inject
상위 컴포넌트에서 제공한 데이터를 하위 컴포넌트에서 받을 때는 inject()
함수를 사용합니다.
<script setup>
import { inject } from 'vue'
const injectedCount = inject('count')
console.log(injectedCount); // 0 provide에 설정한 값
</script>
⚡주입 시 기본 값 설정하기
기본적으로 inject
는 주입된 키 값이 상위 컴포넌트 어딘가에서 제공된다고 가정합니다. 만약 상위 컴포넌트에서 provide
된 데이터가 존재하지 않으면 런타임 오류가 발생합니다. 이러한 오류를 피하기 위해서는 props
처럼 기본값을 선언해야 합니다.
// `value` 값은 0이 됩니다.
// "message"에 해당하는 데이터가 제공되지 않은 경우
const value = inject('count', 0)
반응형으로 만들기
provide
를 반응형 값으로 사용할 때, 값을 주입하는 쪽이나 받는 쪽 모두 변경사항을 반응형 상태로 유지하는 것이 좋습니다. 이렇게 하면 props
와는 달리 제공받은 값을 하위 컴포넌트에서도 변경할 수 있으며, 이러한 변경된 값은 상위 컴포넌트에서도 동일하게 반영됩니다.
하위 컴포넌트에서 inject
받은 값을 변경하려면 상위 컴포넌트에서 provide
할 때 상태 변경을 담당하는 함수를 제공하면 됩니다.
ex)
// Main.vue
<script setup>
import { provide, ref } from 'vue';
import Children from './Children.vue';
const legacyFromMe = ref('할아버지의 유산 $100만');
const updateLegacy = () => {
legacyFromMe.value = '할아버지의 유산 $1만';
};
provide('legacy', { legacyFromMe, updateLegacy });
</script>
<template>
<div class="p-3">
<h1>할아버지 페이지</h1>
<Children />
</div>
</template>
// Children.vue
<template>
<div>
<h1>Children</h1>
<p>자식 컴포넌트</p>
<GrandChildren />
</div>
</template>
<script setup>
import GrandChildren from './GrandChildren.vue';
</script>
// GrandChildren.vue
<template>
<div>
<h1>GrandChildren</h1>
<p>손자 컴포넌트</p>
<p class="injected-from-grand">
할아버지가 Provide 해서 inject로 주입한 값 : {{ legacyFromMe }}
</p>
<button @click="updateLegacy">값을 변경해 봅시다</button>
</div>
</template>
<script setup>
import { inject } from 'vue';
const { legacyFromMe, updateLegacy } = inject('legacy');
</script>
위 예제에서는 Main.vue 파일에서 provide
함수를 사용하여 legacyFromMe
변수와 updateLegacy
함수를 제공합니다. 그런 다음, GrandChild.vue 파일에서 inject
함수를 사용하여 해당 값을 받아옵니다.
그리고 값을 변경하는 버튼을 클릭할 때 상위 컴포넌트에서 제공한 함수를 호출하여 legacyFromMe
변수의 값을 변경시킵니다. 이를 통해 상위 컴포넌트와 하위 컴포넌트 간에 데이터를 동기화할 수 있습니다.
여기서 주의할 점은, 상위 컴포넌트에서 provide
할 때 넣은 변수의 이름을 하위 컴포넌트에서 가져올 때 사용하는 변수 이름을 동일하게 유지해야 한다는 것입니다.
하지만 props
처럼 상위 컴포넌트를 통해 전달된 데이터를 하위 컴포넌트에서 변경할 수 없도록 하려면, 제공된 값을 readonly()
함수로 래핑할 수 있습니다.
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
심볼 키 사용하기
지금까지 예제에서는 문자열 키를 사용했습니다. 하지만 대규모 프로젝트를 작업하거나, 다른 개발자가 사용할 컴포넌트를 작성하는 경우에는 잠재적 충돌을 피하기 위해 문자열 키 대신 Symbol
을 사용하는 것이 권장됩니다.
Symbol
은 유일한 값을 가지므로 충돌없이 고유한 식별자로 사용할 수 있습니다. 이는 코드의 가독성과 유지보수성을 향상시키며, 의도치 않은 데이터 덮어쓰기를 방지할 수 있습니다.
// keys.js
export const myInjectionKey = Symbol()
// 제공하는 곳의 컴포넌트에서
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* 제공할 데이터 */
})
// 주입되는 곳의 컴포넌트에서
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
위의 예제와 같이 키로 사용할 문자열 대신 Symbol
을 관리하는 자바스크립트 파일을 따로 만들고 해당 파일에서 선언된 Symbol
을 키로 사용함으로써 충돌을 방지할 수 있습니다.
결론
지금까지 Vue.js 3버전에서 새롭게 태어난 기능인 Provide
와 Inject
기능에 대해 살펴봤습니다. 저도 지금까지 개발을 하다보면 하위 컴포넌트가 중첩된 페이지가 생겼을 때, 해당 하위 컴포넌트에 값을 내려주려고 해당 값을 사용하지 않는 컴포넌트에까지 props
를 내려가며 작업했던 기억이 떠오르는데요. Provide
와 Inject
기능을 사용하면 더이상 그런 불필요한 과정 없이 상위 컴포넌트에서 바로 필요한 하위 컴포넌트에 값을 전달할 수 있게 되어 아주 편리하게 개발을 할 수 있게 되었습니다.
앞으로도 더욱 다양한 Vue.js의 새로운 기능들에 대해 소개하며 Vue.js를 사용하는 프론트엔드 개발자분들에게 조금이나마 도움이 될 수 있기를 바랍니다. 그럼 오늘의 포스팅을 마무리하겠습니다! 🫡
참고
헥토데이터는 데이터 기반 다양한 서비스를 지원하는 기업입니다. 온라인에 분산된 데이터를 실시간으로 제공하기 위해 클라이언트 엔진과 웹 API를 활용합니다. 유수의 비대면 대출 핀테크 서비스가 선택한 헥토데이터의 CODEF API. 꼼꼼한 보안과 빠른 대응으로 기업이 자사 서비스에만 집중할 수 있도록 돕는 CODEF API에 대해 아래 배너를 눌러 확인해 보세요.
본 페이지 내의 모든 콘텐츠는 저작권법에 의해 보호받는 저작물로서, 모든 사용 권리는 ㈜헥토데이터에게 있습니다. 별도의 저작권 표시 없이 무단으로 사용하는 것을 금지하며, 자세한 저작권 정책은 해당 링크를 참고하시기 바랍니다. Copyright 2024.㈜헥토데이터 All rights reserved.