ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Vue.js 사용하기 -5( 화면 구성하기-컴포넌트 & event bus )
    JavaScript/Vue.js 2019. 2. 11. 16:04
    반응형

    컴포넌트 ?


    Vue.js 는 컴포넌트의 집합체 라고 생각한다. 모든 구성을 컴포넌트 단위로 구성이 가능하고 공용 혹은 내부적으로 컴포넌트를 구현해서 사용이 가능하다. 


     나처럼 누군가의 도움으로 컴포넌트를 제공 받을수 있다면 정말 쉽게 구성이 가능하다. 나중에 기록해두겠지만 기본적인 컴포넌트만으로는 안되는것들이 있어 결국엔 스스로 사용할 커스텀 컴포넌트를 만들수 밖에 없다.  그렇기 때문에 컴포넌트에 대한 개념과 사용법 만드는법을 숙지할 필요가 있는거 같다. 


     기본적으로 미리 만들어놓은 컴포넌트 사용하기위해 npm 을 통해 puetify 라는 vue.js 컴포넌트 모음을 플러그인 형식으로 받았다. 이것은 회사 동료가 만들어서 nexus 서버에 올려놓았기 때문에 npm 타켓 설정을 살포시 바꾸어서 install 하였다. 


     혹시 공개적으로 쓰고 있는게 있나 해서 확인 해보니 vuetify 라는 컴포넌트 프레임워크가 존재했다.  나중에 개인적으로 프로젝트 진행시에 사용해봐도 될듯 하다. ( https://vuetifyjs.com/ko/)



    컴포넌트 간단하게 만들기  



    <template>

    <button type="danger">A</button>


    </template>

    <script>
    export default {
    props: {
    /**
    * @example
    * headers: [ "haeder1", "header2", "header3", ... ]
    */
    headers: {

    }
    },
    name:'TestButton',
    created(){
    },
    data(){
    return {
    }
    }
    }
    </script>

    <style>

    </style>

    컴포넌트는 templete 에 div 를 선언할 필요가 없다. 위에 컴포넌트는 단순하게 버튼만을 표현하는 컴포넌트이다. 스타일에 css 를 사용하면 범용적으로 사용할수있는 버튼을 만들수 있다. 


     컴포넌트는 props 를  선언하여 사용한다.  props 는 컴포넌트간에 데이터를 전달할수 있는 방법 이다.

     기본적으로 하위 컴포넌트에서는 상위 데이터를 직접 참조 할수 없다. 그리고 vue.js 이것을 허용치 않는데 그렇기 때문에 props 옵션을 사용하여 하위 컴포넌트에 데이터를 전달 할수 있다. 


     위에 선언된 props 는 하위 컴포넌트 입장에서 선언해야 하는 부분이고 만일 상위 컴포넌트에서 props 를 사용한다. 한다면 


    <div id="app">
      <child-component v-bind:프롭스 속성 ="상위 컴포넌트의 속성"></child-component>
    </div>

    위와 같이 사용하거나  아래 와 같이 선언하여 사용하면된다.  이벤트나 옵션을 줄경우에는 태그 안에다가 선언해 주면 된다. 


    <test-button type="primary" @onClick="search" name="조회" ></test-button>



     다양한 컴포넌트 만들기 



    아래 컴포넌트는 테이블을 그려주는 컴포넌트이다. jsp 라면 루프문이 동작하면 하나하나 tr/td 로 생성하던것과 유사하다고 생각하면된다. 

     기본적으로 npm으로 인스톨된 컴포넌트 모음에서 basic-button 이란 컴포넌트를 내부적으로 사용하게 되있고 연습삼아 여러가지로 테이블의 컬럼을 생성할때 특정조건에 따라 다른 형식으로 나타나게 구현되있다. ( 버튼이 들어갈수도있고 하이퍼링크가 걸린 문자열이 될수도 있으며 특정컬럼은 CSS가 다르게 지정도 가능하다 ) 



    <template>
    <tbody>
    <tr v-for="(body, index) in bodys" v-bind:key="index">
    <td v-for="(value, key) in body" v-bind:key="key" >
    <div v-if="key === 'ANO' " >
    <b v-on:click="doDetail(value)" class="onHyperLink">{{ value }}</b>
    </div>
    <div v-else-if="key === 'state' " >
    <div v-if="value.substr(0,4) === 'fail' ">
    <b v-on:click="doBounsDetail(value)" class="onHyperLink_danger">{{ '실패' }}</b>
    </div>
    <div v-else-if="value.substr(0,6) === 'finish' ">
    <b v-on:click="doBounsDetail(value)" class="onHyperLink">{{ '완료' }}</b>
    </div>
    <!--<div v-else-if="value.substr(0,9) === 'imperfect' ">-->
    <!--<b v-on:click="doDetail(value)" class="onHyperLink_ready">{{ '일부성공' }}</b>-->
    <!--</div>-->
    <div v-else-if="value.substr(0,5) === 'delay' ">
    <b v-on:click="doBounsDetail(value)" class="onHyperLink_ready">{{ '대기' }}</b>
    </div>
    <div v-else-if="value.substr(0,6) === 'return'">
    <b v-on:click="doBounsDetail(value)" class="onHyperLink_return">{{ '반려' }}</b>
    </div>
    <div v-else >
    <b v-on:click="doBounsDetail(value)" class="onHyperLink_scs_most">{{ '성공1'}}</b>
    </div>
    </div>
    <div v-else-if="key === 'btn_cancle' " >
    <basic-button type="danger" @onClick="doCancle(value)" name="취소"></basic-button>
    </div>
    <div v-else-if="key === 'link' " >
    <basic-button type="primary" @onClick="doLink(value)" name="상세 보기"></basic-button>
    </div>
    <div v-else-if="key === 'btn_onOff' " >
    <basic-button type="danger" @onClick="doChangState(value)" name="수정"></basic-button>
    </div>
    <div v-else-if="key === 'btn_recall' " >
    <basic-button type="danger" @onClick="doRecall(value)" name="회수"></basic-button>
    </div>
    <div v-else-if="key === 'btn_buycancle' " >
    <basic-button type="danger" @onClick="doBuyCancle(value)" name="취소2"></basic-button>
    </div>
    <div v-else-if="key === 'btn_payview' " >
    <basic-button type="primary" @onClick="doBuyCancle(value)" name="상세보기2"></basic-button>
    </div>
    <div v-else v-bind:id=key+index v-bind:value=value >
    {{value}}
    </div>
    </td>
    </tr>


    </tbody>
    </template>

    <script>
    export default {
    props: {
    /**
    * v-bind:bodys="object"
    * @example
    * bodys: [{
    * value1: 1,
    * value2: 2,
    * value3: 3
    * }, {
    * ...
    * }]
    */
    bodys: {
    type: Array,
    default: () => []
    }
    },
    methods : {
    doDetail(value){
    this.$Bus.$emit("account_detail", value );
    },
    doLink(value){
    this.$Bus.$emit("link", value );
    },
    doBuyCancle(value){
    this.$Bus.$emit("buycancle", value );
    },
    doCancle(value){
    this.$Bus.$emit("cancle", value );
    },
    doRecall(value){
    this.$Bus.$emit("recall", value );
    },
    doBounsDetail(value){
    this.$Bus.$emit("bonusDetail", value );
    },
    doChangState(value){
    this.$Bus.$emit("changState", value );
    }
    },
    data: {
    onHidden : ""
    },
    name:'BtnTableBody',
    created() {
    },
    data() {
    return {
    }
    }
    }
    </script>

    <style>
    .onHyperLink {

    }
    .onHyperLink:hover {
    text-decoration: underline;
    color: blue;
    cursor: pointer;

    }
    .onHyperLink_ready {
    background: #ffc107;
    }
    .onHyperLink_ready:hover {
    text-decoration: underline;
    color: blue;
    cursor: pointer;

    }

    .onHyperLink_danger {
    background: #dc3545;
    }
    .onHyperLink_danger:hover {
    text-decoration: underline;
    color: blue;
    cursor: pointer;

    }

    .onHyperLink_scs_most {
    background: cornflowerblue;
    }
    .onHyperLink_danger:hover {
    text-decoration: underline;
    color: blue;
    cursor: pointer;

    }


    .onHyperLink_return {
    background: orangered;
    }
    .onHyperLink_danger:hover {
    text-decoration: underline;
    color: blue;
    cursor: pointer;

    }

    </style>

     앞서 본 v-for , v-if 를 사용하면 이런식으로 조건에 따라 동적으로 컴포넌트들을 구성하거나 CSS 를 다르게 줄수 있다.  v-if 가 많아지면 굉장히 유지보수상 안좋기 때문에 단순하게 쓰는게 좋을거 같고 개인적으로 테이블을 사용하는거보단 grid 를 더 선호하기 떄문에 이런식으로 테이블을 예외처리해가며 구성할거 같진 않다. 


    props 이야기를 하면서 데이터를 주고 받을수 있다고 했는데 컴포넌트간에도 이벤트를 주고 받을수 있다. 가장 기초적인 상황이 버튼을 가지고있는 팝업에서 버튼을 눌렀을때 혹은 위에 경우처럼 테이블안에 버튼이 있을때 그버튼을 누르면 이벤트가 발생을 하지만 정확히는 상위 컴포넌트에서 발생한것이기 때문에 하위 컴포넌트에서는 이벤트가 일어난 건지 아닌것인지 알수 가 없다. 

     이럴때 사용하는게 이벤트 버스이다. 이벤트 버스에 특정 액션이 생기면 이벤트를 특정 값으로 던지고  이벤트 버스를 사용하는 다른 컴포넌트에서는 이벤트버스에서 자신이 받아야할 이벤트가 발생할경우 그 이벤트를 catch 하여 이벤트를 발생 시키는 방식이다. 


      this.$Bus.$emit("recall", value );


    이벤트를 발생시키는 부분이다. recall 이란 키값으로 이벤트 버스로 이벤트를 발생시킨다고 던진다. 


    이렇게 발생된 이벤트는 이벤트를 감지하기 위해 세팅된 vue 에만 영향을 준다. 이벤트를 받는 부분은 아래와같이 하나가 아니라 여러가지를 미리 선언해 놓을수 있다. 개인적으로는 mounted 에다 선언해놓는것을 선호 한다. 딱히 이유는 없다. 모든 컴포넌트가 준비되고 나서 리스닝 준비하는게 좋지 않나라는 그냥 개인적 취향이다. 

    mounted() {
    this.$Bus.$on("buycancle", this.doBuyCancle);
    this.$Bus.$on("recall", this.doRecall);
    this.$Bus.$on("cancle", this.doCancle);
    this.$Bus.$on("account", this.doMoneyProc);
    this.$Bus.$on("reflesh_account", this.onReflesh);
    }

    이벤트를 발생하는 부분에서는 "키값", 전달할 데이터 두가지를 이벤트 버스에 실어주게 되고  이벤트 발생을 감지 하는 입장에선 어떤 키값의 이벤트인지와 그 이벤트를 받을 경우 어느 function 으로 던질지만 연결해주면 된다. 감지되자마자 이벤트버스에서 감지하여 감지된 이벤트의 실린 데이터 값을 선언한 functon 으로 전달하여 function 내에서 변수로 받아 사용이 가능하다 . 



    컴포넌트를 연결 해보자!! 



    기본적인 컴포넌트 등록에 관련된 설명이 잘나와 있다. (https://kr.vuejs.org/v2/guide/components-registration.html) 컴포넌트를 만들떄는 이름부터 주의 해야 한다. 기본적으로 케밥-표기법 과 파스칼 표기법을 모두 지원한다. 둘중에 어느것을 사용해도 상관은 없다. 단 둘중에 하는 꼭 사양 해야한다. 그렇지 않으면 vue 파일을 인식 하지 못하는 경우가 발생한다. 


     앞서 언급했듯이 내가 사용하는 구성에서 컴포넌트를 선언할수 있는 곳은 두곳이다. index.js 와 vue 파일 내에서 이다. 

    Vue.component('component-a', { /* ... */ })
    Vue.component('component-b', { /* ... */ })
    Vue.component('component-c', { /* ... */ })

    new Vue({ el: '#app' })


    <div id="app">
    <component-a></component-a>
    <component-b></component-b>
    <component-c></component-c>
    </div>


    index.js 에서 연결하고 싶다면 위와 같이 선언하면된다. 이렇게 할경우 전역으로 선언되기때문에 app 이라는 이름의 vue 가 생성될경우 하위 컴포넌트들 내부에서 사용이 가능하다. 전역변수가 안써도 빌드시 메모리를 잡고 있듯이 전역으로 선언 했을 경우 안쓰는 부분이 있어도 무조건 js 가 webpack 빌드시 추가 되기 때문에 용량적 문제가 있다. 그렇기 때문에 모든 컴포넌트 내에서 사용한다 싶을때 전역을 사용하는것이 나은거 같다. 



    import SpanTableHeader from "../common/SpanTableHeader"
    import BtnTableBody from "../common/BtnTableBody"

    export default {
    name: "TestSample",
    components : {
    SpanTableHeader , BtnTableBody
    },

     지역 컴포넌트로 선언할경우 index.js 가 아니라 해당 vue 파일안에서 선언 해주면 된다. export default 영역 위에다 선언해주고 내부에 compoents 라는 태그를 선언하고 안에다 네이밍한 값을 선언해주면 그다음부터는 templete 영역에서 내맘대로 선언해서 사용이 가능하다.  


     위 내용을 보면 puetify 컴포넌트에 대한 선언은 없는데 index.js 에서 아래와 같이 선언해놨기 때문에 따로 명시 하지 않았다. 

    정석적인 선언은 공식 커뮤니티에서 하는것 처럼 하면 된다고 알고 있었는데 아래와 같이해도 정상 동작을 하는것 보면 js 특유의 자유로운 방식지원 (?) 이 있는게 아닌가 싶다. 이렇게 해도 되고... 저렇게 해도 되고 ....

    import puetify from 'puetify'


     컴포넌트로 이루어진 구성은 괜찮은 방식 같다.  그리고 컴포넌트를 재사용하면서 개발시간 단축과 확실한 기능 보장이 되는것이 굉장히 매력적으로 느껴졌다.  JSP 만 사용 하던 나로썬 극강의 소스 재사용을 경험하다보니 신세계랄까 ? 단지 내가 아직 vue.js 대한 노하우가 없어서 컴포넌트를 만드는데 시간이 좀 걸리는게 문제지만 이것또한 경험이 쌓이면 해결 될 문제 라고 생각이 든다. 


     필요할때마다  구글링만으로 하다보니 아직은 좀 겉핥기 식으로 하는거 같다.  이번에 정리하면서 좀더 잘 머리속에 정리 해두어야 겠다.


    반응형
Designed by Tistory.