전역 수준 컴포넌트의 문제점

  • 빌드 단계가 없음
    • ECMAScript 2015 이상, Typescript와 같은 최신 문법을 사용할 수 없음
  • CSS 빌드 & 모듈화 기능이 없음
  • template이 모두 고유한 id를 가지도록 개발자가 관리해야 함

그래서 생겨난게...

단일 파일 컴포넌트(Single File Component)

단일파일 컴포넌트: <template />, <script />, <style />로 이루어진 확장자가 .vue인 파일

<template>
    <div id="comp-id">
	    ...
    </div>  
</template>

<script>
export default {
    name : 'comp-name',
    components : { ... },
    data() {
	    return {};
    },
    ...
}
</script>

<style>
    ...
</style>

 

Vue-CLI의 vue-loader라는 구성요소가 이 파일을 파싱하고, 다른 로더들을 활용해 하나의 모듈로 조합한다.

그 중 css-loader는 CSS스타일을 전처리하고 스타일 정보를 모듈화한다.

 

기본 프로젝트 구조

Vue-CLI를 통해 프로젝트를 만들게되면, 다음과 같은 폴더 구조가 생성된다.

컴포넌트에서의 스타일

컴포넌트에서 스타일을 작성할 때에는 다음과 같은 작성 방법이 있다.

  • 전역 CSS
  • 범위 CSS(Scoped CSS)
  • CSS 모듈(CSS Module)

전역 CSS

일반적인 CSS작성을 이야기한다.

단일 파일 컴포넌트 내의 <style> 태그 안에 작성하면 된다.

<style>
.class-name {
	color: black;
}
</style>

그러나, 이것은 전역 스타일이므로 페이지 전체에서 사용된다. 

때문에 다른 컴포넌트에서도 동일한 CSS클래스명을 사용하면 충돌이 발생한다.

따라서 특정 컴포넌트만의 스타일을 지정하는 범위 CSS, CSS 모듈 방식을 사용하는 것을 추천한다.

범위 CSS(Scoped CSS)

<style scoped>
.class-name {
	color: black;
}
</style>

범위 CSS를 사용할 때에는 다음 사항을 알아두자.

  • 범위 CSS는 Attribute Selector를 사용함
    • 브라우저에서 스타일을 적용하는 속도가 느림
    • ID, 클래스, 태그명 선택자를 사용해 스타일을 적용하여, 브라우저의 실행 속도가 느려지는 문제를 보완하는 것이 좋음
  • 부모 컴포넌트에서 범위 CSS를 적용하기 위해 생성되는 attribute가 자식 컴포넌트의 루트 요소에도 등록됨
    • 부모 컴포넌트에 적용된 범위 CSS는 하위 컴포넌트에도 반영됨

CSS 모듈(CSS Module)

CSS 스타일을 마치 객체처럼 다룰 수 있게 해준다.

<template>
  <div>
    <button v-bind:class="$style.hand"> CSS Module을 적용한 버튼 </button>
    <div :class="[$style.box, $style.border]">Hello World</div>
  </div>
</template>

<script>
export default {
    created() {
        console.log(this.$style);
    }
}
</script>

<style module>
.hand { cursor:pointer; background-color:purple; color:yellow; }
.box { width:100px; height:100px; background-color:aqua; }
.border { border:solid 1px orange; }
</style>

위 코드는 아래의 HTML로 생성된다.

<div>
	<button class="_1l2s2YbgGPFeFA2amkytu7_0"> CSS Module을 적용한 버튼 </button>
	<div class="_3PFteHfWSgjUHmj2ZptXNB_0 fYMDrU15gNkPkiiXEBCss_0">Hello World</div>
</div>

hand, box, border과 같이 코드에 주어진 스타일명이 아닌, 충돌하지 않도록 생성된 다른 이름이 클래스명으로 사용된다.

 

슬롯

부모 컴포넌트에서 자식 컴포넌트로 전달할 정보가 HTML 태그를 포함하고 있다면, props를 사용해 전달하기 쉽지 않다.

슬롯을 이용하면 부모 컴포넌트에서 자식 컴포넌트로 HTML 마크업을 전달할 수 있다.

 

기본 슬롯(Default Slot)

<자식 컴포넌트>

