vue.js/vue.js 2

75. 컴포넌트 디자인 패턴 (common, slot, Controlled, Renderless )

DEV-Front 2022. 8. 15. 15:09
반응형

컴포넌트 디자인 패턴

 

1. Common - 기본적인 컴포넌트 등록과 컴포넌트 통신

<App.vue>

<template>
  <div id="app">
    <app-header :title="appTitle"></app-header>
    <app-content :items="items" @renew="renewItems"></app-content>
  </div>
</template>

<script>

import AppHeader from './components/AppHeader.vue'
import AppContent from './components/AppContent.vue'

export default {
  components: {
    AppHeader,
    AppContent
  },
 data() {
  return {
    appTitle: 'Common Approach',
    items: [10, 20, 30]
  };
 },
 methods: {
   renewItems() {
    this.items = [40, 50, 60]
  },
 },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

<AppHeader.vue>

<template>
    <div>
        <h1>{{ title }}</h1>
    </div>
</template>

<script>

export default {

	// 기존 props 방식
    // props: ['title'] 

    // Props Validation
    props:{
        title: String,
    }
};
</script>

<style lang="scss" scoped>

</style>

<AppContent.vue>

<template>
    <div>
        <ul>
            <li v-for="(item, idx) in items" v-bind:key="idx">
                {{item}}
            </li>
        </ul>

        <!-- 메서드 안만들고 클릭하자마자 상위 컴포넌트로 이벤트 올려보내기 가능 -->
        <button @click="$emit('renew')">renew items</button>
    </div>
</template>

<script>
export default {

  // Props Validation
  // array 가 아닌 데이터가 들어오면 에러처리
  props:{
    items:{
        type: Array,
        required: true,
    }
  }
};
</script>

<style lang="scss" scoped>

</style>

2. Slot - 마크업 확장이 가능한 컴포넌트

slot은 데이터는 위에있음 아래 컴포넌트에서 데이터를 표현만 해줄뿐

 

slot을 왜 쓸까?

- 요구사항이 바뀔때를 대비, 유연하게 화면의 영역을 확장 할 수 있다.

<App.vue>

<template>
  <div>
    <ul>
      <!-- 기존 컴포넌트는 이런 모양 <app-header></app-header> -->

      <!-- slot은 props 안내려도 v-for로 데이터 출력 가능  -->
      <item v-for="(item, idx) in items" v-bind:key="idx">{{ item }}</item>

      <item>
        <!-- slot의 장점 -->
        <!-- 만약 요구사항이 바뀔때 유연하게 대처 가능, 모델 마음대로 확장 가능 -->
        아이템 1
      </item>

      <item>
        <!-- 2. 버튼을 넣어주세요 -->
        아이템 2
        <button>click me</button>
      </item>

      <item>
        <!-- 3. 텍스트와 함께 이미지를 표현해주세요 -->
        <div>
          아이템 3
        </div>
        <img src="./assets/f7.png" alt="">
      </item>

      <item>
        <!-- 4. slot에는 style도 넣을수 있단 -->
        <div style="color: blue; font-size: 20px;">
          아이템 4
        </div>
      </item>

      <!-- <item v-for="(item, idx) in items" v-bind:key="idx" :item="item"></item> -->
    </ul>
  </div>
</template>

<script>
import Item from './Item.vue'

export default {
 components:{
    Item,
 },
 data() {
  return {
    items: ['slot 1', 'slot 2', 'slot 3', 'slot 4', 'slot 5']
  };
 },
};
</script>

<style lang="scss" scoped>

</style>

<Item.vue>

<template>
    <li>
        <slot>
            <!-- 등록하는 곳에서 정의할 화면 영역 -->
            <!-- {{ item }} -->
        </slot>
    </li>
</template>


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

</script>

3. Controlled - 결합력이 높은 컴포넌트

- 하위에서 관리해야 했던 데이터를 상위에서 관리하게 하는 방법

- 컴포넌트에 데이터 의존성 분리

<App.vue>

<template>
  <!-- <check-box :checked="checked"></check-box> -->
  <div>
    <check-box v-model="checked"></check-box>
  </div>
</template>
<!-- <script src="http://code.jquery.com/jquery-latest.min.js"></script> -->

<script>
import CheckBox from './components/CheckBox.vue'

export default {
  // @input 이벤트
  // :value 값
  components:{
    CheckBox,
  },
  data(){
    return{
      checked: false,
    }
  },  
};
</script>

<style scoped>


</style>

<CheckBox.vue>

<template>
    <div>
        <!-- <input type="checkbox" v-model="checked"> -->
        <input type="checkbox" :value="value" @click="toggleCheckBox"></input>
    </div>
</template>

<script>
export default {
    // props: ['checked'],
    // @input 이벤트
    // :value 값
    props: ['value'],
    methods: {
        toggleCheckBox() {
            this.$emit('input', !this.value) // true로 올리는것보단 반대로 되야하니까 not! 
        },
    },
};
</script>

<style lang="scss" scoped>

</style>

4. Renderless - 데이터 처리 컴포넌트

<App.vue>

<template>
  <div>
    <fatch-data url="https://jsonplaceholder.typicode.com/users/1">
      <!-- reader 함수로 이곳에 값이 노출 -->
      <div slot-scope="{response, loading}" v-if="!loading">
        {{ response }}
        
        <div v-if="loading">
          loading...
        </div>
      </div>
    </fatch-data>
  </div>
</template>

<script>
import FatchData from './components/FetchData.vue'

export default {
  components: {
    FatchData
  },  
};
</script>

<style lang="scss" scoped>

</style>

<FetchData.vue>

<template>
    <div>
        <p>name : {{ response.name }}</p>
        <p>email : {{ response.email }}</p>
    </div>
</template>

<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(erorr=>{
                alert('[ERROR] fetching the data', error);
                    console.log(erorr);
                });
    },
    // render 함수는? - 컴포넌트를 그리는것, 컴포넌트 표현 - 데이터만 넘겨줌. response, loading 넘겨줌, 노출 - 상위 컴포넌트 등록한곳에
    // $scopedSlots.default 는? - 하위컴포넌트 데이터를 상위에 노출 시키는것 상위에서 접근할수 있게하는것
    
    render(){
        return this.$scopedSlots.default({
            response: this.response,
            loading: this.loading
        })
    }
};
</script>

<style lang="scss" scoped>

</style>
반응형