ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3장. 화면을 개발하기 위한 필수 단위 - 인스턴스 & 컴포넌트 - 3
    개발 공부/Do it! Vue.js 입문 2025. 5. 22. 09:00
    반응형



     

    3장 - 3

     

     

     


    03-3 뷰 컴포넌트 통신

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

    • 앵귤러1이나 백본(Backbone.js)과 같은 초창기 자바스크립트 프레임워크에서는 한 화면을 1개의 뷰(View)로 간주했다. 따라서 한 화면의 데이터를 해당 화면 영역 어디서든 호출할 수 있었다.
      • 백본: MV*(Model-View-Controller 또는 Presentational) 자바스크립트 프레임워크로, 앵귤러1이 유행하던 시기에 쌍벽을 이루던 오픈 소스 라이브러리
    • 하지만 뷰(Vue.js)의 경우 컴포넌트로 화면을 구성하므로 같은 웹 페이지라도 데이터를 공유할 수 없다. 그 이유는 컴포넌트마다 자체적으로 고유한 유효 범위(Scope)를 갖기 때문이다.
    • 이는 뷰 프레임워크 내부적으로 정의된 특징임으로, 각 컴포넌트의 유효 범위가 독립적이기 때문에 다른 컴포넌트의 값을 직접적으로 참조할 수가 없다.
    • 그에 대한 아래의 예제를 살펴보자.
    <html>
        <head>
            <title>Vue Component Scope</title>
            <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        </head>
        <body>
            <div id="app">
                <my-component1></my-component1>
                <my-component2></my-component2>
            </div>
            
            <script>
                // 첫 번째 컴포넌트 내용
                var cmp1 = {
                    template: '<div>첫 번째 지역 컴포넌트 : {{ cmp1Data }}</div>',
                    data: function() {
                        return {
                            cmp1Data : 100
                        }
                    }
                };
                // 두 번째 컴포넌트 내용
                var cmp2 = {
                    template: '<div>두 번째 지역 컴포넌트 : {{ cmp2Data }}</div>',
                    data: function() {
                        return {
                            cmp2Data : cmp1.data.cmp1Data
                        }
                    }
                };
                new Vue({
                    el: '#app',
                    // 지역 컴포넌트 등록
                    components: {
                        'my-component1': cmp1,
                        'my-component2': cmp2
                    }
                });
            </script>
        </body>
    </html>
    • 이 예제는 2개의 지역 컴포넌트를 등록하고, 한 컴포넌트에서 다른 컴포넌트의 값을 직접 참조하는 예제이다.
    • 이 예제를 실행하면 다음과 같은 결과 화면이 나온다.

     

    컴포넌트 유효 범위 확인 코드 결과 화면

    • {{ cmp2Data }}에 값이 출력되지 않은 이유는 my-component2에서 my-component1의 값을 직접 참조할 수 없기 때문이다. 즉, 컴포넌트의 유효 범위로 인해 다른 컴포넌트의 값을 직접 접근하지 못하기 때문에 나타난 결과이다.
    • 뷰에서 미리 정의해 놓은 데이터 전달 방식에 따라 일관된 구조로 애플리케이션을 작성하게 된다. 그러므로 개발자 개개인의 스타일대로 구성되지 않고 애플리케이션이 모두 동일한 데이터 흐름을 갖는다.
    • 이렇게 되면 다른 사람의 코드를 빠르게 파악할 수 있어 협업하기에도 좋다.

     

    <상 · 하위 컴포넌트 관계>

    • 뷰 프레임워크에서 컴포넌트는 직접 다른 컴포넌트의 값을 참조할 수 없기 때문에 가장 기본적인 데이터 전달 방법은 바로 상위(부모) - 하위(자식) 컴포넌트 간의 데이터 전달 방법이다.
    • 상위 - 하위 컴포넌트란 트리 구조에서 부모 노드, 자식 노드처럼 컴포넌트 간의 관계가 부모, 자식으로 이루어진 컴포넌트를 의미한다.
    • 지역 또는 전역 컴포넌트를 등록하면 등록된 컴포넌트는 자연스럽게 하위(자식) 컴포넌트가 되고, 하위 컴포넌트를 등록한 인스턴스는 상위(부모) 컴포넌트가 된다.

     

    상위 - 하위 컴포넌트 간 통신 방식

    • 위 그림은 뷰에서 상위 - 하위 컴포넌트 간에 데이터를 전달하는 기본적인 구조를 나타낸다.
    • 상위에서 하위로는 props라는 특별한 속성을 전달하며, 하위에서 상위로는 기본적으로 이벤트만 전달할 수 있다.
      • 참고로 이벤트와 함께 데이터를 전달하고 싶은 경우에는 이벤트의 두 번째 인자 값으로 전달하거나 이벤트 버스(Event Bus)를 활용하는 방법이 있다.

     

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

    props 속성

    • props 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때 사용하는 속성이다.
    • props 속성은 아래와 같이 컴포넌트의 속성을 정의한다.
    Vue.component('child-component', {
    	props: ['props 속성 이름']
    });
    • 그런 다음 상위 컴포넌트의 HTML 코드에 등록된 child-component 컴포넌트 태그에 v-bind 속성을 추가한다.
    <child-component v-bind:props 속성 이름="상위 컴포넌트의 data 속성"></child-component>
    • v-bind 속성의 왼쪽 값으로 하위 컴포넌트에서 정의한 props 속성 이름을 넣고, 오른쪽 값으로 하위 컴포넌트에 전달할 상위 컴포넌트의 data 속성을 지정한다.
    • props 속성을 사용하여 데이터를 전달하는 아래의 예제를 살펴보자.
    <html>
        <head>
            <title>Vue Props Sample</title>
            <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        </head>
        <body>
            <div id="app">
                <!-- 팁 : 오른쪽에서 왼쪽으로 속성을 읽으면 더 수월하다. -->
                <child-component v-bind:propsdata="message"></child-component> <!-- 4 -->
            </div>
            
            <script>
                Vue.component('child-component', { // 2
                    props: ['propsdata'], // 3
                    template: '<p>{{ propsdata }}</p>' //5
                });
                new Vue({ // 1
                    el: '#app',
                    data: {
                        message: 'Hello Vue! passed from Parent Component'
                    }
                });
            </script>
        </body>
    </html>
    • 위 코드는 상위 컴포넌트의 message 속성을 하위 컴포넌트에 props로 전달하여 메시지를 출력하는 예제이다.
    • props 속성을 이해하기 위해 코드를 작성한 순서대로 살펴보자.
      1. new Vue( )로 인스턴스를 하나 생성한다.
      2. Vue.component( )를 이용하여 하위 컴포넌트인 child-component를 등록한다.
      3. child-component의 내용에 props 속성으로 propsdata를 정의한다.
      4. HTML에 컴포넌트 태그를 추가한다. <child-component> 태그의 v-bind 속성을 보면, v-bind:propsdata="message"는 상위 컴포넌트의 message 속성 값인 Hello Vue! passed from Parent Component 텍스트를 하위 컴포넌트의 propsdata로 전달하였다.
      5. child-component의 template 속성에 정의된 <p>{{ propsdata }}</p>는 Hello Vue! passed from Parent Component가 된다.
    • 위 내용을 더 간단히 정리하면 뷰 인스턴스의 data 속성에 정의된 message 속성을 하위 컴포넌트 props로 전달하여 화면에 나타낸다.
    • 위 코드의 실행 결과는 아래와 같다.

     

    props 속성 전달 코드 결과 화면

    • 예제 코드에서는 child-component를 전역으로 등록한 것 이외에 딱히 지정한 것이 없지만 뷰 인스턴스 안에 마치 상위컴포넌트가 존재하는 것처럼 하위 컴포넌트로 props를 내려보냈다.
    • 그 이유는 컴포넌트를 등록함과 동시에 뷰 인스턴스 자체가 상위 컴포넌트가 되기 때문이다.
    • 아래의 그림을 살펴보자.

     

    뷰 인스턴스에 child-component를 등록한 모습

    • 인스턴스에 새로운 컴포넌트를 등록하면 기존에 있는 컴포넌트는 상위 컴포넌트(부모)가 되고, 새로 등록된 컴포넌트는 하위(자식) 컴포넌트가 된다.
    • 그리고 이렇게 새 컴포넌트를 등록한 인스턴스를 최상위 컴포넌트(Root Component)라고 부른다.

     

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

    이벤트 발생과 수신

    • 하위 컴포넌트에서 상위 컴포넌트로의 통신 이벤트를 발생시켜(event emit) 상위 컴포넌트에 신호를 보낸다.

     

    이벤트 발생과 수신 형식

    • 이벤트 발생과 수신 $emit( )과 v-on: 속성을 사용하여 구현한다. 각각의 형식은 아래와 같다.
    // 이벤트 발생
    this.$emit('이벤트명');
    <!-- 이벤트 수신 -->
    <child-component v-on:이벤트명="상위 컴포넌트의 메서드명"></child-component>
    • $emit( )을 호출하면 괄호 안에 정의된 이벤트가 발생한다. 그리고 일반적으로 $emit( )을 호출하는 위치는 하위 컴포넌트의 특정 메서드 내부이다. 따라서 $emit( )을 호출할 때 사용하는 this는 하위 컴포넌트를 가리킨다.
    • 호출한 이벤트는 하위 컴포넌트를 등록하는 태그(상위 컴포넌트의 template 속성에 위치)에서 v-on:으로 받는다. 하위 컴포넌트에서 발생한 이벤트 명을 v-on: 속성에 지정하고, 속성의 값에 이벤트가 발생했을 때 호출될 상위 컴포넌트의 메서드를 지정한다.
    • 아래의 예제를 살펴보자.
    <!DOCTYPE html>
    <html>
        <head>
            <title>Vue Event Emit Sample</title>
            <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        </head>
        <body>
            <div id="app">
                <child-component v-on:show-log="printText"></child-component> <!-- 3 -->
            </div>
            <script>
                Vue.component('child-component', {
                    template: '<button v-on:click="showLog">show</button>', // 1
                    methods: { // 2
                        showLog: function() {
                            this.$emit('show-log');
                        }
                    }
                });
    
                var app = new Vue({
                    el: '#app',
                    data: {
                        message: 'Hello Vue! passed form Parent Component'
                    },
                    methods: {
                        printText: function() { // 4
                            console.log("received an event");
                        }
                    }
                });
            </script>
        </body>
    </html>
    • 위 코드는 child-component의 [show] 버튼을 클릭하여 이벤트를 발생시키고, 발생한 이벤트로 상위 컴포넌트(여기서 루트 컴포넌트)의 printText( ) 메서드를 실행시키는 예제이다.
    • [show] 버튼을 클릭했을 때 처리되는 과정은 다음과 같다.
      1. [show] 버튼을 클릭하면 클릭 이벤트 v-on:click="showLog"에 따라 showLog( ) 메서드가 실행된다.
      2. showLog( ) 메서드 안에 this.$emit('show-log')가 실행되면서 show-log 이벤트가 발생한다.
      3. show-log 이벤트는 <child-component>에 정의한 v-on:show-log에 전달되고, v-on:show-log의 대상 메서드인 최상위 컴포넌트의 메서드 printText( )가 실행된다.
      4. printText( )는 received an event라는 로그를 출력하는 메서드이므로 마지막으로 콘솔에 로그가 출력된다.
    • [show] 버튼을 클릭한 실행 결과 화면은 아래와 같다.

    이벤트 발생 예제 실행 결과 화면

     

    • 이와 같은 방식으로 하위 컴포넌트에서 상위 컴포넌트로 신호를 올려보내면 상위 컴포넌트의 메서드를 실행할 수 있다.

     

    <같은 레벨의 컴포넌트 간 통신>

    • 상위 - 하위 관계가 아니라 같은 레벨에 있는 컴포넌트끼리 어떻게 통신하는지 알아보자.

     

    같은 레벨 간의 컴포넌트 통신 흐름

    • 위 그림은 같은 상위 컴포넌트를 가지는 2개의 하위 컴포넌트를 나타낸다.
    • 뷰는 상위에서 하위로만 데이터를 전달해야 하는 기본적인 통신 규칙을 따르기 때문에 바로 옆 컴포넌트에 값을 전달하려면 하위에서 공통 상위 컴포넌트로 이벤트를 전달한 후 공통 상위 컴포넌트에서 2개의 하위 컴포넌트에 props를 내려보내야 한다.
    • 이러한 이유는 컴포넌트 고유의 유효 범위 때문이다.
    • 하지만 이런 통신 구조를 유지하다 보면 상위 컴포넌트가 필요 없음에도 불구하고 같은 레벨 간에 통신을 하기 위해 강제로 상위 컴포넌트를 두어야 한다.
    • 그래서 이를 해결할 수 있는 방법이 바로 이벤트 버스이다.

     

    반응형

     

    <관계 없는 컴포넌트 간 통신 - 이벤트 버스>

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

     

    컴포넌트 관계도에서 이벤트 버스의 모습

    • 위 그림에서 만약 하위 컴포넌트 B에서 상위 컴포넌트 A로 데이터를 전달하려면 왼쪽 그림처럼 여러 컴포넌트를 거쳐 가야한다. 하지만 웹 앱이 커져 컴포넌트가 많아지면 이런 방식은 매우 번거롭다.
    • 이때 이벤트 버스를 활용하면 오른쪽 그림처럼 중간 컴포넌트를 거치지 않고 바로 데이터를 전달할 수 있다.

     

    이벤트 버스 형식

    • 이벤트 버스 형식은 아래와 같다.
    // 이벤트 버스를 위한 추가 인스턴스 1개 생성
    var eventBus = new Vue();
    • 이벤트 버스를 구현하려면 애플리케이션 로직을 담는 인스턴스와는 별개로 새로운 인스턴스를 1개 더 생성하고, 새인스턴스를 이용하여 이벤트를 보내고 받는다.
    • 보내는 컴포넌트에서는 .$emit( )을, 받는 컴포넌트에서는 .$on( )을 구현한다.
    // 이벤트를 보내는 컴포넌트
    methods: {
    	메서드명: function() {
        	eventBus.$emit('이벤트명', 데이터);
    	}
    }
    // 이벤트를 받는 컴포넌트
    methods: {
    	created: function() {
        	eventBus.$on('이벤트명', function(데이터) {
            	...
            });
        }
    }
    • 이 형식을 사용하여 이벤트 버스를 구현해보자.
    <!DOCTYPE html>
    <html>
        <head>
            <title>Vue Event Bus1 Sample</title>
            <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
        </head>
        <body>
            <div id="app">
                <child-component></child-component>
            </div>
            <script>
                var eventBus = new Vue(); // 1
                Vue.component('child-component', { // 2
                    template: '<div>하위 컴포넌트 영역입니다.<button v-on:click="showLog">show</button></div>',
                    methods: {
                        showLog: function() {
                            eventBus.$emit('truggerEventBus', 100);
                        }
                    }
                });
                var app = new Vue({
                    el: '#app',
                    created: function() {
                        eventBus.$on('triggerEventBus', function(value) { // 3
                            console.log("이벤트를 전달받음. 전달받은 값 : ", value);
                        });
                    }
                });
            </script>
        </body>
    </html>
    • 위 코드는 등록한 하위 컴포넌트의 [show] 버튼을 클릭했을 때 이벤트 버스를 이용하여 상위 컴포넌트로 데이터를 전달하는 코드이다.
      1. 먼저 이벤트 버스로 활용할 새 인스턴스를 1개 생성하고 eventBus라는 변수에 참조한다. 이제 eventBus 변수로 새 인스턴스의 속성과 메서드에 접근할 수 있다.
      2. 하위 컴포넌트에는 template 속성과 methods 속성을 정의한다. template 속성에는 '하위 컴포넌트 영역입니다.'라는 텍스트와 [show] 버튼을 추가한다. methods 속성에는 showLog( ) 메서드를 정의하고, 메서드 안에는 eventBus.$emit( )을 선언하여 triggerEventBus라는 이벤트를 발생하는 로직을 추가한다. 이 이벤트는 발생할 때 수신하는 쪽에 인자 값으로 100이라는 숫자를 함께 전달한다.
      3. 상위 컴포넌트의 created 라이프 사이클 훅에 eventBus.$on( )으로 이벤트를 받는 로직을 선언한다. 발생한 이벤트 triggerEventBus를 수신할 때 앞에서 전달된 인자 값 100이 콘솔에 출력된다.
    • 간단히 정리하면 [show] 버튼을 클릭하여 showLog( )가 실행되었을 때 eventBus의 이벤트가 발생한다. 그리고 발생한 이벤트는 상위 컴포넌트의 created( )에 있는 eventBus.$on( )에서 전달받는다. 이벤트와 함께 전달된 인자 값 100이 콘솔 로그에 함께 출력된다.
    • [show] 버튼을 클릭하면 아래와 같은 결과 화면이 나온다.

    이벤트 버스 예제 실행 결과 화면

    • 이벤트 버스를 활용하면 props 속성을 이용하지 않고도 원하는 컴포넌트 간에 직접적으로 데이터를 전달할 수 있어 편리하지만 컴포넌트가 많아지면 어디서 어디로 보냈는지 관리가 되지 않는 문제가 발생한다.
    •  문제를 해결하려면 뷰엑스(Vuex)라는 상태 관리 도구가 필요하다.
      • 뷰엑스는 중, 대형 애플리케이션에서 컴포넌트 간의 데이터 관리를 효율적으로 하는 라이브러리이다. 구글에서 'vuex 시작하기', 'vuex 튜토리얼'로 검색해보아라.

     

     

     


    해당 글은 [Do it! Vue.js 입문] 책을 토대로 공부한 내용을 기록하기 위하여 작성됨.

     


     

     

     

    반응형
Designed by Tistory.