<template>
	<div>
	...
		<div class="content">
			<!-- 슬롯 정의 -->
			<slot></slot>
		</div>
	... 
	</div>
</template>

<부모 컴포넌트>

<template>
	<div>
    ...
    	<child-component>
        	<div>
            이 안에 들어오는 모든 것들이 slot으로 자식 컴포넌트에게 전달된다.
            그리고 자식 컴포넌트에서 마크업으로 렌더링하게 된다.
            </div>
        </child-component>
    ...
    </div>
</template>

명명된 슬롯(Named Slot)

슬롯에 이름을 부여하여, 컴포넌트에 여러 개의 슬롯을 작성할 수 있다.

 

<자식 컴포넌트>

<template>
    <div>
    	<header>
            <slot name="header"></slot>
        </header>
        <section id="content">
             <slot name="content"></slot>
        </section>
        <footer>
            <slot name="footer"></slot>
        </footer>
    </div>
</template>

<부모 컴포넌트>

<template>
    <div>
    	<child-component>
            <template v-slot:header>
                <!-- header slot 안에 채울 HTML 코드 작성 -->
            </template>
             <template v-slot:content>
                <!-- header slot 안에 채울 HTML 코드 작성 -->
             </template>
            <template v-slot:footer>
                <!-- header slot 안에 채울 HTML 코드 작성 -->
            </template>
        </child-component>
    </div>
</template>

참고로, 기본 슬롯의 이름은 'default'이다.

범위 슬롯(Scoped Slot)

부모 컴포넌트에서 슬롯 내부에 들어갈 컨텐츠를 작성할 때,

자식 컴포넌트로부터 필요한 정보를 전달받아서 (부모 컴포넌트에서) 출력할 내용을 커스터마이징하기 위한 용도로 범위 슬롯이 사용된다.

 

<자식 컴포넌트>

<template>
    <div class="child">
        <slot name="type1" :cx="x" :cy="y"></slot>
        <slot name="type2" :cx="x" :cy="y"></slot>
    </div>
</template>

<부모 컴포넌트>

<template>
    <div class="parent">
        <child>
            <!-- 자식으로부터 전달받은 props를 감싼 객체를 그대로 전달받아 사용 -->
            <template v-slot:type1="p1">
                <div>{{p1.cx }} + {{p1.cy}} = 
                {{ parseInt(p1.cx) + parseInt(p1.cy) }}</div>
            </template>
            <!-- 자식으로부터 전달받을 props를 지정하여 사용 -->
            <template v-slot:type2="{cx, cy}">
                <div>{{cx }} 더하기 {{cy}} 는 
                {{ parseInt(cx) + parseInt(cy) }}입니다.</div>
            </template>
        </child>
    </div>
</template>

 

동적 컴포넌트(Dynamic Component)

화면의 동일한 위치에, 상황에 따라 다른 컴포넌트를 표현할 때 사용한다.

성능상의 이유로 상태를 유지하거나 재-렌더링을 피하길 원할 때, <keep-alive>를 이용한다.

<template>
<div>
  <div class="header">
    <nav>
      <ul>
        <li>
          <a href="#" @click="changeMenu('home')">Home</a>
        </li>
        <li>
          <a href="#" @click="changeMenu('about')">About</a>
        </li>
        <li>
          <a href="#" @click="changeMenu('contact')">Contact</a>
        </li>
      </ul>
    </nav>
  </div>

  <div class="container">
    <!-- keep-alive로 감싸서 캐싱하여 여러번 실행되지 않도록 함 -->
    <keep-alive>
     <!-- 동적으로 컴포넌트가 바인딩됨 -->
      <component v-bind:is="currentView"></component>
    </keep-alive>
  </div>
  
</div>
</template>

<script>
import Home from './components/Home.vue';
import About from './components/About.vue';
import Contact from './components/Contact.vue';

export default {
  components : { Home, About, Contact },
  data() {
    return { currentView : 'home' }
  },
  methods : {
    changeMenu(view) {
      this.currentView = view;
    }
  }
}
</script>

 

재귀 컴포넌트(Recursive Component)

템플릿에서 자기 자신을 호출하는 컴포넌트를 재귀 컴포넌트라고 한다.

재귀적으로 컴포넌트를 사용하려면, 반드시 name 옵션을 지정해야 한다.

