02. Vue 가이드 문서 따라가기
- Vue 인스턴스
<div id='app'> <div>{{msgA}}</div> <div>{{msgB}}</div> <button v-on:click='changeMessage'>Change!</button> </div>
생성될때 정의되지 않으면 데이터는 반응성을 보이지 않는다.<script> //msgB는 반응성을 가지지 않음 const vm = new Vue({ el: '#app', data : { msgA : 'Message A.' }, methods : { changeMessage(){ this.msgA = 'Changed message A!' this.msgB = 'Changed message B!' } }, } }) </script>
{{msgB}}는 동작을 하지만 vm.msgB값을 넣어도 반응하지 않는다. - 라이프 사이클
<div id="app"> <div ref='msg'>{{msg}}</div> <div ref='div'></div> </div>
<script> const vm = new Vue({ el : '#app', data : { msg : 'Hello Vue!' }, //msg를 못찾음(Vue인스턴스 생성전) beforeCreate : function() { console.log('beforeCreate!', this.msg); }, //msg 찾음 created () { console.log('created!', this.msg); }, //html이랑(Vue인스턴스랑 html요소와 연결전) beforeMount () { console.log('beforeMount!', this.$refs.div); }, mounted () { console.log('mounted!', this.$refs.div); }, //데이터가 변경되고 나서 화면에 반응성에 의해 렌더링 되기 직전에 작동 beforeUpdate () { console.log('beforeUpdate!', this.$refs.msg.innerText); }, updated () { console.log('updated!', this.$refs.msg.innerText); }, //인스턴스가 파괴되기 직전에 beforeDestroy () { console.log('beforeDestroy!'); }, destroyed () { console.log('destroyed!'); } }) </script>
템플릿 문법
{{}} : 보간법-내부에서는 자바스크립트 표현식 사용 가능 로직은 불가 ex) new Date()가능
v-once : 한번만 정의 하고 반응성 신경안씀
v-html : html으로 출력함 <br/>이 사용이 되어서 엔터 기능이 됨
v-bind : html속성 컨트롤 v-bind:class='className' => :class='className' 축약해서 사용 가능
v-on : DOM이벤트를 컨트롤 v-on:click='changeClassName' => @click='changeClassName' 축약해서 사용 가능
v-computed : 데이터 값을 미리 가공해서 씀 데이터를 기반으로 작업하는 것이므로 만약 기존 데이터의 추가나 삭제가 일어나면 가공작업이 다시 일어남 해당작업이 캐싱(저장)되서 같은걸 몇번을 재사용해도 작업은 처음 한번만 일어남
Getter와 Setter가 필요한 순간에는 만들어서 사용<div id="app"> <div>{{msg}}</div> </div>
<script> const vm = new Vue({ el : '#app', data : { msg : 'Hello Vue!' }, computed : { reversedMsg: { //Getter get : function() { return this.map.splice('').reverse().join('') }, //Setter set (value) { this.msg = value } } } }) </script>
watch : 특정데이터 변경 감지함, computed도 감시 할수 있음<div id="app"> <div>{{msg}}</div> <div>{{reversedMsg}}</div> </div> <script> const vm = new Vue({ el : '#app', data : { msg : 'Hello Vue!' }, computed : { reversedMsg : { get () { return this.msg.split('').reverse().join('') }, set (value) { this.msg = value } } }, watch : { msg (newMsg) { console.log('New data : ' + newMsg) }, reversedMsg (newMsg) { console.log('New reversedMsg : ' + newMsg) } } }) </script>
클래스와 스타일 바인딩
v-bind : 클래스와 스타일을 동적으로 토글하기 위해 사용
클래스<div v-bind:class="classObject"></div> data: { classObject: { active: true, 'text-danger': false } }
꼭 인라인일 필요는 없음<div v-bind:class="classObject"></div> data: { isActive: true, error: null }, computed: { classObject: function () { return { active: this.isActive && !this.error, 'text-danger': this.error && this.error.type === 'fatal' } } }
computed도 사용 가능함<div v-bind:class="[activeClass, errorClass]"></div> data: { activeClass: 'active', errorClass: 'text-danger' }
배열도 넣을 수 있음<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
배열을 쓸 경우 삼항 연산자 사용가능Vue.component('my-component', { template: '<p class="foo bar">Hi</p>' }) <my-component class="baz boo"></my-component> <p class="foo bar baz boo">Hi</p>
기본 컴포넌트에 추가 된다.<my-component v-bind:class="{ active: isActive }"></my-component> <p class="foo bar active">Hi</p>
기본 클래스 바인딩도 추가 된다.
스타일 바인딩<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <div v-bind:style="{ color: activeColor, 'font-size': fontSize + 'px' }"></div> data: { activeColor: 'red', fontSize: 30 }
fontSize 객체니까 카멜케이스로 쓰면 알아서 케밥케이스로 변환 케밥 케이스 쓰고 싶으면 ' '로 감싸주면 된다.<div v-bind:style="styleObject"></div> data: { styleObject: { color: 'red', fontSize: '13px' } }
객체로 만들 수 있음<div v-bind:style="[baseStyles, overridingStyles]"></div>
스타일이 만약 겹치면 뒤의 스타일로 적용이 됨<div v-bind:style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
이런식으로 배열을 사용할 수 있음 그리고 제일 마지막 값으로 적용이 됨(가능한 것 중에서)조건부 렌더링
v-if v-else-if v-else로 if문 사용하면 된다<style> .box{ width : 100px; height : 100px; } .box--red{ background-color: red; } .box--blue{ background-color: blue; } .box--gray{ background-color : gray; } </style> <div id="app"> <div v-if='colorState === "red"' class="box box--red"></div> <div v-else-if='colorState === "blue"' class="box box--blue"></div> <div v-else class='box box--gray'></div> </div> <script> const vm = new Vue({ el : '#app', data : { colorState : 'blue' } }) </script>
v-show는 v-if와 비슷 하다 true에 나오고 false에 안나오고 근데 show는 css로 숨김처리이고 if는 아예 요소 생성을 하지 않는다 엄청 자주 토글되면 show를 쓰고 그외는 if를 쓰라고 공식문서가 말함리스트 렌더링
배열
배열값을 변경할 때는
this.todos[3] = {title: '야식 먹기'}
처럼 할당 연산자를 사용할 경우 반응성이 동작을 안함
변이 메소드인 push(), pop(), shift(), unshift(), splice(), sort(), reverse()을 사용해야 래핑하여 뷰 갱신을 트리거 한다.
v-for을 사용시 항상 key값이 있어야 하는데
<li v-for='(todo, index) in todos' :key='index'>
인덱스 사용가능함
배열의 setter
Vue.set(this.todos, 3, {title: '야식 먹기!'}) this.$set(this.todos, 3, {title: '야식 먹기!'})
둘다 사용 가능
객체
객체의 인덱스 활용은
<li v-for='(value, key, index) in heropy' :key='index'> {{value}} </li>
이렇게 사용하고 객체를 미리 만들어 놓지 않으면 객체값을 바꿔도 반응성이 동작을 안함 그래서 미리 만들던가 아니면
vm.heropy = Object.assign({}, vm.heropy, {email: '@naver.com', a:'ttrfgr', b:'asdawdawd'})
이 방법을 하면 객체가 새로 생성되면서 반응성 사용이 가능함
객체의 setter
Vue.set(this.heropy, 'homepage', 'heropy.blog') this.$set(this.heropy, 'homepage', 'heropy.blog')
<div id="app"> <button @click='pushTodo'>Push</button> <ul> <li v-for='(todo, index) in todos' :key='index'> {{todo.title}} </li> </ul> <button @click='addHomepage'>addHomepage</button> <ul class='heropy'> <li v-for='(value, key, index) in heropy' :key='index'> {{value}} </li> </ul> </div> <script> const vm = new Vue({ el: '#app', data: { todos: [ {title: '아침 먹기'}, {title: '점심 먹기'}, {title: '저녁 먹기'} ], heropy:{ name: 'HEROPY', age: 35, //지금 사용하지 않지만 나중에라도 쓴다면 미리 만들어야 반응성이 동작함 안말들면 값이
//들어가도 화면에는 나오지 않음 //homepage: '', //email: '' //콘솔에
//vm.heropy = Object.assign({}, vm.heropy,
//{email: '@naver.com', a:'ttrfgr', b:'asdawdawd'}) 넣으면 반응성이 동작함 } }, methods: { pushTodo() { //이렇게 할당 연산자로 데이터 추가하면 반응성이 제대로 동작 안함 //this.todos[3] = {title: '야식 먹기'} //this.todos.push({title: '야식 먹기'}) //setter //Vue.set(this.todos, 3, {title: '야식 먹기!'}) this.$set(this.todos, 3, {title: '야식 먹기!'}) }, addHomepage() { //Vue.set(this.heropy, 'homepage', 'heropy.blog') this.$set(this.heropy, 'homepage', 'heropy.blog') } } }) </script>
이벤트 핸들링
click이벤트를 이용해서 data와 이벤트를 활용하기@click='clickMethod(todo.title, $event)'>
clickMethod(title, event){ console.log(title); console.log(event.currentTarget.className); }
todo는 todos배열에서 꺼내온거고 $event로 이벤트 동작 확인 가능(class명 나옴)@click='clickMethod(todo.title, $event); clickMethod2(todo.title, $event)'
메소드 두개를 사용할때는 ;로 구분해야 하고 한개만 쓸경우는 clickMethod로 ()필요가 없지만, 두개를 사용할 경우에는 무조건 두개다 써야한다.<div id="app"> <ul> <li v-for='(todo, index) in todos' :key='index' :class='"item-"+(index +1)' @click='clickMethod(todo.title, $event); clickMethod2(todo.title, $event)'> {{todo.title}} </li> </ul> </div> <script> const vm = new Vue({ el: '#app', data: { todos: [ {title: '아침 먹기'}, {title: '점심 먹기'}, {title: '저녁 먹기'} ] }, methods: { clickMethod(title, event){ console.log(title); console.log(event.currentTarget.className); }, clickMethod2(title, event){ console.log(title+'두번째'); //console.log(event.currentTarget.className); } } }) </script>
이벤트 수식어
.stop: 이벤트 버블링을 막겠다는 뜻 겹칠때 처음꺼만 반응함
.capture: 캡쳐링이라는데.....
.self: 이벤트가 겹치지 않은 자기 만의 영역만 반응함<div id="app"> <div class="parent" @click.capture='clickHandler'> <div class="child" @click.stop='clickHandler'></div> </div> </div> <script> const vm = new Vue({ el: '#app', methods:{ clickHandler(event){ console.log(event.currentTarget.className); } } }) </script>
<div class="parent" @click.capture='clickHandler'> <div class="child" @click='clickHandler'></div> </div>
capture전에는 child parent였는데, parent child로 바뀜 자기가 젤 우선이 되는거 같음<div class="parent" @click.self='clickHandler'> <div class="child"></div> </div>
겹치지 않는 부분만 반응함
뷰 홈페이지에 나와있는 안내
<!-- 클릭 이벤트 전파가 중단됩니다 --> <a v-on:click.stop="doThis"></a> <!-- 제출 이벤트가 페이지를 다시 로드 하지 않습니다 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 수식어는 체이닝 가능합니다 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 단순히 수식어만 사용할 수 있습니다 --> <form v-on:submit.prevent></form> <!-- 이벤트 리스너를 추가할 때 캡처모드를 사용합니다 --> <!-- 즉, 내부 엘리먼트를 대상으로 하는 이벤트가 해당 엘리먼트에서 처리되기 전에 여기서 처리합니다. --> <div v-on:click.capture="doThis">...</div> <!-- event.target이 엘리먼트 자체인 경우에만 트리거를 처리합니다 --> <!-- 자식 엘리먼트에서는 안됩니다 --> <div v-on:click.self="doThat">...</div>
키수식어
엔터는 키번호가 13이라서 결국 같은 거고 컨트롤 + 엔터를 눌러야 작동함<div id="app"> <input type="text" @keydown.enter='keydownHandler'> <!--같음--> <input type="text" @keydown.13='keydownHandler'> <input type="text" @keydown.ctrl.enter='keydownHandler2'> </div> <script> const vm = new Vue({ el: '#app', methods:{ keydownHandler (e) { // if(e.keycode ===13){ // console.log('Done!'); // } console.log('Done!'); }, keydownHandler2 (e) { console.log('Done2'); } } }) </script>
폼입력 바인딩
v-model: 양방향 바인딩 v-bind는 데이터값만 가져오는거고 수정할경우 Vue의 데이터 값이 바뀌지 않음 바꾸려면 v-model을 써야한다. v-model을 실시간으로 보여 줄때 한글은 글자가 완성되어야 인식이 되는 문제가 있음<input type="text" v-model:value='message'> <div>{{message}}</div>
한글 보완하는건<input type="text" @input='message=$event.target.value'> <div>{{message}}</div>
or<div id="app"> <input type="text" @input='bindMessage' :value='message'> <div>{{message}}</div> </div> <script> const vm = new Vue({ el: '#app', data: { message: 'Hello~!' }, methods:{ bindMessage(event) { this.message = event.target.value } } }) </script>
:value는 없으면 처음의 Hello~!값이 나오지 않기에 단방향 바인딩으로 처음에 로딩 시켜주고 그이후는 @input으로 데이터값 연결하면 된다.
그런데 실시간으로 연결이 말고 엔터나 포커싱 아웃이 될때만 입력되길 원할때는<input type="text" @change='message = $event.target.value' :value='message'> <div>{{message}}</div>
or<input type="text" v-model.lazy='message'>
방법으로 사용할 수 있음
앞뒤의 트림 기능도 사용가능 중간의 스페이스는 유지됨(근데 여러번 해도 한번으로만 인식이 됨)<input type="text" v-model.trim='message'>
숫자를 입력받을 때는<input type="text" v-model.number='message'> <div>{{typeof message}}</div>
.number로 string이 아닌 number형식으로 받음컴포넌트 나누는 기준은 1. 재사용하느냐? 2. 복잡하거나 개념적으로 분리해야하느냐? 카멜케이스 최신 문법에서는 가능하지만 기본적으로 케밥케이스로 네이밍 작성 전역 컴포넌트
<div id="app"> <my-component> </div> <script> Vue.component('my-component', { template: '<div class="me">{{message}}</div>', data: function(){ return { message: 'Hello Vue!' } } }) const vm = new Vue({ el: '#app' }) </script>
지역 컴포넌트<div id="app1"> <my-component> </div> <div id="app2"> <my-comp> </div> <script> const myComp = { template: '<div class="me">{{message}}</div>', data: function(){ return { message: 'Hello Vue!' } } } const vm1 = new Vue({ el: '#app1', components: { 'my-component': myComp } }) const vm2 = new Vue({ el: '#app2', components : { //'my-comp': myComp myComp } }) </script>
'myComp' 속성명과 들어올 myComp 속성명이 같으면 myComp처럼 단순하게 쓸 수 있음 그리고 myComp를 my-comp로 케밥케이스로 변경이 자동으로 일어난다고 함. data는 반드시 함수여야한다 : 오브젝트 형태(객체, 배열)의 데이터는 같은 곳을 참조하기 때문에 서로 영향을 미칠 수가 있다. 자바스크립트 사용은 카멜케이스 HTML사용은 케밥케이스<div id="app"> <my-comp :my-msg='message'></my-comp> </div> <script> Vue.component('my-comp',{ template: '<div>{{myMsg}}</div>', props: { myMsg: { //type: String, type: [String, Number], default: 'Default!!!', required: true, validator: function(value){ return value ==='Hello' } } } }) const vm = new Vue({ el: '#app', data () { return { message: 'Hello' } } }) </script>
props를 v-bind:my-mSg(:my-msg)로 연결 시켜서 사용가능(부모가 자식에서 값을 전달) type으로 속성 정의하고 default로 데이터가 없을때 기본값 제공 required는 데이터가 없으면 콘솔에서 오류 validator는 틀리면 콘솔에 오류가 나옴 자식에서 부모의 값을 전달할때는 emit을 활용해야 한다.<div id="app"> <my-comp :my-msg='message' @my-event='updateMessage'></my-comp> </div> <script> Vue.component('my-comp',{ template: '<div @click="updateMsg">{{myMsg}}</div>', props: { myMsg: String }, methods: { updateMsg() { //직접적으로 바꾸면 에러남 //this.myMsg = 'Good' this.$emit('my-event', 'Good') } } }) const vm = new Vue({ el: '#app', data () { return { message: 'Hello' } }, methods: { updateMessage(value) { this.message = value } } }) </script>
emit의 my-event를 실행하고 html의 @(v-on:)을 통하여 vm객체의 updateMessage가 실행이 되고 emit의 함께 파라미터로 보내지는 'Good'가 updateMessage의 value변수로 받아서 실행이 된다. 콤포넌트 내부의 글이나 데이터 태그를 쓸때 slot를 쓰면 된다.<div id="app"> <my-comp> <input type="text"> hello~~ </my-comp> </div> <script> Vue.component('my-comp',{ template:'<div><slot>대체 콘텐츠</slot></div>' }) const vm = new Vue({ el: '#app' }) </script>
그런데 만약 컴포넌트 내부의 값이 없으면<div id="app"> <my-comp> </my-comp> </div> 대체 콘텐츠가 나오게 된다. 슬롯도 이름으로 지정이 가능하다.
<div id="app"> <my-comp> <div slot='slot1'>Hello Slot!</div> <input type="text"> </my-comp> </div> <script> Vue.component('my-comp',{ template:'<div> <slot></slot> <slot name="slot1"></slot></div>' }) const vm = new Vue({ el: '#app' }) </script>
보면 slot이 먼저 나왔으니까 이름이 지정이 안된 인풋태그가 먼저 나오고 다음은 slot1로 지정된 div가 나오게 된다.<div id="app"> <my-comp> <template slot-scope='myProps'> {{ myProps.mySlotData}} </template> </my-comp> </div> <script> Vue.component('my-comp',{ template:'<div><slot my-slot-data="Hello Slot!"></slot></div>' }) const vm = new Vue({ el: '#app' }) </script>
자식이 가진 특정 데이터를 슬롯을 이용하여 부모에서 데이터 사용 가능하다 slot-scope로 슬롯의 데이터 값을 부르고 myProps.mySlotData로 my-slot-data로 지정해둔 데이터를 호출한다.<div id="app"> <my-comp> <template slot-scope='myProps'> {{ myProps.mySlotData}} </template> </my-comp> </div> <script> Vue.component('my-comp',{ template:'<div><slot :my-slot-data="message"></slot></div>', data () { return { message: 'Hello Slot~' } } }) const vm = new Vue({ el: '#app' }) </script>
이런식으로도 사용 가능<template slot-scope='{mySlotData}'> {{ mySlotData }} </template>
이렇게 줄일수도 있음
댓글
댓글 쓰기