[Do it! Vue.js] 03-2장~ 3장 마무리

버버니야·2022년 3월 18일
0

03-2 뷰 컴포넌트

컴포넌트란 조합하여 화면을 구성할 수 있는 블록(화면의 특정 영역)을 의미한다.

컴포넌트의 장점
1. 화면을 빠르게 구조화하여 일괄적인 패턴으로 개발 가능
2. 재활용할 수 있는 형태로 관리하면 코드의 재사용이 편리함
3. 코드의 가독성이 증가한다.

각각 분할된 영역은 컴포넌트를 의미한다.

왼쪽은 웹 페이지 한 화면의 영역을 각각 역할별로 분할
왼쪽 그림은 화면 전체를 Header, Content, Footer로 분할했다.
또 Content 영역을 Aside와 List로 분할했다.

왼쪽 그림은 컴포넌트 간의 관계를 나타낸 것인데, 자료구조의 트리와 비슷하다.

컴포넌트 등록하기

컴포넌트를 등록하는 방법은 전역과 지역 두 가지가 존재
지역 컴포넌트는 특정 인스턴스에서만 유효한 범위를 갖고, 전역 컴포넌트는 여러 인스턴스에서 공통으로 사용할 수 있다.

지역 - 특정 범위 내
전역 - 여러 인스턴스에서 공통으로 사용

전역 컴포넌트 등록

전역컴포넌트는 뷰 라이브러리를 로딩하고 나면 접근 가능한 Vue 변수를 이용해 등록.
전역 컴포넌트를 모든 인스턴스에 등록하려면 Vue 생성자에서 .component()를 호출해 수행

Vue.component('컴포넌트 이름', {
  	//컴포넌트 내용
});

전역 컴포넌트 등록 형식에는 컴포넌트 이름과 컴포넌트 내용이 있다.
컴포넌트 이름은 template 속성에서 사용할 HTML 사용자 정의 태그 이름을 의미

사용자 정의 태그 : HTML 표준 태그들이외에도 웹 개발자가 직접 정의하여 사용할 수 있느 태그

이름을 규칙은 '모두 소문자'와 '케밥 키법'을 따르지 않아도 된다.

케밥 기법 : 변수가 단어의 조합으로 이루어져 있을 때, 단어와 단어 사이를 -로 잇는 방식

<html>
    <head>
        <title>Vue Component Registration</title>
    </head>
    <body>
        <div id="app">
            <button>컴포넌트 등록</button>
            <my-component></my-component> //전역 컴포넌트 표시
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        <script>
            Vue.component('my-component', {
                template: '<div>전역 컴포넌트가 등록되었습니다.</div>'
            }); // 전역 컴포넌트 등록

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

전역 컴포넌트가 화면에 나타나는 과정
뷰 라이브러리 파일 로딩 -> 뷰 생성자로 컴포넌트 등록 -> 인스턴스 객체 생성 -> 특정 화면 요소에 인스턴스 부착 -> 인스턴스 내용 반환 -> 변환된 화면 요소를 사용자가 최종 확인

인스턴스 내용 변환에서 < my-component>가 < div>로 변환된다.

지역 컴포넌트 등록

지역 컴포넌트 등록은 전역 컴포넌트 등록과는 다르게 인스턴스에 components 속성을 추가하고 등록할 컴포넌트 이름과 내용을 정의하면 된다.

new Vue({
  components: {
    '컴포넌트 이름': 컴포넌트 내용
  }
});

지역 컴포넌트를 등록하는 방법


<script>
  var cmp = {
    // 컴포넌트 내용
    template: '<div>지역 컴포넌트가 등록되었습니다!</div>'
};

new Vue({
  el: '#app'
  components: {
  	'my-local-component': cmp
	}
});
</script>

변수 cmp에는 화면에 나타낼 컴포넌트의 내용을 정의.
컴포넌트의 내용에 template, data,methods등 여러 속성이 들어갈 수 있다.
컴포넌트 이름 : my-local-component
cmp라는 컴포넌트의 내용을 정의한 변수를 지정

지역 컴포넌트와 전역 컴포넌트의 차이

지역 컴포넌트와 전역 컴포넌트 등록하기

<html>
    <head>
        <title>Vue Component Registration</title>
    </head>
    <body>
        <div id="app">
            <h3>첫 번째 인스턴스 영역</h3>
            <my-global-component></my-global-component>
            <my-local-component></my-local-component>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        <script>
            Vue.component('my-global-component', {
                template: '<div>전역 컴포넌트입니다.</div>'
            });

            var cmp = {
                template: '<div>지역 컴포넌트입니다.</div>'
            };

            new Vue({
                el: '#app',

                components: {
                    'my-local-component': cmp
                }
            });
        </script>
    </body>
</html>

인스턴스 유효 범위를 벗어나면