<template>
  <ul>
    <li v-for="s in subs" v-bind:class="s.type">
        {{s.name}}
        <tree :subs="s.subs"></tree>
    </li>    
  </ul>
</template>

<script>
export default {
    name : 'tree',
    props : [ 'subs' ]
}
</script>

 

 

Vue 컴포넌트? 

작고

독립적이며

재사용할 수 있는

 

-> 뷰 세계에서 가장 기본적인 구성 요소

 

컴포넌트 시스템

= 컴포넌트의 조합

컴포넌트 여러개가 모여서 컴포넌트 시스템을 이루어 대규모 애플리케이션 구축이 가능하다.

 

각각의 컴포넌트들은 트리구조로 이루어져 있으며, 서로 상호작용을 하여 애플리케이션 서비스를 이룬다.

컴포넌트 작성과 사용

<script type="text/javascript">
Vue.component('hello-component', {
    template : '<div>hello world!!!</div>'
})
</script>

파라미터

  • 태그명: 컴포넌트를 사용할 태그

    • 태그명은 대소문자를 구별하지 않으므로, 가능한 케밥 표기법을 따르는 것이 좋다!

  • 옵션: 기타 정보(template, props 등)

템플릿 작성 방식

1. 인라인 템플릿

위처럼, template 옵션에 템플릿 문자열을 사용하는 방식을 인라인 템플릿(inline template)이라고 한다.

그리 권장되는 방법은 아니다.

 

2. template 태그 사용

<template id="helloTemplate">
    <div>hello world!!!</div>
</template>

<script type="text/javascript">
  Vue.component('hello-component', {
    template : '#helloTemplate'
  })
</script>

 

3. <script type="text/x-template"> 태그 사용

<script type="text/x-template" id="helloTemplate">
    <div>hello world!!!</div>
</script>

<script type="text/javascript">
  Vue.component('hello-component', {
    template : '#helloTemplate'
  })
</script>

 

사용하기

이렇게 작성된 컴포넌트를 사용할 때는 등록한 태그명을 사용한다.

<hello-component></hello-component>

 

전체 코드를 보면 아래와 같다.

  
<!DOCTYPE html>
<html>
<head>...</head>

<!-- 템플릿 등록 -->
<template id="helloTemplate">
    <div>hello world!!!</div>
</template>

<!-- 컴포넌트 작성 -->
<script type="text/javascript">
  Vue.component('hello-component', {
    template : '#helloTemplate'
  })
</script>

<!-- 컴포넌트 사용 -->
<body>
  <div id="app">
    <hello-component></hello-component>
    <hello-component></hello-component>
    <hello-component></hello-component>
  </div>
</body>

<script type="text/javascript">
var v = new Vue({
  el : '#app'
})
</script>

</html>

 

작성 시 주의사항 1 - 자식 요소가 정해져 있는 HTML 요소

HTML 요소들은 자식 요소로 포함할 수 있는 요소들이 정해져있는 경우가 있다.

아래의 select, option 태그가 그러하다.

<select name='fruits'>
  <option value='' selected>-- 선택 --</option>
  <option value='apple'>사과</option>
  <option value='banana'>바나나</option>
  <option value='lemon'>레몬</option>
</select>

브라우저는 구문분석을 수행하다가, 자식 요소로 정해져있는 태그가 아닌, 다른 것이 왔을 때에 오류를 내뿜는다.

아래 코드를 살펴보자.

<script type="text/javascript">
  Vue.component('option-component', {
    template : '<option>hello</option>'
  })
</script>

<body>
    <div id="app">
        <select>
            <option-component></option-component>
            <option-component></option-component>
        </select>
    </div>
</body>

<script type="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
  el : '#app'
})
</script>

 

브라우저는 태그 구문 분석하는 작업을 먼저 수행한 후, Vue 컴포넌트를 렌더링한다.

option-component를 select 태그 안에서 사용할수 있다고 브라우저에 등록되어있지 않으므로, Vue 컴포넌트 렌더링 단계까지 가기 전에 구문분석 오류를 내뱉는다.

 

이 문제를 해결하기 위해서 is 특성(Attribute)을 이용할 수 있다.

<select>
        <option is="option-component"></option>
        <option is="option-component"></option>
</select>

