15 –애플리케이션 실전 파트– 컴포넌트 디자인 패턴

source: categories/study/vue-beginner-lv3/vue-beginner-lv3_9-06.md

15.1 Component Design Patterns 소개

컴포넌트 디자인 패턴

  1. Common - Common 패턴은 기본적인 컴포넌트 등록과 컴포넌트 통신 방식을 의미합니다.
  2. Slot - Slot 패턴은 마크업 확장이 가능한 컴포넌트를 의미합니다.
  3. Controlled - Controlled 패턴은 결합력이 높은 컴포넌트를 의미합니다.
    아직까지 한번도 보여드리진 않았습니다.
    Controlled 패턴을 사용하시게되면 특히 써드 파티 라이브러리들.. 그리고 체크박스를 컨트롤할 때 쉽게 컨트롤할 수 있게끔 컴포넌트를 설계할 수 있습니다.
  4. Renderless - Renderless 패턴은 데이터 처리 컴포넌트를 의미합니다.
    한마디로 template 표현식이 없습니다.
    script단에서 어떤 비즈니스 로직만 처리해서 상위 컴포넌트로 다시 데이터를 노출시켜주는 역할을 합니다.

(feat. + HOC, Mixins)

15.2 Common Approach

props validation 문법



export default {
   // 일반적으로 props를 아래와 같이 배열로 선언했었다.
   // props: ['title'],
   // props는 컴포넌트간 주고받는 데이터이다보니까 validation이라고 하는 유효성 검사가 필요해서, 
   // 아래처럼 type이라던지, 혹은 required - 필수 속성이라던지, 기본값을 설정할 수가 있다. 
   // props validation 문법이다.
   props: {
      title: String,
   }
}


  1. App.vue 데이터를 내려주고(props) event emit을 받는 컨테이너 컴포넌트
  2. AppContent.vue, AppHeader.vue 화면에 뿌려주는 프레젠테이셔널 컴포넌트
    • 단방향 통신 준수
    • N:N 통신 지양

15.3 Component with Slots - Slot VS Props

15.4 Component with Slots 구현 방법과 활용처

위와 같이 slot을 사용하게되면 하위 컴포넌트의 데이터에 대한 의존성이 없어집니다.

위와 같이 Item에 데이터는 없고 데이터가 여전히 ‘상위 컴포넌트'에만 머물러있습니다.
현재 Item.vue 컴포넌트는 App.vue에 있는 데이터를 표현만 했을 뿐입니다.
만약 props로 내려준다면 하위 컴포넌트에서 내려받은 props를 바꾸지 못한다거나 제약사항이 생기는거죠.

slot을 쓰는 이유

‘요구 사항' 때문.
위와 같은 list 컴포넌트를 사용할 때, 특정 list 부분에 버튼이 들어가게 해주세요. 라는 요구사항이 왔다면,
slot을 사용하지 않은 상태라면 해당 컴포넌트는 사용하지 못하게됩니다.
왜냐면 굳어있기 때문입니다.

slot을 사용하면 유연하게 확장할 수 있습니다.

15.5 Controlled Component - Input 박스를 다룰 때 생기는 문제점

위와 같이 컴포넌트를 세밀하게 쪼갤 때가 온다.
이런 경우를 대비해서 살펴보도록하자.

위와 같이 코딩을 하면,


[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "checked"

위와 같이 에러가 뜨게된다.
props로 내려받은 값을 하위 컴포넌트에서 직접적으로 수정하지 말라 라는 에러메시지입니다.
N:N 통신을 방지하기 위해 컴포넌트 통신 방식 위에서 아래로는 props 아래에서 위로는 event emit이 존재하는건데,



<template>
  <!-- 일반적으로 input에 v-model을 썼다. 2-way 바인딩. -->
  <!-- 그런데 이와 같은 코딩은 잘못된 코딩이다. -->
  <input type="checkbox" v-model="checked">
</template>

<script>
export default {
  props: ['checked'],
}
</script>


위와 같이 v-model로 하위 컴포넌트에서 2-way 데이터 바인딩으로 묶어버리면,



<template>
  <!-- 일반적으로 input에 v-model을 썼다. 2-way 바인딩. -->
  <!-- 그런데 이와 같은 코딩은 잘못된 코딩이다. -->
  <input type="checkbox" :value="checked" @input="updateInput">
</template>

<script>
export default {
  props: ['checked'],
  methods: {
    updateInput: function(event) {
      var updatedText = event.target.value;
      this.checked = updatedText;
    }
  },
}
</script>


v-model은 사실 위와 같은 구조입니다.
즉, 하위 컴포넌트에서 상위 data의 값을 바꿔버리는 상황입니다.
이를 어떻게 해결할 수 있을까?

15.6 Controlled Component - 구현 방법과 활용처

이 방법도 그렇고 slot도 그렇고 하위에서 관리해야될 데이터(props)를 상위 컴포넌트에서 관리하도록 만든다.
컴포넌트의 데이터 의존성을 분리하는 것들이 저희가 지금 배우고있는 디자인 패턴의 핵심이다.

달력, 모달, 간단 입력폼 - 위와 같은 방법으로하면 굉장히 유용하다.

번외 - vuex, redux 같은 상태관리 상태의 컴포넌트(컨테이너 컴포넌트, 프레젠테이셔널 컴포넌트)를 위한 storybook 설정 연구

겨우 에러안나게는 했다..
그런데 실제로 사용하려면..?
좀 더 연구 필요..

15.7 Renderless Component - 소개

위의 FetchData.vue를 보면 template이 없다.
오직 script만 있다.
데이터 제공만을 위한 컴포넌트이다.

FetchData.vue



<script>
import axios from 'axios';

export default {
  props: ['url'],
  data() {
    return {
      response: null,
      loading: true,
    }
  },
  created() {
    axios.get(this.url)
      .then(response => {
        this.response = response.data;
        this.loading = false;
      })
      .catch(error => {
        alert('[ERROR] fetching the data', error);
        console.log(error);
      });
  },
  render() {
    return this.$scopedSlots.default({
      response: this.response,
      loading: this.loading,
    });
  },
}
</script>


15.8 Renderless Component - Render Function

15.9 Renderless Component - 구현 방법과 활용처

아래에서 데이터를 전달하기 때문에 하위 컴포넌트에 데이터가 있다.
그리고 기존과는 다르게 하위 컴포넌트의 데이터 값을 변경하면 그것이 화면에 반영된다.
(이런 점은 기존과 달라서 좀 헷갈릴수도 있으려나?)

하위 컴포넌트의 데이터를 상위컴포넌트에서 참고하는 기이한 현상이 발생한다는 것이다.
원래 데이터는 상위 컴포넌트에서 하위 컴포넌트로 props로 내려주는데..

여튼 이런식으로 유연하게 컴포넌트를 설계할 수 있다.