<html>
    <head>
        <title>Vue Component Registration</title>
    </head>
    <body>
        <div id="app">
            <h3>첫 번째 인스턴스 영역</h3>
            <my-global-component></my-global-component>
            <my-local-component></my-local-component>
        </div>
        <div id="app2">
            <h3>두 번째 인스턴스 영역</h3>
            <my-global-component></my-global-component>
            <my-local-component></my-local-component>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        <script>
            Vue.component('my-global-component', {
                template: '<div>전역 컴포넌트입니다.</div>'
            });

            var cmp = {
                template: '<div>지역 컴포넌트입니다.</div>'
            };

            new Vue({
                el: '#app',

                components: {
                    'my-local-component': cmp
                }
            });
            new Vue({
                el: "#app2"
            });
        </script>
    </body>
</html>

app2라는 새로운 인스턴스를 생성했다. app2에는 새로운 지역 컴포넌트를 생성하지 않고 app의 지역 컴포넌트를 사용해보았다.

전역 컴포넌트는 화면에 표시가되지만 지역 컴포넌트는 그렇지 않다.
전역 컴포넌트와 지역 컴포넌트의 유효 범위가 다르기 때문이다.

두번째 인스턴스 영역인 < div id="app2">의 범위 안에서는 지역 컴포넌트가 인식이 되지 않는다.

03-3 뷰 컴포넌트 통신

컴포넌트 간 통신과 유효 범위

뷰의 경우 컴포넌트로 화면을 구성하므로 같은 웹 페이지라도 데이터를 공유할 수 없다.
이유 - 컴포넌트 마다 자체적으로 고유한 유효 범위를 갖기 때문.

상/하위 컴포넌트 관계

가장 기본적인 데이터 전달 방법은 상위(부모) - 하위(자식) 컴포넌트 간의 데이터 전달이다.
상위-하위 컴포넌트란 트리 구조에서 부모 노드, 자식 노드처럼 컴포넌트 간의 관계가 부모, 자식으로 이루어진 컴포넌트를 말한다.
지역 또는 전역 컴포넌트를 등록하면 등록된 컴포넌트는 자연스럽게 하위 컴포넌트가 된다.
그리고 하위 컴포넌트를 등록한 인스턴스는 상위 컴포넌트가 된다.

상위에서 하위로는 props라는 속성을 전달한다.
하위에서 상위로는 기본적으로 이벤트만 전달이 가능하다.

상위에서 하위 컴포넌트로 데이터 전달하기

props 속성

props는 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때 사용하는 속성.
props 속성을 사용하려면 하위 컴포넌트의 속성에 정의한다.

props 속성을 사용한 데이터 전달 예제

Vue.component('child-component', {
  props: ['props 속성 이름'],
});

그 다음 상위 컴포넌트의 HTML 코드에 등록된 child-component 컴포넌트 태그에 v-bind속성을 추가

<child-component v-bind:props 속성 이름="상위 컴포넌트의 data속성"></child-component>

v-vind 속성의 왼쪽 값으로 하위 컴포넌트에서 정의한 props 속성을 넣고, 오른쪽 값으로 하위 컴포넌트에 전달할 상위컴포넌트의 data 속성을 지정

<html>
    <head>
        <title>Vue Component Registration</title>
    </head>
    <body>
        <div id="app">
            <child-component v-bind:propsdata="message"></child-component>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        <script>
            Vue.component('child-component', {
                props: ['propsdata'],
                template: '<p>{{ propsdata }}</p>'
            });

            

            new Vue({
                el: '#app',
                data: {
                    message: 'Hello Vue! passed from Parent Component'
                }
            });
            
        </script>
    </body>
</html>


데이터가 이동하는 순서
1. new Vue()로 인스턴스를 생성
2. Vue.component()를 이용해 하위 컴포넌트인 chlid-component를 등록
3. child-component의 내용에 props 속성으로 propsdata를 정의
4. HTML에 컴포넌트 태그 추가 v-bind 속성을 보면, v-bind:propsdata="message"는 상위 컴포넌트의 message 속성 값인 Hello Vue! passed from Parent Component 텍스트를 하위 컴포넌트의 propsdata로 전달
5. child-component의 template 속성에 정의된 < p>{{ propsdata}}< /p>는 Hello..Component로 치환

뷰 인스턴스의 data 속성에 정의된 message 속성을 하위 컴포넌트 props로 전달

사실 위 코드에서는 child-component를 전역으로 등록했지 상위 컴포넌트를 지정하지 않았다.
그럼에도 뷰 인스턴스 안에 마치 상위 컴포넌트가 존재하는 것 처럼 하위 컴포넌트로 props를 사용했다
그 이유는 컴포넌트를 등록함과 동시에 뷰 인스턴스 자체가 상위 컴포넌트가 되기 때문이다.
인스턴스에 새로운 컴포넌트를 등록하면 기존에 있는 컴포넌트는 상위 컴포넌트가 되고, 새로 등록된 컴포넌트는 하위 컴포넌트가 된다. 이렇게 새 컴포넌트를 등록한 인스턴스를 최상위 컴포넌트라고 부른다.

하위에서 상위 컴포넌트로 이벤트 전달하기

이벤트 발생과 수신

하위 컴포넌트에서 상위 컴포넌트로의 통신은 이벤트를 발생시켜 상위 컴포넌트에 신호를 보낸다.
트특정 이벤트가 발생하면 상위 컴포넌트에서 해당 이벤트를 수신하여 상위 컴포넌트의 메서드를 호출한다.