Single File Component로 작성된 경우나 <script type="text/x-template"> 태그 안에서 사용된 경우에는 굳이 is 특성을 사용하지 않아도 된다.

 

작성 시 주의사항 2 - 템플릿 요소 안에는 루트 요소가 하나

<!-- 틀린 예 -->
<template id="helloTemplate">
    <div>hello world!!!</div>
    <div>hello world!!!</div>
</template>

<!-- 옳은 예 -->
<template id="helloTemplate">
	<div>
      <div>hello world!!!</div>
      <div>hello world!!!</div>
    </div>
</template>

 

컴포넌트 옵션 - data 옵션

data 옵션은 컴포넌트 내부의 로컬 상태 정보를 저장하기 위한 용도로, 아래와 같이 작성한다.

<template id='timeTemplate'>
    <div>
        <span>{{nowTS}}</span>
        <button v-on:click="timeClick">현재 시간</button>
    </div>
</template>

<script>
Vue.component('time-component', {
    template : '#timeTemplate',
    data : function() {
        return { nowTS : 0 };
    },
    methods : {
        timeClick : function(e) {
            this.nowTS = (new Date()).getTime();
        }
    }
})
</script>

 

위 코드처럼 정상적으로 렌더링되려면 data 옵션에는 객체를 리턴하는 함수가 주어져야 한다.

이렇게 함수를 지정하는 이유는, 동일한 컴포넌트가 여러 번 사용되더라도 동일한 객체를 가리키는 것이 아니라, 새로운 객체가 리턴되도록 하여서 서로 다른 객체를 참조하도록 하기 위함이다.

 

같은 맥락에서, 아래 코드처럼 data 옵션을 지정하면 같은 객체를 참조하므로, 아래와 같이 작성해서는 안된다.

var data = { nowTS : 0 };
Vue.component('time-component', {
    template : '#timeTemplate',
    data : function() {
        return data;
    }
})

컴포넌트 옵션 - props와 event

 

 

부모 컴포넌트는 Props를 통해 자식 컴포넌트에 정보를 전달하고,

자식 컴포넌트는 event를 발생시켜서 상태를 전달한다.

 

부모에서 자식으로 단방향으로만 props를 이용한 정보 전달이 가능하다.

자식 컴포넌트에서 props를 바꿀 수 없으며, 부모로부터 전달 받는 것을 사용하는 것만 가능하다.

 

 

 

 

 

 

 

 

자식 컴포넌트에서 props 정의

props 명을 배열로 나열하거나, 엄격한 유효성 검증이 필요한 경우에는 아래처럼 객체 형태로 정의할 수 있다.

<template id="listTemplate">
    <li>{{message}}</li>
</template>

<script>
Vue.component('list-component', {
    template : '#listTemplate',
    props : [ 'message' ]    // props 명을 배열로 나열
})
</script>
<template id="listTemplate">
    <li>{{message}}</li>
</template>

<script type="text/javascript">
Vue.component('list-component', {
    template : '#listTemplate',
    props : { 
        message : { 
        	type: String, 
        	default: '안녕하세요' 
        },
        count : { 
        	type: Number, 
        	required: true 
        },
        countries: {
        	type: Array,
        	default: () => ['Korea']
        }
    } // props 명을 객체 형태로 정의
})
</script>

※ 전달받을 값이 배열이나 객체인 props에 default 값을 부여할 때에는 반드시 함수를 사용해야 한다.

부모 -> 자식 컴포넌트에 props 전달

props를 통해 정보를 전달하는 방법은 HTML 요소의 attribute에 값을 부여하는 방식과 비슷하다.

전달하는 방법은 2가지가 있다.

 

  • 값을 바로 부여하기 (String)
  • 변수나 객체를 바인딩하여 부여하기

값을 바로 부여하는 방법은 해당 props의 type이 String인 경우에만 동작한다.

그 외에는 v-bind를 통해 바인딩함으로써 전달한다.

<ul>
  <list-component message="Hello"/> <!-- 값을 그대로 부여 -->
    <list-component v-bind:count="100"/> <!-- Number type의 값을 바인딩하여 전달 -->
  <list-component :count="21"/> <!-- v-bind 축약 형태 -->
  <list-component :message="message"/> <!-- 변수를 바인딩하여 전달 -->
