컴포넌트 디자인 패턴 (common, slot, controlled, renderless) 간단 정리

 

아웅 정리할수록 복잡하고 더 헷갈리네ㅎ

이런 것들 언젠가 편하게 사용하겠지?ㅜ

아무튼  common, slot, controlled, renderless 디자인 패턴에 대해 알아보자

 

 

컴포넌트 디자인 패턴

Common

  • 간단하고 기본적인 컴포넌트 등록과 컴포넌트 통신
  • AppContent, Appheader 컴포넌트를 불러옴
  • 이벤트, props 활용
before after

 

 

버튼을 클릭하면 내용이 바뀌도록 구현해보자

// CommonView.vue

<template>
  <div>
    <app-header :name="userName"></app-header>
    <app-content :items="items" @evol="evolItems"></app-content>
  </div>
</template>


<script>
import AppHeader from '../components/AppHeader.vue';
import AppContent from '../components/AppContent.vue';

export default {
  components: {
    AppHeader,
    AppContent,
  },
  data() {
    return {
      userName: '포켓몬',
      items: ['피카츄', '꼬부기', '파이리'],
    }
  },
  methods: {
    evolItems() {
      this.items = ['라이츄', '어니부기', '리자돈'];
    },
  },
}
</script>

 

import한 AppHeader 컴포넌트와 AppContent 컴포넌트 간 데이터를

이벤트, props로 주고받고 있다.

 

 

// AppHeader.vue

<template>
  <header>
    <h1>{{ name }}</h1>
  </header>
</template>

<script>
export default {
  props: {
    name: String,
  }
}
</script>

 

AppHeader에서는 userName을 props로 받아서 그대로 표시

 

 

// AppContent.vue

<template>
  <div>
    <ul>
      <li v-for="item, index in items" :key="index">
        {{ item }}
      </li>
    </ul>
    <button @click="$emit('evol')">진화</button>
  </div>
</template>

<script>
export default {
  props: {
    items: {
      type: Array,
      // 필수값 정의
      required: true,
    },
  },
}
</script>

 

AppContent에서는 버튼이 있는데

클릭하면 evol 이벤트를 전송

 

 

// CommonView.vue

...
<app-content :items="items" @evol="evolItems"></app-content>
...
methods: {
    evolItems() {
      this.items = ['라이츄', '어니부기', '리자돈'];
    },
  },

 

CommonView 에서 evol 이벤트 발생 시 호출할 메서드를 통해 내용이 바뀌게 된다.

 


 

Slot

  • 마크업 확장이 가능한 컴포넌트
  • 확장이 유연하다
  • slotComponent에 배치된 slot들을 상위 컴포넌트에서 정의한다.

 

 

slot을 활용해서 위 구조처럼 개발해보자

// SlotComponent.vue

<template>
  <div>
    <div>
      <slot name="slotTest">
        empty
      </slot>
    </div>
    <div>
      <slot name="slotRouterTest">
        empty
      </slot>
    </div>
    <div>
      <slot name="slotEmptyTest">
        empty
      </slot>
    </div>
  </div>
</template>

<script>
export default {

}
</script>

 

slot을 정의한 컴포넌트

각 slot 별로 name도 정의했고 현재는 empty 라는 텍스트만 있다.

이제 상위 컴포넌트에서 slot을 채워보자

 

 

// 상위 컴포넌트

<template>
  <div>
    <SlotComponent>
      <!-- slotTest라는 slot을 정의 -->
      <template slot="slotTest">
        <div>텍스트</div>
      </template>
      <!-- slotRouterTest라는 slot을 정의 -->
      <router-link slot="slotRouterTest" :to="`/.`">
        링크
      </router-link>
    </SlotComponent>
  </div>
</template>
<script>
import SlotComponent from '../components/SlotComponent.vue'
export default {
  components: {
    SlotComponent,
  }
}
</script>
<style></style>

 

상위 컴포넌트에서 slot들을 재정의한다.

하나는 template을, 하나는 router-link를

마지막 하나는 재정의하지 않았다.

 


 

Controlled

  • 하위에서 관리해야 했던 데이터를 상위에서 관리하게 하는 방법
  • props로 전달하지 않고 v-model 활용
  • 컴포넌트에 데이터 의존성 분리

v-model양방향 데이터 바인딩을 지원한다.

즉, 화면의 데이터와 뷰 인스턴스의 데이터가 일치한다.

