danbibibi
article thumbnail
Published 2023. 5. 14. 03:06
[Vue 기초 4] Component WEB/front-end

Component

  • Vue의 가장 강력한 기능 중 하나
  • HTML Element를 확장하여 재사용 가능한 코드를 캡슐화
  • Vue Component는 Vue Instance이기도 하기 때문에 모든 옵션 객체를 사용
  • Life Cycle Hook 사용 가능
중복 코드를 제거하고 유지보수가 쉬운 코드를 작성할 수 있으며, 애플리케이션 규모가 큰 경우에도 쉽게 확장 가능

 

전역 component

  • Vue.component(tagName, options)로 등록
  • 권장하는 컴포넌트 이름 : 케밥 표기법 (전부 소문자, -)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app1">
      <my-global></my-global>
      <my-global></my-global>
    </div>
    <div id="app2">
      <my-global></my-global>
      <my-global></my-global>
    </div>
    <script>
      Vue.component("MyGlobal", {
        template: "<h2>전역 컴포넌트임</h2>",
      });
      new Vue({
        el: "#app1",
      });
      new Vue({
        el: "#app2",
      });
    </script>
  </body>
</html>

 

지역 component

  • 컴포넌트를 components 인스턴스 옵션으로 등록
  • 특정 인스턴스 내에서만 사용 가능한 컴포넌트
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app1">
      <my-local></my-local>
      <my-local></my-local>
    </div>
    <div id="app2">
      <my-local></my-local>
      <my-local></my-local>
    </div>
    <script>
      new Vue({
        el: "#app1",
        components: {
          MyLocal: {
            template: "<h2>지역 컴포넌트</h2>",
          },
        },
      });
      new Vue({
        el: "#app2",
      });
    </script>
  </body>
</html>

 

component template

  • Vue.js에서 컴포넌트를 정의할 때, data 옵션에 객체를 직접 할당하는 것이 아니라 함수를 사용해야 함
  • 컴포넌트가 재사용될 때 데이터가 공유되는 것을 방지하기 위해!!
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <my-comp></my-comp>
    </div>
    <template id="mycomp">
      <div>
        <h2>{{msg}}</h2>
      </div>
    </template>
    <script>

      // Vue.component('MyComp', {
      //   template: '#mycomp',
      //   data: {
      //     msg: 'hello component',
      //   },
      // });

      Vue.component("MyComp", {
        template: "#mycomp",
        data() {
          return {
            msg: "hello component",
          };
        },
      });

      new Vue({
        el: "#app",
      });
    </script>
  </body>
</html>

 

Component 간 통신

  • 상위(부모) - 하위(자식) 컴포넌트 간의 data 전달 방법
  • 부모에서 자식 : props라는 특별한 속성을 전달 (Pass Props)
  • 자식에서 부모 : event로만 전달 가능 (Emit Event)

 

상위에서 하위 컴포넌트로 data 전달, props

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <h2>props test</h2>
      <child-component props-data="안녕하세요"></child-component>
    </div>
    <script>
      //하위 컴포넌트
      Vue.component("child-component", {
        props: ["propsData"],
        template: "<span>{{ propsData }}</span>",
      });
      new Vue({
        el: "#app",
        // data() {
        //   return {
        //     message: 'hello',
        //   };
        // },
      });
    </script>
  </body>
</html>
Vue.js에서는 props의 이름을 camelCase로 정의하지만,
HTML에서는 소문자와 대문자를 구분하지 않기 때문에
kebab-case(하이픈)로 작성된 속성 이름을 props 이름으로 사용해야함

 

동적 props

  • v-bind 를 사용하여 부모의 데이터에 props를 동적으로 바인딩 할 수 있음
  • 데이터가 상위에서 업데이트 될 때마다 하위 데이터로도 전달됨
  • v-bind 에 대한 단축 구문을 사용하는 것이 더 간단함
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <child-comp
        v-for="(area, i) in areas"
        :key="i"
        :area="area"
        v-bind:msg="msg[parseInt(Math.random() * 4)]"
      >
      </child-comp>
    </div>
    <template id="childcomp">
      <div>
        <h2> {{area}}이(가) 여행지로 {{msg}}</h2>
      </div>
    </template>
    <script>
      Vue.component("childComp", {
        props: {
          area: String,
          msg: {
            type: String,
            require: true,
          },
        },
        template: "#childcomp",
      });
      new Vue({
        el: "#app",
        data: {
          areas: ["서울", "부산", "제주"],
          msg: ["굿^^", "최고!!", "좋아!!", "짱!!"],
        },
      });
    </script>
  </body>
