Vue 3 – Provide / Inject로 깊은 컴포넌트 간 데이터 전달하기

2025. 3. 30. 21:30Front-End/Vue.js

반응형

 

Prop Drilling 문제

Vue에서 일반적으로 부모 → 자식으로 데이터를 전달할 때 props를 사용한다.
하지만 컴포넌트 구조가 깊어지면 아래처럼 중간 단계의 컴포넌트들이 단순히 전달만을 위한 용도로 props를 받아야 하는 상황이 생긴다.

<App>
  <Layout>
    <Footer>
      <DeepChild />
    </Footer>
  </Layout>
</App>

App → DeepChild까지 데이터를 전달하려면 Layout, Footer까지 모두 props를 받아야 한다.
이런 문제를 Prop Drilling이라고 부른다.


Provide / Inject란?

Vue 3에서는 provide()와 inject()를 사용하면 컴포넌트 트리 구조와 상관없이 상위 → 하위로 직접 데이터 전달이 가능하다.

  • 상위: provide()를 통해 데이터를 “제공”
  • 하위: inject()로 데이터를 “주입받음”
  • 중간 컴포넌트는 관여하지 않아도 된다

기본 사용법

상위 컴포넌트에서 제공

// ProviderComponent.vue
import { provide, ref } from 'vue';

export default {
  setup() {
    const message = ref('Hello from parent');
    provide('sharedMessage', message);

    return { message };
  }
}

하위 컴포넌트에서 주입

// DeepChild.vue
import { inject } from 'vue';

export default {
  setup() {
    const message = inject('sharedMessage');
    return { message };
  }
}

이제 DeepChild는 ProviderComponent에서 제공한 message에 접근할 수 있다.
message가 ref이기 때문에 반응성도 그대로 유지된다.


기본값 설정

주입하려는 키가 실제로 제공되지 않을 경우, 기본값을 두 번째 인자로 넣을 수 있다.

const fallback = inject('nonexistentKey', '기본 메시지');

기본값을 함수로 전달할 수도 있다.

const fallback = inject('nonexistentKey', () => '함수형 기본 메시지');

함수 전달하기

반응형 데이터뿐 아니라 함수도 전달할 수 있다.

// 상위 컴포넌트
const count = ref(0);
const increment = () => count.value += 1;

provide('counter', {
  count,
  increment
});
// 하위 컴포넌트
const { count, increment } = inject('counter');

하위 컴포넌트에서도 increment()를 호출해 값을 변경할 수 있다.


readonly로 전달 막기

하위 컴포넌트에서 값을 직접 바꾸는 걸 막고 싶다면 readonly()를 사용한다.

import { provide, readonly, ref } from 'vue';

const count = ref(0);
provide('readonlyCount', readonly(count));

Symbol을 키로 사용하기

문자열 키는 충돌 위험이 있다. 대규모 앱에서는 Symbol을 키로 사용하는 것이 좋다.

// keys.js
export const MessageKey = Symbol('messageKey');
// 상위 컴포넌트
import { provide, ref } from 'vue';
import { MessageKey } from './keys';

const message = ref('메시지입니다');
provide(MessageKey, message);
// 하위 컴포넌트
import { inject } from 'vue';
import { MessageKey } from './keys';

const message = inject(MessageKey);

App 레벨에서 provide

provide()는 루트 앱 인스턴스에서도 설정할 수 있다. 이 경우 전체 앱에서 사용할 수 있다.

// main.js
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);
app.provide('appName', 'My Vue App');
app.mount('#app');

이렇게 설정한 값은 모든 컴포넌트에서 inject('appName')으로 사용할 수 있다.


예: 글로벌 Config 전달

Vue 2에서는 $root나 this.$app 같은 방식으로 글로벌 데이터를 공유했지만, Vue 3의 setup()에서는 컴포넌트 인스턴스에 접근할 수 없기 때문에 provide/inject 방식이 권장된다.

// main.js
app.provide('config', {
  apiBase: 'https://api.example.com',
  version: '1.0.0',
});
// 내부 컴포넌트
const config = inject('config');
console.log(config.apiBase); // https://api.example.com

정리

기능 설명

provide() 상위 컴포넌트에서 데이터를 등록
inject() 하위 컴포넌트에서 해당 데이터를 사용
반응형 지원 ref, reactive 모두 가능 (연결 유지)
기본값 설정 inject 두 번째 인자로 fallback 가능
함수 전달 데이터 외에 함수도 함께 제공 가능
읽기 전용 readonly()로 하위에서 직접 수정 방지
Symbol 키 키 충돌 방지를 위한 권장 방식
App-level 제공 앱 전체에 공통 데이터를 제공할 때 사용

Vue의 provide/inject는 단순히 props 전달을 생략하기 위한 편법이 아니라, 역할이 분리된 데이터 공유 방식이이다.
특히 전역 설정, 공통 유틸, 다국어 리소스, 테마 구성 등에서 유용하게 사용할 수 있다.

반응형