반응형
Vue3의 expose에 대해 알아보고, 사용 사례를 알아봅니다.
원문 : https://www.vuemastery.com/blog/understanding-vue-3-expose/
Vue3의 인스턴스 Accecibility
Vue3에서 부모 컴포넌트는 자식 컴포넌트의 메서드와 프롭 전체에 엑세스 할 수 있다고 합니다.
즉 setup 메서드에서 리턴되는 요소들은 전부 퍼블릭 합니다.
📃 MyCounter.vue
<template>
<p>Counter: {{ counter }}</p>
<button @click="reset">Reset</button>
<button @click="terminate">☠️</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
const counter = ref(0)
const interval = setInterval(() => {
counter.value++
}, 1000)
const reset = () => {
counter.value = 0
}
const terminate = () => {
clearInterval(interval)
}
return {
counter,
reset,
terminate
}
}
}
</script>
컴포지션 관점에서 부모 컴포넌트가 필요한 경우 reset 메서드를 직접 호출할 수 있기를 원하지만,
컴포넌트 내부의 terminate, counter는 프라이빗하게 유지하고 싶습니다.
하지만 현재 App.vue와 같은 부모 컴포넌트에서 이 컴포넌트를 인스턴스화하고,
해당 인스턴스에 ref를 부착하면
setup return에서 노출된 메서드를 모두 호출할 수 있습니다.
📃 App.vue
<template>
<MyCounter ref="counter" />
<button @click="reset">Reset from parent</button>
<button @click="terminate">Terminate from parent</button>
</template>
<script>
import MyCounter from '@/components/MyCounter.vue'
export default {
name: 'App',
components: {
MyCounter
},
methods: {
reset () {
this.$refs.counter.reset()
},
terminate () {
this.$refs.counter.terminate()
}
}
}
</script>
reset 함수만 사용할 수 있도록 부모에게 노출하려는 항목을 명시해 봅시다.
📃 MyCounter.vue
<script>
import { ref } from 'vue'
export default {
setup (props, context) {
const counter = ref(null)
const interval = setInterval(() => {
counter.value++
}, 1000)
const reset = () => {
counter.value = 0
}
const terminate = () => {
console.log(interval)
clearInterval(interval)
}
context.expose({ reset })
return {
counter,
reset,
terminate
}
}
}
</script>
이제 부모의 종료 버튼을 클릭하면 오류가 발생할 것입니다. 해당 메서드가 노출되지 않을 것이기 때문입니다.
defineExpose & <script setup>
<script setup>을 사용하는 경우에는 자동으로 모든 메서드 / 프로퍼티가 private하게 됩니다.
이 경우 defineExpose를 사용하여 노출하고자 하는 데이터를 명시합니다.
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
Composition API Render functions
@cliick에서 디폴트로 reset을 클릭하게 하는 방법으로 expose를 피할 수도 있습니다.
<script>
// The template has been deleted
import { ref, h } from 'vue'
export default {
setup (props, context) {
const counter = ref(0)
const interval = setInterval(() => {
counter.value++
}, 1000)
const reset = () => {
counter.value = 0
}
const terminate = () => {
clearInterval(interval)
}
// context.expose({ reset })
return () => h('div', [
h('p', `Counter: ${counter.value}`),
h('button', { onClick: reset }, 'Reset'),
h('button', { onClick: terminate }, 'Terminate')
])
}
}
</script>
참고로 리액트에도 이와 유사한 useImperativeHandle이 있습니다.
https://beta.reactjs.org/apis/react/useImperativeHandle
React.useImperativeHandle(ref, () => ({
scrollToTop,
scrollToBottom,
}))
이를 남용하지 마십시오. props로 표현할 수 없는 명령형 동작에만 refs를 사용해야 합니다.
예를 들어 스크롤, 포커스, 애니메이션 트리거, 텍스트 선택 등이 있습니다.
무언가를 prop으로 표현할 수 있다면 ref를 사용하면 안 됩니다.
예를 들어 Modal 컴포넌트에서 { open, close }와 같은 명령형 핸들러를 노출하는 대신
<Modal isOpen={isOpen} />과 같은 prop으로 isOpen을 사용하는 것이 좋습니다.
효과는 prop을 통해 명령형 동작을 노출하는 데 도움이 될 수 있습니다.
결과적으로 expose는 사용은 ref를 노출하는 것과 동일한 효과를 발생시킵니다.
개방 폐쇄 원칙(OCP)
프론트엔드를 개발하다보면 컴포넌트들이 요구사항 변경에 의해 수정되어야 하는 경우가 생가는데
수정을 발생시키는 요소들은 다음과 같습니다
- 마크업 / 레이아웃(기능)
- UI 인터랙션
- 부수효과
- 상태 관리
인풋에 포커싱 함수를 추가해야 하는 경우,
- 사용자에게 dom ref 참조를 허용하면, 사용자가 비즈니스 로직에 따라 직접 dom API를 사용할 수 있습니다.
- expose를 사용하면 포커싱, 인터랙션, 상태 변경 등의 특수한 사용 사례가 추가될 때마다 컴포넌트의 메서드를 추가하여 노출해 줍니다.
요구사항이 추가될 때마다 우리는 OCP를 체크해야 할 필요가 있습니다.
- wrapping하는 클래스, 함수를 추가하여 기능을 확장할 수 있다 : OCP를 만족함
- 원래 있던 클래스, 함수를 수정해야 한다 : OCP 위반
물론 상황에 따라 해당 선택을 피할 수 없는 케이스가 있을 수 있으니, 상황에 따라 적절한 방법을 활용하면 되겠습니다.
반응형
'FrontEnd' 카테고리의 다른 글
브라우저의 이벤트 루프와 렌더링의 관계에 대해 알아보자 (2) | 2022.12.28 |
---|---|
[번역] JS 번들 분할의 모든 것 (0) | 2022.12.26 |
Vue3 컴포넌트 디자인 패턴 : v-model (0) | 2022.12.22 |
Vue3 컴포넌트 디자인 패턴 : Object binding (0) | 2022.12.22 |
Vue3 컴포넌트 디자인 패턴 : Scoped Slots (0) | 2022.12.22 |