또한 v-model 은 @input이벤트와 :value로 이루어져있다.

 

 

props와 v-model의 데이터를 전달을 비교해보자

 

일단 props로 전달해보자

<template>
  <div>
	  <div>
	    props 사용
	    <check-box-2 :checked2="checked2" @renew="renewItems"></check-box-2>
	    checked2는 {{ checked2 }}
	  </div>
  </div>
</template>

<script>
import CheckBox2 from '../components/CheckBox2.vue';
export default {
  components: {
    CheckBox2
  },
  data() {
    return {
      checked2: false,
    }
  },
  methods: {
    renewItems() {
      this.checked2 = !this.checked2
    }
  }
}
</script>

 

우선 하위 컴포넌트에게 checked2 데이터를 보내고 있다.

보내지 않으면 데이터가 비겠지

 

 

<template>
  <input type="checkbox" @click="toggleCheckBox">
</template>

<script>
export default {
  // v-model이 :value로 내려옴
  props: {
    checked2: Boolean
  },
  methods: {
    toggleCheckBox() {
      // 하위에서 상위로 이벤트 보내기
      this.$emit('renew');
    }
  }
}
</script>

 

하위에서는 props로 데이터를 받아야한다.

그리고 버튼 클릭시 이벤트를 보내고 부모에서 해당 이벤트를 받으면

checked2 데이터를 변경하는 메서드를 수행해야 한다.

 

 

이제 v-model를 사용해보자

<template>
  <div>
      <div>
        v-model 사용
        <check-box v-model="checked"></check-box>
        checked는 {{ checked }}
      </div>
  </div>
</template>

<script>
import CheckBox from '../components/CheckBox.vue';
export default {
  components: {
    CheckBox,
  },
  data() {
    return {
      checked: false,
    }
  },
}
</script>

 

v-model은 양방향 디렉티브니까 데이터를 따로 보내지 않아도 된다.

 

 

<template>
  <input type="checkbox" :value="value" @click="toggleCheckBox">
</template>

<script>
export default {
  // v-model이 :value로 내려옴
  props: ['value'],
  methods: {
    toggleCheckBox() {
      // 하위에서 상위로 데이터 올리기
      this.$emit('input', !this.value);
    }
  }
}
</script>

 

자식에서 :value를 통해 props로 받고,

버튼을 클릭하면 데이터를 input 이벤트가 발생한다.

 

이를 통해 자식에서 관리해야 했던 데이터를 부모에서 관리하게 하여

컴포넌트 데이터 의존성을 분리할 수 있다.

 


 

Renderless

  • 데이터 처리 컴포넌트
  • 템플릿 표현식이 없다!
  • 그저 스크립트로 로직만 있는 컴포넌트
  • 데이터 제공만하는 컴포넌트

render

render는 template를 내부적으로 구현하는 함수

createElement를 기본 인자로 받는다.

즉, 태그 명, 속성, 태그를 인자로 받는다.

 

코드로 이해해보장

<template>
  <div>
		<!-- 1. url 전달 -->
    <fetch-renderless url="url.com">
       <!-- 2. slot-scope -->
      <div slot-scope="{ response, loading}">
        <div v-if="!loading">
          {{ response }}
        </div>
        <div v-else>
         로딩중
        </div>
      </div>
    </fetch-renderless>
  </div>
</template>

<script>
import FetchRenderless from '../components/FetchRenderless.vue'
export default {
  components: {
    FetchRenderless
  },
}
</script>

 

FetchRenderless 컴포넌트에 props로 url을 전달

slot-scopt를 통해 하위 컴포넌트의 데이터(response, loading) 접근!

 

// FetchRenderless.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 함수
  render() {
    // scopedSlots : 하위 컴포넌트의 데이터(response, loading)에 접근할 수 있음
    return this.$scopedSlots.default({
      response: this.response,
      loading: this.loading,
    });
  },
}
</script>

 

FetchRenderless 컴포넌트는 template 없이 그저 비즈니스 로직으로만 이루어져 있다.

'🧠 저장 > Vue' 카테고리의 다른 글

vue2 watch와 computed의 차이점  (0) 2024.01.24
vue2 네비게이션 가드 간단 정리  (0) 2024.01.21
mixin 간단 정리  (0) 2024.01.09
하이오더컴포넌트(HOC) 간단 정리  (0) 2024.01.03
eventBus 간단 정리  (0) 2023.12.31