🌿 컴포넌트 간 상태 공유 - Provide / Inject 완전정복
Vue 기초 시리즈 > 2️⃣ 컴포넌트 기반 개발 > 3️⃣ 컴포넌트 간 상태 공유 방법 (Provide/Inject)
- 1. 왜 Provide/Inject가 필요한가?
- 2. 기본 사용법 (부모 Provide / 자식 Inject)
- 3. 반응형 데이터로 공유하는 방법
- 4. 주의할 점 및 실전 팁
- 5. Vuex, Pinia와의 비교
- 🔚 다음 글 안내
1. 왜 Provide/Inject가 필요한가?
Vue에서는 props를 통해 상위 → 하위로 데이터를 넘길 수 있습니다. 하지만 하위 컴포넌트가 깊게 중첩되면 props drilling
문제가 발생합니다.
Provide/Inject는 이 문제를 해결하는 Vue만의 간접 데이터 전달 방법입니다. 중간 컴포넌트는 데이터를 전달하지 않아도 되며, 최상위 컴포넌트에서 하위까지 값을 전파할 수 있습니다. provide/inject는 부모가 자식에게 자료를 넘겨주는 비공식 통신 수단(여전히 흐름은 상위에서 하위로 지켜짐)
주로 여러 컴포넌트에서 로그인 정보, 공통 설정값, 테마, 글로벌 상태 공유할 때 사용합니다.
main.js
나 루트 컴포넌트(App.vue)에서 provide()
하면, 그 아래 하위 컴포넌트에서 inject()
로 받아 사용
2. 기본 사용법
부모 컴포넌트 - Provide,
자식 컴포넌트 - Inject
// Parent.vue
import { provide } from "vue";
setup() {
const message = "안녕하세요!";
provide("msg", message);
}
// Child.vue
import { inject } from "vue";
setup() {
const msg = inject("msg");
console.log(msg); // "안녕하세요!"
}
해당 컴포넌트의 하위에서만 inject 가능 ( 자신이나 상위에서는 inject 불가)
(컴포넌트 구조 내에서 특정 값 하위 전달 - 특정 섹션의 필터 조건, 하위 모듈 전용 데이터 )
// main.js
const app = createApp(App);
app.provide('user', reactive({ name: '홍길동' }));
app.provide('theme', 'dark')
app.mount('#app');
// 하위 컴포넌트
inject('theme') // => 'dark'
inject('user')
모든 컴포넌트에서 inject 가능 (전역 제공)
(앱 전체 공통 설정, 예: 테마, API 클라이언트, 사용자 정보, 로깅 유틸, 글로벌 상태 )
✔ 키포인트
- provide 키와 inject 키가 문자열로 일치해야 한다.
- 데이터 흐름은 단방향 (상위 → 하위)이며, inject된 값은 직접 수정하지 않는 것이 좋다.
- 반응형 객체를 전달하면, 하위에서 변경해도 상위에 반영됨 (단, ref나 reactive로 넘겨야 함) 다만 하위 컴포넌트에서 직접 수정하는 것은 Vue의 철학과 컴포넌트 설계 원칙에 어긋나는 행위이므로, ref, reactive 객체를 전달하는 것은 권장되지 않습니다. (추적 불가, 혼란 발생)
3. 반응형 데이터로 공유
단순 문자열은 반응성이 없기 때문에, ref
나 reactive
로 감싸줘야 하위 컴포넌트에서도 자동 반영됩니다.
// App.vue
const count = ref(0);
provide("count", count);
// Child.vue
const count = inject("count");
count.value가 바뀌면 하위 컴포넌트에서도 반영됩니다.
4. 주의할 점 및 실전 팁
- Provide/Inject는 단방향입니다. 하위에서 값을 직접 수정하지 말고, emit 이벤트로 부모에 알리는 방식으로 사용하세요.
- Provide는 setup() 안에서만 사용할 수 있습니다. Options API에서는
provide() { return { key: value } }
형태로도 가능합니다. - Inject 키가 없으면 undefined가 되므로,
inject("key", defaultValue)
처럼 기본값을 설정하는 것이 좋습니다.
✅ 왜 하위에서 직접 수정하면 안 될까?
📌 이유 1. 컴포넌트 간 의존성 증가
- 상위 → 하위로만 흐르는 단방향 데이터 흐름을 깨뜨리게 됩니다.
- 어느 하위 컴포넌트가 상위의 상태를 바꿨는지 추적하기 어려워져요.
📌 이유 2. 유지보수성 저하
- 구조가 복잡해질수록 어디서 데이터가 바뀌는지 파악하기 어렵습니다.
- 하위 컴포넌트가 독립적으로 동작하지 않게 됩니다.
✅ 그래서 보통 이렇게 합니다
읽기 전용처럼 inject() 사용
const theme = inject('theme') // 값을 읽기만
상위에서 함수(provide된 setter)를 함께 전달해서 간접적으로 수정
// 상위 컴포넌트에서
const count = ref(0);
const setCount = (v) => count.value = v;
provide('count', count);
provide('setCount', setCount);
// 하위 컴포넌트에서
const count = inject('count');
const setCount = inject('setCount');
setCount(5); // setter를 통해서만 변경
이 방식은 Vue의 단방향 데이터 흐름을 지키면서도 제어를 가능하게 해줍니다.
5. Provide/Inject vs Vuex, Pinia
구분 | Provide/Inject | Vuex/Pinia |
---|---|---|
범위 | 부모-자식 계층 | 앱 전체 |
복잡도 | 매우 간단 | 설정 및 boilerplate 필요 |
적합한 상황 | 로컬 상태 공유 (예: 테마, 설정) | 전역 상태 관리 (로그인 정보 등) |
✅ inject 없이 전역에서 사용할 수 있는 대표적인 방법
방법 | 설명 | 사용 예시 |
1. 전역 프로퍼티 (app.config.globalProperties) | 모든 컴포넌트의 this에서 접근 가능 | 전역 함수, 유틸리티, 라이브러리 등록 |
2. 전역 상태 관리 (예: Pinia, Vuex) | 공식 전역 상태 관리 도구 | 로그인 정보, 전역 테마 등 |
3. 전역 컴포지션 API 플러그인 | 커스텀 훅 또는 함수로 전역 상태 공유 | useAppState() 등 커스텀 전역 모듈 |
1️⃣ app.config.globalProperties 사용
✅ main.js에서 등록
const app = createApp(App);
app.config.globalProperties.$appName = 'MyApp';
app.config.globalProperties.$logger = (msg) => console.log('[LOG]', msg);
app.mount('#app');
app.config.globalProperties는 Vue 앱 인스턴스(app)에 등록된 속성이기 때문에,
main.js처럼 createApp(App)으로 생성된 Vue 앱 객체(app)에 등록해야 (뷰 플러그인 install패턴으로 확장해도 로직은 동일함)
모든 컴포넌트에서 전역으로 접근할 수 있어요.
export default {
created() {
this.$myValue = 'Hello'; // this.$xxx는 자기 자신에서만 씀. 여기서 설정한 건 다른 컴포넌트에 안 퍼짐
},
mounted() {
console.log(this.$myValue); // 가능 (자기 자신이니까)
}
}
// main.js app속성에 전역등록이 아니고, 해별 인스턴스 속성에만 추가한 것이기 때문에 다른 컴포넌트에서 접근 불가
✅ 모든 컴포넌트에서 사용 가능
<script>
export default {
mounted() {
console.log(this.$appName); // "MyApp"
this.$logger('Hello from component');
}
}
</script>
❗ Composition API (setup 함수) 에서는
this
를 사용할 수 없기 때문에 이 방식은 Options API용입니다.
2️⃣ 전역 상태 관리 도구: Pinia (Vue 3 공식)
npm install pinia
// main.js
import { createPinia } from 'pinia';
const pinia = createPinia();
app.use(pinia);
// stores/userStore.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({ name: '홍길동', isLoggedIn: true }),
});
<script setup>
import { useUserStore } from '@/stores/userStore';
const userStore = useUserStore();
console.log(userStore.name); // 전역 상태 접근
</script>
✅ inject 없이 어디서든 사용할 수 있음, Composition API에 강하게 추천되는 방식
3️⃣ 전역 커스텀 훅 방식
useAppState() 같은 함수를 만들어서 공용으로 사용합니다.
// src/composables/useAppState.js
import { reactive } from 'vue';
const appState = reactive({ theme: 'dark', count: 0 });
export function useAppState() {
return appState;
}
<script setup>
import { useAppState } from '@/composables/useAppState';
const state = useAppState();
state.count++;
</script>
이건 Vue 내부 기능은 아니지만, inject 없이 전역처럼 관리하는 구조로 많이 사용됩니다.
상태가 고정된 단일 인스턴스이기 때문에 어디서든 공유 가능해요.
🔍 정리표
방법 | Composition API 호환 | 반응형 | 사용 위치 |
inject | ✅ | ✅ | 상위 → 하위 컴포넌트 |
globalProperties | ❌ (Options API 전용) | ❌ | 전역 유틸, 설정값 |
Pinia | ✅ | ✅ | 앱 전역 상태 |
커스텀 훅 (예: useAppState) | ✅ | ✅ | 간단한 전역 관리 |
🔚 다음 글 안내
이제 Provide/Inject를 통해 컴포넌트 간 상태를 효율적으로 공유하는 법을 익혔습니다!
다음 글에서는 슬롯(Slot)의 개념과 활용법을 알아볼 예정입니다.
👉 다음 글: Slot의 개념과 실전 예제
'framework_library > vue' 카테고리의 다른 글
🌿 Vue 템플릿 조건부 렌더링과 반복 처리 패턴 (0) | 2025.05.01 |
---|---|
🌿 Vue.js 컴포넌트 슬롯(Slot)의 개념과 활용법 (0) | 2025.05.01 |
🌿 Vue 컴포넌트 간 통신 - Props와 Emit 완전정복 (0) | 2025.05.01 |
🌿 Vue.js 컴포넌트 선언과 등록 방식 (전역, 지역) (0) | 2025.05.01 |
🌱 Vue.js 반응형 데이터 이해하기 - reactive와 ref의 차이점 완전정복 (1) | 2025.05.01 |
댓글