</html>
오브젝트의 모든 속성을 전달 할 경우, v-bind:prop-name 대신 v-bind만 작성함으로써 모든 속성을 prop으로 전달 할 수 있음
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <h2>컴포넌트 객체 데이터 전달</h2>
    <div id="app">
      <member-view :member="user"></member-view>
    </div>
    <template id="memberview">
      <div>
        <div>이름 : {{ member.name }}</div>
        <div>나이 : {{ member.age }}</div>
        <div>이메일 : {{ member.email }}</div>
      </div>
    </template>
    <script>
      Vue.component("member-view", {
        props: ["member"],
        template: "#memberview",
      });
      new Vue({
        el: "#app",
        data() {
          return {
            user: {
              name: "홍길동",
              age: 22,
              email: "hong@example.com",
            },
          };
        },
      });
    </script>
  </body>
</html>

 

하위에서 상위 컴포넌트로 event 전달

  • 하위 컴포넌트에서 상위 컴포넌트가 지정한 이벤트를 발생 $emit
  • 상위 컴포넌트는 하위 컴포넌트가 발생한 이벤트를 수신(on) 하여 data 처리

 

하위 컴포넌트 (자식 컴포넌트)

<template>
  <button v-on:click="sendMessage">메시지 보내기</button>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      this.$emit('message-sent', '안녕하세요!');
    }
  }
};
</script>

 

상위 컴포넌트 (부모 컴포넌트)

<template>
  <div>
    <child-component v-on:message-sent="handleMessage"></child-component>
    <p>{{ message }}</p>
  </div>
</template>

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

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      message: ''
    };
  },
  methods: {
    handleMessage(message) {
      this.message = message;
    }
  }
};
</script>

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h4>당신이 좋아하는 파트를 선택하세요</h4>
      <h2>총 투표수 : {{ total }}</h2>
      <subject v-on:add-total-count="addTotalCount" title="코딩"></subject>
      <subject v-on:add-total-count="addTotalCount" title="알고리즘"></subject>
    </div>
    <script>
      Vue.component("Subject", {
        template: '<button v-on:click="addCount">{{title}} : {{ count }}</button>',
        props: ["title"],
        data: function () {
          return {
            count: 0,
          };
        },
        methods: {
          addCount: function () {
            this.count += 1;
            // 부모 v-on:이름 에 해당하는 이름의 이벤트를 호출
            this.$emit("add-total-count", "인자도 넘길수 있어요."); // payload
          },
        },
      });

      new Vue({
        el: "#app",
        data: {
          total: 0,
        },
        methods: {
          addTotalCount: function (msg) {
            console.log(msg);
            this.total += 1;
          },
        },
      });
    </script>
  </body>
</html>

 

비 상하위간 통신

  • 비어 있는 Vue Instance 객체를 Event Bus로 사용
  • 복잡해질 경우 상태관리 라이브러리인 Vuex 사용 권장
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <my-count></my-count>
      <log></log>
    </div>
    <template id="mycount">
      <div>
        <input type="number" v-model.number="count" @keyup.enter="send" />
        <button @click="send">보내기</button>
      </div>
    </template>
    <template id="log">
      <div>
        <h2>{{count}}</h2>
        <ul>
          <li v-for="msg, index in list" :key="index">{{msg}}</li>
        </ul>
      </div>
    </template>
    <script>
      const bus = new Vue();
      Vue.component("my-count", {
        template: "#mycount",
        data() {
          return {
            count: 0,
          };
        },
        methods: {
          send() {
            bus.$emit("updateLog", this.count);
            this.count = "";
          },
        },
      });

      Vue.component("log", {
        template: "#log",
        data() {
          return {
            count: 0,
            list: [],
          };
        },
        methods: {
          updateLog(data) {
            this.count += data;
            this.list.push(`${data}을 받았습니다.`);
          },
        },
        created: function () {
          bus.$on("updateLog", this.updateLog);
        },
      });

      new Vue({
        el: "#app",
      });
    </script>
  </body>
</html>
profile

danbibibi

@danbibibi

꿈을 꾸는 시간은 멈춰 있는 것이 아냐 두려워하지 마 멈추지 마 푸른 꿈속으로