Vue3 - Composition API - Provide / Inject

예시 - 코드샌드박스 by yejineee

🍒 Provide / inject

provide/inject의 필요성

기존에는 부모에서 자식 컴포넌트로 데이타를 전달해야할 경우엔 props를 사용하였다. 만약, 컴포넌트 구조가 복잡하게 중첩되어있을 경우엔, drilling으로 데이타를 전달해야 하며, 이 작업은 매우 짜증난다

이러한 문제를 해결하기 위해 provide, inject를 사용한다. 부모 컴포넌트가 provider가 되어 그 하위의 모든 자식들에게 데이타를 전달하게 된다. 부모는 provide option으로 데이타를 전달하게 되고, 자식 컴포넌트는 inject option으로 데이타를 사용하게 된다.

(리액트의 Context API가 이러한 역할을 한다. Context.Provider / Context.Consumer가 provide/inject에 대응한다.)

예시

부모 컴포넌트 쪽에서 provide로 데이터를 넘긴다. 이 때, provide는 객체를 넘기는 함수인데, 이렇게 해야 component instance property를 자식 컴포넌트에게 넘길 수 있다.

app.component('todo-list', {
  data() {
    return {
      todos: ['Feed a cat', 'Buy tickets']
    }
  },
  provide() {
    return {
      todoLength: this.todos.length
    }
  },
  template: `
    ...
  `
})
app.component('todo-list-statistics', {
  inject: ['todoLength'],
  created() {
    console.log(`Injected property: ${this.todoLength}`) // > Injected property: John Doe
  }
})

만약, component instance property가 아닌 값을 넘겨줄 때는 provide는 다음과 같은 형태여도 된다.

  provide: {
    location: "North Pole",
    geolocation: {
      longitude: 90,
      latitude: 135,
    },
  },

provide/inject에 반응형 추가하기

기본적으로 provide/inject는 reactive하지 않다. 따라서, todos 배열의 길이가 달라져도, 주입된 todoLength property에는 반영되지 않는다.

반응형을 추가하고 싶으면

  • ref property 전달 or
  • reactive object 전달
    을 해야 한다.

여기서는 Composition API의 computed 프로퍼티를 todoLength에 추가하였다.

injected property를 가져올 때는 this.todoLength.value로 가져온 것을 알 수 있다.

app.component('todo-list', {
  // ...
  provide() {
    return {
      todoLength: Vue.computed(() => this.todos.length)
    }
  }
})

app.component('todo-list-statistics', {
  inject: ['todoLength'],
  created() {
    console.log(`Injected property: ${this.todoLength.value}`) // > Injected property: 5
  }
})

🍒 Composition API - provide/inject

Composition API에서 provide, inject를 사용할 수 있다.

Provide 사용하기

setup()안에서 provide를 사용하기 위해서는 vue에서 provide를 import해야 한다.

provide 함수로는 두 값을 전해주어야 한다.

  • name (String type)
  • value
<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
  }
}
</script>

Inject 사용하기

setup()안에서 inject 사용하려면 vue에서 import 해와야 한다.
inject함수는 두 파라미터를 받는다.

  • inject할 프로퍼티 이름
  • default value(optional)
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>

반응형 추가하기

반응형을 추가하기 위해선 refreactive 를 provide할 value에 추가해야 한다.

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    provide('location', location)
    provide('geolocation', geolocation)
  }
}
</script>

반응형 프로퍼티 변경하기

  • provider안에서 값 변경하기 : 가능한한 그렇게 할 것 !

reactive property를 변경하는 것은 provider 안에서 하는 것을 추천한다.

<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    provide('location', location)
    provide('geolocation', geolocation)

    return {
      location
    }
  },
  methods: {
    updateLocation() {  // provider안에서 location property 변경하는 메소드 추가
      this.location = 'South Pole'
    }
  }
}
</script>
  • inject한 컴포넌트에서 값 변경하기

만약, 데이타를 inject받은 컴포넌트에서 데이타를 변경해야 할 경우도 있을 것이다. 이럴 때는, reactive property를 변경할 메소드를 provider쪽에서 전달할 것을 추천한다.

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    const updateLocation = () => {
      location.value = 'South Pole'
    }

    provide('location', location)
    provide('geolocation', geolocation)
    provide('updateLocation', updateLocation)
  }
}
</script>
  • readonly 사용하여 상수화하기

값이 변경되어서는 안될 프로퍼티에는 readonly를 provider 쪽에서 추가하여, injected component 쪽에서 값을 변경시키지 못하도록 할 것을 추천한다.

<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    const updateLocation = () => {
      location.value = 'South Pole'
    }

    provide('location', readonly(location))
    provide('geolocation', readonly(geolocation))
    provide('updateLocation', updateLocation)
  }
}
</script>
Select a repo