본문 바로가기

FrontEnd

Vue3 컴포넌트 디자인 패턴 : expose

반응형

Vue3의 expose에 대해 알아보고, 사용 사례를 알아봅니다.

원문 : https://www.vuemastery.com/blog/understanding-vue-3-expose/

 

Understanding Vue 3's "expose" | Vue Mastery

With the release of Vue 3.2 a new composition tool was made available for us, called "expose"

www.vuemastery.com

 

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 위반

물론 상황에 따라 해당 선택을 피할 수 없는 케이스가 있을 수 있으니, 상황에 따라 적절한 방법을 활용하면 되겠습니다.

 

반응형