티스토리 뷰

Vue

Vue.js 컴포넌트 심화

인삼추출물 2022. 1. 6. 11:23

목차

     

    (공부 책 : Vue.js Quick Start )

    앞서 컴포넌트에 대한 내용을 살펴보았습니다.

    하지만 이는 전역 컴포넌트로써 html파일 내부에 작성하는 방법이었습니다.

    간단한 페이지 작성때는 적절하지만 대규모 웹어플리케이션을 구현할 때는 컴포넌트를 관리하기 힘들고 재사용성이 낮습니다.

    그렇기에 Vue CLI 기반으로 단일 파일 컴포넌트를 작성해 개발해야 합니다.

     

    단일 파일 컴포넌트

    전역 컴포넌트의 문제점은 다음과 같습니다.

    · 빌드 단계가 없어 최신 자바스크립트 문법을 사용할 수 없습니다.

    · CSS를 지원하지 않습니다. 그렇기에 CSS 스타일을 빌드하고 모듈화하는 기능을 제공하지 않습니다.

    · 템플릿이 작성될 때 HTML파일 내 여러개의 <template/> 태그가 작성되어 식별하기 어렵습니다. 또한 템플릿마다 고유의 ID를 부여하고 이름을 지정해야 합니다.

     

    Vue CLI를 이용해 생성한 프로젝트는 vue-loader npm 패키지를 이용해 단일 파일 컴포넌트를 지원합니다.

    확장자가 .vue인 파일에 <template/>, <script/>, <style/>를 작성하면 vue-loader는 파일을 파싱하고 다른 로더들을 활용해 하나의 모듈로 조합합니다.

    그럼 이제 앞서 실습했던 EventBus를 예제로 작성하면서 단일 컴포넌트 실습을 해보겠습니다.

    예제를 위한 디렉터리를 생성한 뒤 vue create 명령어로 기본 프리셋 프로젝트 템플릿을 생성합니다.

    이어서 프로젝트 디렉터리(timelistapp)로 이동합니다.

    vue create timelistapp

    cd timelistapp

    !! Vue 3는 생태계 라이브러리가 준비가 안되어 있는 상태라 합니다. 레퍼런스가 충분히 많아지면 그때 공부하면 될거같습니다. 지금은 Vue 2로 프로젝트를 생성해주면 됩니다.

    src 디렉터리 안 샘플 코드인 App.vue를 살펴보면 <template/>, <script/>, <style/> 3개의 영역으로 나누어져 있는걸 확인 가능합니다.

    이는 전역 컴포넌트와 비교하면 몇 가지 차이점이 있습니다.

    <template>에는 ID 특성을 부여하지 않습니다.

    <script>에서는 Vue 컴포넌트의 template을 지정하지 않으며, vue.component()로 이름과 template 속성을 지정하지 않습니다. 반면에 name 속성을 지정할 수 있으며, 반드시 객체를 export해야 합니다.

    <style>은 컴포넌트에서 사용할 스타일을 작성해놓는 영역입니다.

    다른 컴포넌트를 참조할 시 import문을 이용합니다.

    (샘플로 생성되는 App.vue)

    <template>
      <div id="app">
        <img alt="Vue logo" src="./assets/logo.png">
        <HelloWorld msg="Welcome to Your Vue.js App"/>
      </div>
    </template>
    
    <script>
    import HelloWorld from './components/HelloWorld.vue'
    
    export default {
      name: 'App',
      components: {
        HelloWorld
      }
    }
    </script>
    
    <style>
    ....
    </style>

    App.vue 컴포넌트를 화면에 담기 위해 main.js를 사용합니다. App 컴포넌트를 렌더링한 결과물을 #app 요소에 출력합니다.

    셈플로 제작된 컴포넌트 파일로 App.vue는 삭제해줘도 됩니다.

    (EventBus.js)

    import Vue from 'vue';
    var eventBus = new Vue();
    export default eventBus;

    EventBus 객체는 순수 자바스크립트 코드로 작성합니다.

    이벤트 등록하고 발생시키기 위해 앞으로 추가할 컴포넌트에서 EventBus 객체를 import 한 후에 $on, $emit 메서드를 사용합니다.

    (FirstChild.vue)

    <style>
    .timebutton {
        padding: 10px; width: 10%; height:35px; background: #d9d9d9; 
        color: #555; float: left; text-align: center;
        font-size: 13px; cursor: pointer; transition: 0.3s;
    }
    .timebutton:hover { background-color: #bbb; }
    </style>
    <template>
        <div>
            <button class="timebutton" v-on:click="clickEvent">first child button!!</button>
            <div>{{currentTime}}</div>
        </div>
    </template>
    <script type="text/javascript">
    import eventBus from '../EventBus'
    
    export default {
        name: 'first-child',
        data: function() {
            return {
                currentTime: ''
            };
        },
        methods: {
            clickEvent: function() {
                var d = new Date();
                var t = d.toLocaleTimeString() + " " + d.getMilliseconds() + "ms";
                eventBus.$emit('click1', t);
                this.currentTime = t;
            }
        }
    }
    </script>

    버튼과 생성된 시간을 보여줄 FirstChild.vue 파일을 생성합니다.

    (SecondChild.vue)

    <style>
    ul {  margin: 0; padding: 0; }
    ul li { 
        cursor: pointer; position: relative; padding: 8px 8px 8px 40px;
        background: #eee; font-size: 14px;  transition: 0.2s;
        -webkit-user-select: none; -moz-user-select: none;
        -ms-user-select: none; user-select: none;  
    }
    ul li:hover {  background: #ddd;  }
    </style>
    <template>
        <ul>
            <li v-for="time in timelist" v-bind:key="time">{{time}}</li>
        </ul>
    </template>
    <script type="text/javascript">
    import eventBus from '../EventBus'
    
    export default {
        name: 'second-child',
        data: function() {
            return {
                timelist: []
            };
        },
        created: function() {
            eventBus.$on('click1', this.child1Click);
        },
        methods: {
            child1Click: function(time) {
                this.timelist.push(time);
            }
        }
    }
    </script>

    생성된 시간의 List를 보여줄 SecondChild.vue 파일을 생성합니다.

    (TimeList.vue)

    <template>
        <div id="app">
            <first-child></first-child>
            <br />
            <hr />
            <second-child></second-child>
        </div>
    </template>
    <script type="text/javascript">
    import FirstChild from './FirstChild.vue'
    import SecondChild from './SecondChild.vue'
    
    export default {
        name: 'time-list',
        components: {FirstChild, SecondChild}
    }
    </script>

    컴포넌트를 종합적으로 취합하여 보여줄 vue파일을 생성합니다.

    (main.js)

    import Vue from 'vue'
    //import App from './App.vue'
    import TimeList from './components/TimeList.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
        //render: h => h(App),
        render: h => h(TimeList),
    }).$mount('#app')

    main.js에서 기존 App.vue는 지워주고 TimeList.vue를 import하여 마무리합니다.

    npm run serve로 실행하면 전역 컴포넌트로 만들어진 EventBus 예제의 화면과 동일하게 나오는걸 확인할 수 있습니다.

    완성후 프로젝트 구성

     

    컴포넌트에서의 스타일

    1. 범위 CSS(Scoped CSS)

    vue create styletest

    cd styletest

    범위 CSS를 확인하기 위해 간단한 예제를 작성해보도록 합니다.

    styletest 예제 프로젝트를 만들고나면 src/ompnents/Hello World.vue 파일을 삭제합니다.

    Child1.vue, Child2.vue라는 간단한 스타일 기능만 테스트할 간단한 컴포넌트를 만듭니다. 

    (Child1.vue)

    <template>
        <div class="main">{{msg}}</div>
    </template>
    <script>
    export default {
        name: 'ChilD1',
        data(){
            return{
                msg: 'Child1'
            }
        }
    }
    </script>
    <style>
    .main{border:solid 1px black; background-color:yellow;}
    </style>

    (Child2.vue)

    <template>
        <div class="main">{{msg}}</div>
    </template>
    <script>
    export default {
        name: 'ChilD2',
        data(){
            return{
                msg: 'Child2'
            }
        }
    }
    </script>
    <style>
    .main{border:solid 1px black; background-color:aqua;}
    </style>

    두 컴포넌트의 style에는 서로 다른 배경색 속성을 가진 main 클래스가 정의되어 있습니다.

    이 상태로 실행하게되면 둘다 전역 클래스이기에 충돌하게되어 최종적으로 마지막에 정의한 컴포넌트가 실행됩니다.

    이를 확인하기 위해 App.vue를 변경해줍니다.

    (App.vue)

    <template>
      <div id="app">
        <h2>{{msg}}</h2>
        <Child1/>
        <Child2/>
      </div>
    </template>
    
    <script>
    import Child1 from './components/Child1.vue'
    import Child2 from './components/Child2.vue'
    
    export default {
      name: 'App',
      components: {
        Child1, Child2
      },
      data(){
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      }
    }
    </script>

    전역 CSS

    이러한 상황을 막기위해 범위 CSS로 변경을 합니다.

    이번엔 Child1.vue, Child2.vue의 <stlye>을 <style scoped>로 변경하고 실행해봅니다.

    범위 CSS

    결과화면 같이 두 컴포넌트가 다르게 style이 적용되는걸 확인할 수 있습니다.

    여러 상황에서 활용할 수 있는 기능입니다만 범위 CSS를 사용할 때는 몇가지 사항을 기억해야 합니다.

    (1) 범위 CSS는 특성 선택자를 사용하기 때문에 브라우저에서 스타일 적용하는 속도가 느립니다.

    그렇기 때문에 반드시 속도가 빠른 ID, 클래스, 태그명 선택자로 요소를 선택해 스타일을 적용토록 합니다.

    (2) 부모 컴포넌트에 적용된 범위 CSS는 하위 컴포넌트에도 반영됩니다.

    부모 컴포넌트에서 범위 CSS를 적용하기 위해 생성되는 특성이 자식 컴포넌트의 루트 요소에도 등록되기 때문입니다.

     

    2. CSS 모듈

    CSS 모듈은 CSS 스타일을 마치 객체처럼 다룰 수 있게 합니다.

    <style module></style>로 설정하는 방법은 간단합니다.

    예제를 위해 components 디렉터리 아래에 새로운 컴포넌트 ModuleTest.vue를 추가합니다.

    (src/components/ModuleTest.vue)

    <template>
        <div>
            <button :class="$style.hand"> CSS Module을 적용한 버튼 </button>
        </div>
    </template>
    
    <script>
    export default({
        created() {
               console.log(this.$style);
        },
    })
    </script>
    
    <style module>
        .hand { cursor: pointer; background-color: purple; color: yellow;}
    </style>

    생성한 컴포넌트를 사용하기 위해 App.vue에 설정합니다.

    (src/App.vue)

    <template>
      <div id="app">
        <h2>{{msg}}</h2>
        <child1/>
        <child2/>
        <module-test/>
      </div>
    </template>
    
    <script>
    import Child1 from './components/Child1.vue'
    import Child2 from './components/Child2.vue'
    import ModuleTest from './components/ModuleTest.vue'
    
    export default {
      name: 'App',
      components: {
        Child1, Child2, ModuleTest
      },
      data(){
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      }
    }
    </script>

    결과화면

    <style module></style>로 CSS 모듈화한 스타일은 Vue 인스턴스 내에서 $style이라는 계산형 속성으로 통해 이용가능합니다.

    개발자 모드에서 적용된 style hand의 클래스명이 class="ModuleTest_hand_f8NXv" 와 같이 변경되어 있는 걸 확인가능합니다.

    이는 충돌하지 않도록 생성된 다름 이름을 사용하기 때문입니다.

     

    슬롯

    앞서 우리는 부모 컴포넌트와 자식 컴포넌트 사이의 정보 교환 방법으로 props와 event를 사용하는 방법을 알아보았습니다.

    props는 편리한 기능이지만 전달해야하는 정보가 HTML을 포함 문자열이라면 사용하기 쉽지 않습니다.

    슬롯은 부모 컴포넌트에서 자식 컴포넌트로 HTML 마크업을 전달할 수 있는 방법으로 위 어려움을 해결해줍니다.

     

    1. 슬롯의 기본 사용법

    슬롯을 사용하기 위해 자식 컴포넌트에서는 <slot></slot> 태그를 작성하고 부모 컴포넌트에서는 콘텐츠 영역에 자식 컴포넌트의 <slot></slot> 영역에 나타낼 HTML 마크업을 작성하면 됩니다.

     

    기타

    'Vue' 카테고리의 다른 글

    Vue-CLI 도구  (0) 2021.09.28
    Vue.js 컴포넌트 기초 - 2  (0) 2021.06.29
    Vue.js 컴포넌트 기초 - 1  (0) 2021.05.14
    Vue.js 스타일  (0) 2021.03.25
    Vue.js 이벤트 처리  (0) 2021.02.25
    댓글
    공지사항