본문 바로가기
framework_library/vue

🌿 컴포넌트 간 상태 공유 - Provide / Inject 완전정복

by 죄니안죄니 2025. 5. 1.

🌿 컴포넌트 간 상태 공유 - Provide / Inject 완전정복

Vue 기초 시리즈 > 2️⃣ 컴포넌트 기반 개발 > 3️⃣ 컴포넌트 간 상태 공유 방법 (Provide/Inject)


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. 반응형 데이터로 공유

단순 문자열은 반응성이 없기 때문에, refreactive로 감싸줘야 하위 컴포넌트에서도 자동 반영됩니다.

// 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의 개념과 실전 예제

댓글