</ul>

 

 props를 표기할 때 주의할 점은, 컴포넌트 작성 시 props를 카멜 표기법으로 사용했다면, 이를 케밥 표기법으로 바꾸어 사용해야 한다.

태그 작성 시 attribute는 대소문자를 구분하지 않기 때문이다.

 

Event를 이용한 정보 전달

자식 컴포넌트에서는 이벤트를 발신(emit)하고, 부모 컴포넌트에서는 v-on 디렉티브를 이용해 이벤트를 수신한다.

 

<자식 컴포넌트>

<template id="childTemplate">
    <div>
    	<!-- HTML 요소의 이벤트 리스너 등록 -->
        <button v-on:click="clickEvent" :data-lang="buttonInfo.value">
	        {{ buttonInfo.text }}
        </button>
    </div>
</template>

<script>
Vue.component('child-component', {
    template : '#childTemplate',
    props : [ 'buttonInfo' ],
    methods : {
        clickEvent : function(e) {
            /**
            * 상위 컴포넌트에 'timeclick' 이벤트 발신
            * 이벤트를 발신할 때 필요한 정보들을 인자로 전달
            */
            this.$emit('timeclick', e.target.innerText, e.target.dataset.lang);
        }
    }
})
</script>

 

<부모 컴포넌트>

<template id="parent-template">
    <div>
        <child-component v-for="s in buttons" :button-info="s" 
			             @timeclick="timeclickEvent"/>
                         <!-- timeclick 이벤트 수신 및 리스너 등록 -->
        <hr />
        <div>{{ msg }}</div>
    </div>
</template>

<script>
Vue.component('parent-component', {
    template : '#parent-template',
    data : function() {
        return { 
	        msg: "",
            buttons : [ 
                { text : "Hello", value : "영어" }, 
                { text : "씬짜오", value : "베트남어" },
                { text : "니하오", value : "중국어" }
            ]
        };
    },
    methods : {
    	/** 
        * timeclick 이벤트 리스너
        * 자식 컴포넌트에서 이벤트를 발신할 때 넘겨준 2개의 인자를 전달받음
        */
        timeclickEvent : function(k, v) {
            this.msg = k + ", " +v;
        }
    }
})
</script>

 

 

 

Event Bus 객체를 이용한 통신

비어있는 Vue 인스턴스를 만들어, global한 이벤트버스로써 데이터 전달 역할을 한다.

var eventBus = new Vue();

 

 

<!-- 이벤트 버스 객체 -->
<script>
var eventBus = new Vue();
</script>

<!-- 첫번째 자식 컴포넌트 -->
<template id="chidl1Template">
    <div>
        <button v-on:click="clickEvent">child1 button!!</button>
        <div>{{currentTime}}</div>
    </div>
</template>

<script>
Vue.component('child1-component', {
    template : '#chidl1Template',
    data : function() {
        return { currentTime : '' };
    },
    methods : {
        clickEvent : function() {
			var d = new Date();
			var t = d.toLocaleTimeString() + " " + d.getMilliseconds() + "ms";

			// 이벤트 버스를 통해 click1 이벤트 발신
			eventBus.$emit('click1', t);
			this.currentTime = t;
        }
    }
});
</script>

<!-- 두번째 자식 컴포넌트 -->
<template id="chidl2Template">
    <ul>
        <li v-for="t in timelist">{{t}}</li>
    </ul>
</template>
<script type="text/javascript">
Vue.component('child2-component', {
    template : '#chidl2Template',
    data : function() {
        return { 
            timelist : []
        };
    },
    created : function() {
        // 이벤트 버스를 통해 click1 이벤트 발신
        eventBus.$on('click1', this.child1Click);
    },
    methods : {
        child1Click : function(time) {
            this.timelist.push(time);
        }
    }
});
</script>

<body>
    <div id="app">
        <child1-component></child1-component>
        <hr />
        <child2-component></child2-component>
    </div>
</body>

 

 

 

 

- End -

 

 

 



뷰 최신 버전인 2.5.17 버전에서 트랜지션을 사용해보아따.


참고로 그 사용 방법은 Vue.js 공식 페이지에 기가막히게 잘 나와있다.




뷰 트랜지션을 써보다가 마주친 문제를 설명하기에 앞서, 그놈을 어떤식으로 사용하는지 알아보즈아~.




