Vue 3.0/04.Component

[vue 3] 05. provide/inject

  • -

이번 포스트에서는 provide와 inject의 개념과 사용법에 대해 알아보자.

 

prop drilling의 문제와 해결

 

prop drilling

아주 긴 상/하 관계의 컴포넌트 트리가 있다고 생각해 보자. 이 상황에서 root 컴포넌트에서 생성된 어떤 값을 말단의 컴포넌트에게 전달할 수 있을까? 

당연히 된다. 우리가 배운 props를 한 땀 한 땀 이용하다 보면 언젠가 말단의 컴포넌트에 도착할 것이다. 하지만 매우 지루한 작업이 될 텐데 이런 것을 prop drilling이라고 한다. 

왼쪽 화살표 흐름: prop drilling, 당연히 좋지 않다.

 

provide와 inject

prop drilling 문제를 해결하기 위해서 vue에서는 provide와 inject를 사용할 수 있다.

  • provide: 데이터를 제공할 상위 컴포넌트에서 자료를 공급하는 것
  • inject: 데이터를 사용할 하위 컴포넌트에서 자료를 주입(사용) 받는 것

vue2에서는 event bus가 있어서 이런 비슷한 일을 처리했는데 vue3에서는 deprecated 되고 대신 등장한 녀석 같다. (https://v3-migration.vuejs.org/breaking-changes/events-api.html)

 

provide/inject

 

조상 컴포넌트의 provide

function provide<T>(key: InjectionKey<T> | string, value: T): void

provide 함수는 문자열 key와 T 타입 value를 갖는다. value는 반응형을 포함한 모든 값을 사용할 수 있다. 반응형을 사용하면 자식 컴포넌트도 당연히 반응성을 이용할 수 있어서 양방향 소통이 가능하다. props가 단방향의 readonly로 사용되는 것이 권장되는 것과는 좀 다른 양상이다.

import { provide, ref } from 'vue'
provide('num1', 1)      // 일반 값
const num2 = ref(0);
provide('num2', num2)   // 반응성 값

 

자식 컴포넌트의 inject

다음으로 inject 함수에 대해 알아보자. inject 함수는 provide된 key를 이용해 value를 사용할 수 있다. 

// 기본 값 없음
function inject<T>(key: InjectionKey<T> | string): T | undefined

// 기본 값 정의 있음
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T

// 팩토리 함수
function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T

 

key에 해당하는 값이 provide되지 않았을 때 대응하기 위해 여러 가지 타입의 함수가 재정의 되어있다. 첫 번째 함수는 기본값이 설정되지 않은 형태로 값이 없을 때 udefined가 반환된다. 두 번째 함수는 defaultValue를 설정하고 반환한다. 세 번째 factory 함수는 반환 값을 생성할 factory 함수를 등록해서 사용할 수 있다.

inject를 component에서 사용할 때에는 setup 영역에서만 가능하고 다른 함수 내부에서는 사용할 수 없다. 

router에서는 최근 버전에서 사용 가능해졌다.
import {inject} from "vue"
const num1 = inject("num1");
const num2 = inject("num2");
const num3 = inject("num3", 100); // provide 값 없을 때 기본값 100 제공
const num4 = inject("num4", ()=> { // provide 없을 때 동작할 생성 함수 제공
               return Math.random()
             }, true);

 

반응성 값 사용 시 문제점

props를 readonly로 사용하기로 결정한 이유는 조상의 값을 자식들이 일관성 있게 사용하기 위해서였다. 그런데 provide/inject에 반응성 값이 전달되면 손쉽게 자식들이 편집할 수 있어서 최초의 의도대로 사용되지 않을 수 있다.

이를 위해서 Vue는 2가지 안정장치를 제공하는데 첫 번째는 readonly 함수 사용이다.

import { provide, ref, readonly } from 'vue'
const num2 = ref(0);
provide('num2', readonly(num2))

위와 같이 반응성 값을 readonly 함수로 감싸서 전달하면 조상에서는 반응성으로 사용할 수 있지만 자식은 읽기 전용으로 사용해야 한다. 만약 자식이 값을 변경하려는 경우 아래와 같이 warn이 발생하며 수정되지 않는다. props와 가장 유사한 형태이다.

 

하지만 자식이 조상이 가이드한 방식대로만 수정한다면 나쁘지 않을 수도 있다. 즉 조상 컴포넌트가 validation 함수를 제공하는 것이다. 이를 위해서 조상 컴포넌트는 provide 시 value로 값과 함께 validation 함수를 객체 형태로 전달한다.

const even = ref(2)
provide('even', { even,  
                  modifyEven: (newValue) => 
                               even.value = newValue%2==0?newValue:even.value
                 }
         )

다음으로 자식 컴포넌트는 inject 받은 객체를 통해서 값과 함께 함수를 전달받고 이 함수를 호출해서 값을 수정하게 된다. 

주입받은 값: <input type="number" :value="even" @change="modifyEven($event.target.value)">
const {even, modifyEven} = inject("even");

단, 이 경우라도 자식이 함수를 통하지 않고 직접 값을 변경하는 것을 막지는 못한다.(예를 들어 v-model로 처리하는 경우)

 

기타

 

전역 레벨에서의 provide

일반 컴포넌트에서 provide는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 내려보내기 위해서 사용된다.

플러그인들(app.use로 사용되는 router 등)은 일반적으로 Component를 사용해서 값을 제공받지 않기 때문에 공유되는 값을 사용하기 어렵다. 이런 경우  App 자체에서 제공하는 provide 함수를 사용할 수 있다. 

interface App {
  provide<T>(key: InjectionKey<T> | symbol | string, value: T): this
}

하지만 정작 main.js에서는 inject 받을 수 없다.

app.provide('user', ref({ id: 'hong' }))
// inject: main.js에서는 사용되지 않음.(undefined)
console.log('injected: ', inject('user'))
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.