이벤트 발생과 수신 형식

이벤트 발생과 수신은 $emit()과 v-on: 속성을 사용해 구현

this.$emit('이벤트명');

or

<child-component v-on:이벤트명="상위 컴포넌트의 메서드명"></child-component>

$emit()을 호출하면 괄호 안에 정의된 이벤트가 발생, 일반적으로 $emit()을 호출하는 우치ㅣ는 하위 컴포넌트의 특정 메서드 내부이다. 따라서 $emit()을 호출할 때 사용되는 this는 하위 컴포넌트를 가리킨다.

호출한 이벤트는 하위 컴포넌트를 등록하는 태그에서 v-on:으로 받는다.
하위 컴포넌트에서 발생한 이벤트명을 v-on:속성에 지정하고, 속성의 값에 이벤트가 발생했을 때 호출될 상위 컴포넌트의 메서드를 지정한다.

<html>
    <head>
        <title>Vue Component Registration</title>
    </head>
    <body>
        <div id="app">
            <child-component v-on:show-log="printText"></child-component>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        <script>
            Vue.component('child-component', {
                
                template: '<button v-on:click="showLog">show</button>',
                methods: {
                    showLog: function() {
                        this.$emit('show-log');
                    }
                }
            });

            

            new Vue({
                el: '#app',
                data: {
                    message: 'Hello Vue! passed from Parent Component'
                },
                methods: {
                    printText: function() {
                        console.log("recived an event");
                    }
                }
            });
            
        </script>
    </body>
</html>


child-component의 [show]버튼을 클릭해 이벤트를 발생시키고, 발생한 이벤트로 상위 컴포넌트의 printText() 메서드를 실행시키는 예제이다.

  1. [show]버튼을 클릭하면 클릭이벤트 v-on:click="showLog"에 따라 showLog()메서드가 실행
  2. showLog() 메서드 안에 this.$emit('show-log')가 실행되면서 show-log 이벤트 발생
  3. show-log 이벤트는 에 정의한 v-on:show-log에 전달되고 대상 메서드인 printText()가 실행된다.
  4. printText()는 recived an event라는 로그를 출력한다.

같은 레벨의 컴포넌트 통신

뷰는 상위에서 하위로만 데이터를 전달해야하는 기본적인 통신 규칙을 따르기 때문에 바로 옆 컴포넌트에 값을 전달하려면 하위에서 공통 상위 컴포넌트로 이벤트를 전달 후 공통 상위 컴포넌트에서 2개의 하위 컴포넌트에 props를 내려보내야한다.

하지만 이런 통신 구조를 유지하다보면 상위 컴포넌트가 필요 없음에도 같은 레벨 간에 통신을 위해 강제로 상위 컴포넌트를 둬야하기대문에 이벤트 버스를 사용한다.

관계 없는 컴포넌트 간 통신 - 이벤트 통신

이벤트 버스는 개발자가 지정한 2개의 컴포넌트 간에 데이터를 주고받을 수 있는 방법

이벤트 버스 형식

이벤트 버스의 형식은 아래와 같다.

var eventBus = new Vue();

이벤트 버스를 구현하려면 어플리케이션 로직을 담는 인스턴스와는 별개로 새로운 인스턴스를 1개를 더 생성하고, 새 인스턴스를 이용하여 이벤트를 보내고 받아야 한다.
보내는 .$emit()을, 받는 컴포넌트에서는 $on()을 구현한다.

이벤트를 보내는 컴포넌트

methods: {
	메서드명: function() {
		eventBus.$emit('이벤트명',데이터);
	}
}

이벤트를 받는 컴포넌트

methods: {
	created: function() {
		eventBus.$on('이벤트명', function(데이터) {
			...
		});
	}
}

이벤트 구현하기

<html>
  <head>
      <title>Vue Component Registration</title>
  </head>
  <body>
      <div id="app">
          <child-component></child-component>
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
      <script>
          var eventBus = new Vue();
          Vue.component('child-component', {
              template: '<div>하위컴포넌트의 영역입니다.<button v-on:click="showLog">show</button></div>',
              methods: {
                  showLog: function() {
                      eventBus.$emit('triggerEventBus', 100);
                  }
              }

          });

          var app = new Vue({
              el: '#app',
              created: function() {
                  eventBus.$on('triggerEventBus', function(value) {
                      console.log("이벤트를 전달받음, 전달받은 값 : ", value);
                  });
              }
          })
      </script>
  </body>
</html>

[show]버튼을 클릭해 showLog()가 실행되었을 때 eventBut의 이벤트가 발생한다.
발생한 이벤트는 상위컴포넌트의 created()에 있는 eventBus.$on()에서 전달받는다.

이벤트 버스를 활용하면 props 속성을 이용하지 않고도 원하는 컴포넌트 간에 직접적으로 데이터를 전달할 수 있어 편리하지만 컴포넌트가 많아지면 어디서 어디로 보냈는지 관리가 되지 않는 문제가 발생한다.

profile
안녕하세요

0개의 댓글