뷰(Vue) 트랜지션 사용하기


뷰 트랜지션을 이용하여, 로고 3개를 차례로 뿅뿅뿅 나타나게하는 효과를 적용해보려고 한다.


뷰 템플릿(template) 작성

 
<transition-group name="fade" v-on:after-enter="fadeNext" tag="div" class="text-center">
     <img v-show="fade[0]" key="0" class="logo" src="../assets/logo.png">
     <img v-show="fade[1]" key="1" class="logo" src="../assets/logo.png">
     <img v-show="fade[2]" key="2" class="logo" src="../assets/logo.png">
</transition-group>
  


먼저 template 부분인데, 아주아주 간단하다.


  1. 그냥 "transition-group" 이라는 태그를 사용해주면 되고, 

  2. 효과 이름을 정해서 name="내가 정한 트랜지션 이름" 의 형태로 속성을 넣어주고,

  3. 자식 엘리먼트들에다가 'key'를 부여해주자.

  4. 그리고, 이 효과가 나타나게 해주는 switch 역할을 하는 v-show="내가 정한 switch 데이터 변수명" 을 추가해준다(요놈 스위치 변수는 아래 script 코드 부분에서 추가할거심).

  5. 마지막으로, 우리는 세 개의 로고가 뿅뿅뿅 차례차례 뜨도록 하는게 목적이니까, 첫번째 로고가 다 뜨면 다음 로고가 뜨도록 하는 메소드를 호출해주어야 한다. 
    요거슨 뷰에서 트랜지션 훅(hook)을 제공해주니, 그거슬 이용하면 된다. 
    사용하는 방법은 다양한데, 나는 'v-on' 디렉티브를 이용하는 방법을 선택했다.
    v-on:after-enter="내가 정한 메소드명" 형태로 집어넣어 주면 된다(훅 메소드도 아래 script 코드 부분에서 가할 것이니 인내하자).



<참고>


트랜지션 훅(hook) 메소드는 총 8가지가 있다.


v-on:before-enter
v-on:enter
v-on:after-enter
v-on:enter-cancelled

v-on:before-leave
v-on:leave
v-on:after-leave
v-on:leave-cancelled


각각이 어떤 것인지는 뷰 가이드에 두말하면 입아프게 너무나도 자세히 나와 있으니, 


조오기 위에 링크 눌러서 참고하시길.





아직까지는 아무런 효과를 적용해주지 않았으므로 걍 아래와 같은 정적인 상태가 보일 거심.









뷰 스타일(style) 코드 작성

얘가 뿅뿅 나타나도록 CSS 코드로 Fade-in 효과를 만들어보자.


  
.fade-enter {
  opacity: 0;
}
.fade-enter-active {
  transition: opacity .5s;
};
 


이 코드는, "fade" 라는 이름의 뷰 트랜지션을 정의해준다.


트랜지션 진입 시 처음 상태"fade-enter"로 정해주고,

트랜지션 효과"fade-enter-active"로 정해준다.


위 코드를 통해 "fade"라는 뷰 트랜지션은

=> 처음에는  'opacity: 0' 에서 시작해서 0.5초동안 opacity가 1로 변하는 트랜지션

이라고 이해하면 된다.




뷰 스크립트(script) 코드 작성

이제 중요한 것 2가지가 남았다.


1. 처음에 트랜지션 효과가 일어나도록 해주는 스위치를 만들어주는 것과,


2. 첫번째 엘리먼트에 트랜지션 효과가 끝났으면, 그 다음 엘리먼트에 효과가 일어나도록 하는 훅 메소드를 만들어주는 것.


 
let fadeIdx = 0

export default {
  ...
  data() {
    return {
      fade: [false, false, false]
    }
  },
  methods: {
    fadeNext: function() {
       this.fade.splice(fadeIdx++, 1, true)
    }
  },
  mounted() {
    setTimeout(this.fadeNext, 1000)
  }
}
...
 


fade 배열은 각각의 로고 이미지들의 트랜지션을 시작시켜주는 스위치들의 배열이다.


default 값이 모두 false 이므로, 어디선가 누군가가 값을 true로 바꿔주지 않으면, 트랜지션 효과는 일어나지 않는다.



