본문 바로가기
Dev/Vue.js

[Vue.js] Hacker News - 컴포넌트 디자인 패턴

by dev_jsk 2020. 10. 7.
728x90
반응형

Component Design Patterns

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

일반적인 컴포넌트 사용 패턴으로 상위 컴포넌트와 하위 컴포넌트 간 props 속성Event emit을 사용하여 통신하는 방법

 

예제

- App.vue

<template>
  <div>
    <!-- 일반적인 컴포넌트 사용 방식 -->
    <!-- props 속성, event emit 사용 하여 데이터 통신 -->
    <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>

- AppHeader.vue

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

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

- AppContent.vue

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

<script>
export default {
  props: {
    items: {
      type: Array,
      required: true,
    },
  },
};
</script>

결과

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

마크업 확장 컴포넌트를 구성하는 패턴

 

예제

- App.vue

<template>
  <div>
    <ul>
      <item>
        <!-- slot 에 들어갈 내용 -->
        아이템 1
      </item>
      <item>
        아이템 2 <button>click me</button>
      </item>
      <item>
        <div>아이템 3</div>
        <img src="./assets/unnamed.png" alt="스누피" width="50px" height="50px">
      </item>
      <item>
        <div style="color: blue; font-size: 20px;">아이템 4</div>
      </item>
    </ul>
  </div>
</template>

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

export default {
  components: {
    Item,
  },
}
</script>

- Item.vue

<template>
  <li>
    <slot>
      <!-- 등록하는 곳(상위 컴포넌트)에서 정의할 화면 영역 -->
    </slot>
  </li>
</template>

결과

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

컴포넌트를 세밀하게 나눌 때 사용하는 패턴으로 상위 컴포넌트에서 하위 컴포넌트의 데이터를 관리하고, 하위 컴포넌트에서 Event emit을 통해 상위 컴포넌트에 전달하여 데이터 변화를 주는 방법

 

예제

- App.vue

<template>
  <!-- 상위 컴포넌트에서 데이터 관리 -->
  <!-- v-model 은 내부에서 input 이라는 이벤트와 value 라는 데이터를 통해 값을 바꾸고 있다 -->
  <check-box v-model="checked"></check-box>
</template>

<script>
import CheckBox from "./components/CheckBox.vue";

export default {
  components: {
    CheckBox,
  },
  data() {
    return {
      checked: false,
    };
  },
};
</script>

- CheckBox.vue

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

<script>
export default {
  // v-model 은 내부에서 input 이라는 이벤트와 value 라는 데이터를 통해 값을 변경한다.
  // @input 이벤트
  // :value 값

  // v-model 은 내부에서 value 라는 데이터를 사용
  props: ["value"],
  methods: {
    toggleCheckBox() {
      // 상위 컴포넌트로 이벤트 전달
      // v-model 은 내부에서 input 이라는 이벤트 사용
      this.$emit("input", !this.value);
    },
  },
};
</script>

결과

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

표현을 하지 않는 컴포넌트, 데이터 제공만 하는 컴포넌트를 구성하는 패턴으로 render() 를 이용하여 화면을 구성하는 방법

 

예제

- App.vue

<template>
  <div>
    <fetch-data url="https://jsonplaceholder.typicode.com/users/1">
      <!-- slot-scope 선언된 태그 내에서만 response, loading 접근 가능 -->
      <div slot-scope="{ response, loading }">
        <div v-if="!loading">
          <p>name: {{ response.name }}</p>
          <p>email: {{ response.email }}</p>
        </div>
        <div v-if="loading">
          Loading...
        </div>
      </div>
    </fetch-data>
  </div>
</template>

<script>
import FetchData from "./components/FetchData.vue";

export default {
  name: "app",
  components: {
    FetchData,
  },
};
</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() {
    // $scopedSlots : 등록한 하위 컴포넌트의 데이터
    // 유연하게 컴포넌트 설계 가능
    return this.$scopedSlots.default({
      response: this.response,
      loading: this.loading,
    });
  },
}
</script>

결과

※ Render Function

render() 함수 사용 예제

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Render Function</title>
  </head>
  <body>
    <div id="app">
      <!-- 인스턴스 영역 -->
      <!-- <p>{{ message }}</p> -->
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      new Vue({
        el: "#app",
        data: {
          message: "Hello Vue",
        },
        // template: '<p>{{ message }}</p>',
        render: function(createElement) {
            // return createElement('태그 이름', '태그 속성', '하위 태그 내용');
            return createElement('p', this.message);
        }
      });
    </script>
  </body>
</html>

결과

main.js 에서 render() 함수 표현 방법

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  // 1
  // render: function(createElement) {
  //   return createElement(App);
  // },
  // 2
  // Vue.js 는 Virtual DOM 사용
  // 따라서 createElement -> HyperScript의 약자인 h로 줄여서 쓴다.
  // render: function(h) {
  //   return h(App);
  // },
  // 3
  // Arrow Function 사용
  // render: (h) => {
  //   return h(App);
  // },
  // 4
  // render : h => h(App),
}).$mount('#app')

참고

v3.vuejs.org/guide/render-function.html

 

Render Functions | Vue.js

Render Functions Vue recommends using templates to build applications in the vast majority of cases. However, there are situations where we need the full programmatic power of JavaScript. That's where we can use the render function. Let's dive into an exam

v3.vuejs.org

 

728x90
반응형

댓글