fadeNext 함수다음 순서의 트랜지션 스위치를 켜는 역할을 한다.


즉, fade 배열에서, 다음 인덱스의 스위치를 켜는 역할을 한다.


인덱스 값의 default가 0이므로, 맨 처음 트랜지션도 해당 함수로 켜면 된다.


(위 코드에서는 mount가 되면, 1초 뒤에 맨 처음 로고의 트랜지션이 시작되도록 해놔씀)






결과물, 그리고...







읭...?


뭔가 이상하지 않음?


원하던 아름다운 그림이 아닐 거시다.


'by WANZARGEN' 이 위에있다가, 로고 트랜지션이 시작되어야 제자리로 내려간다...


by WANZARGEN 요거슬 괜히, 그냥, 한 번 짱인 척 할라고 넣은 것이 결코 아녀. 



그 뿐만이 아님.


얘네들이 제자리에서 뿅뿅뿅 하고 뜨는게 아니라, 하나가 생길때마다 왼쪽으로 밀려난다는...





문제점


이러한 현상이 나타나는 이유는 아주 심플하다.


뷰의 트랜지션은 기본적으로 v-if, v-show 를 사용하여 트랜지션을 시작시키도록 되어있다.



뭔말이냐,


아까 위에서 '트랜지션 스위치' 역할을 하는 변수가 있다고 했잖음?


그 친구가 true면  v-if = true  혹은  v-show = true  가 되고, 해당 엘리먼트가 화면에 보여지기 시작한다는 말이 된다.



이렇게 엘리먼트가 화면에 출력되는 때가 트랜지션이 시작되는 때라고 이해하면 된다.




요까지만 말해도 이해하는 당신, 아래 '참고2'는 제끼십쇼.





<참고2>


    • v-if

      이 친구는 그 대응 값이 false이면 DOM 트리에 아예 렌더링 자체가 안된다.


    • v-show

      이 친구는 그 대응 값이 false이면 렌더링은 되지만, 출력이 안된다.

      그 출력이 안되는 방식이 중요한데, 해당 엘리먼트의 스타일을 "display: none" 처리 해버린다.

      "display: none" 처리를 하게 되면, 해당 엘리먼트가 차지하는 영역 또한 사라지게 된다.

      해당 영역을 살리고 싶으면,  "visibility" 속성을 이용하면 된다.

      "visibility" 속성을 'hidden' 으로 설정하면 엘리먼트가 차지하는 영역은 살리되, 안보이게 할 수 있다.

      또 다른 방법으로는, "opacity" 속성 값을 0으로 두어 투명하게 하는 방법이 있다.

 



자신의 영역이 없다가, 트랜지션 스위치가 켜지는 때에 영역이 생겨버리니...


그에 따른 부작용으로 '안예쁜', '의도한 바 없는' 애니메이션이 되버린 것이다.




비교해서 보자.


원하지 않은 결과물

 이것이 내가 원한 그 것

 

 



 

로고의 영역이 없다가 생기니까 'by WANZARGEN' 문구가 위에 있다가 내려오는 것이고,


로고 이미지를 감싸고 있는 엘리먼트가 '가운데 정렬'인 상황에서,


엘리먼트들이 하나씩 늘어나니, 그에 따라 왼쪽으로 밀리는 것이다.



ㅠ_ㅠ


왜 이런걸 고려하지 않았을까.. 




솔루션


시간이 좀 걸려도, 트랜지션을 하루 이틀 쓸게 아니라서 

나는 뷰의 '커스텀 디렉티브'를 만들어 해결했다.


참고로 vuetifyjs 등 디자인 프레임웍을 이용해 해결하면 더 쉽고 빠르게 원하는 트랜지션을 만들 수 있을 것이지만..

나는 커스텀 디렉티브를 좀 더 심도있게 다뤄볼 겸 + 아주 라이트하고 특정한 상황에도 동작하는게 필요해서 직접 만들어 해결했다.


















  1. vue맨 2018.12.24 11:38

    정말 내용이 간지러운데를 제대로 긁어주시네요 효자손이세요? 다음번에는 믹스인에 대해서좀 알아보고 적어주세요 제발요 부탁입니다.

  2. 허허 2019.06.20 23:12

    열심히 하시네요 ㅎㅎ

+ Recent posts