FE/Vue
Vue.js (CDN)
soohykim
2025. 4. 11. 09:06
728x90
반응형
📒 Vue.js
📕 Vue.js 기본
1. Vue.js
1) 개념
- Evan You가 2013년에 발표한 Front-end JavaScript framework
- Angular와 React 장점만 가진 프레임워크
- SPA(Single Page Application) 목적의 프레임워크
- Vue-CLI : Vuejs 프로젝트(코드, 디렉토리 구조)를 생성 해주는 라이브러리
- CRA(Create Project App) : ReactJS 프로젝트(코드, 디렉토리 구조)를 생성 해주는 라이브러리
- Vite : 다양한 JS(TS) 프로젝트를 생성해주는 라이브러리
2) 특징
- JS 컴파일러가 필요함
- Babel 은 javascript 컴파일러 변환하는 이유는 브라우저 호환성을 위해서 ES6 => ES5 로 transform (변환) 해서 배포
- Bundling(번들링) 도구가 필요함
- Webpack
2. MVVM 패턴
- Vue 및 React, Angular 등 프레임워크가 지향하는 패턴
- MVVM 패턴은 Model, View, ViewModel의 약자로 어플리케이션 로직과 UI 분리를 위한 패턴
- ViewModel은 Model과 View를 연결하는데 Model의 데이터가 변경되면 View에 화면을 갱신하게 하고, 사용자가 화면에서 값을 변경하면 다시 그 값을 Model에 업데이트
3. Vue.js 개발 방법
1) CDN 이용
<script src="https://cdn/jsdelivr.net/npm/vue/dist/vue.js"></script>
2) CLI 이용
npm install -g @vue/cli
4. CDN 활용한 개발
1) 기본 구조
- View (Template)
- HTML과 CSS로 이루어진 요소
- Vue.js의 디렉티브 또는 {{}} 같은 템플릿
- 표현식으로는 HTML DOM에 데이터를 렌더링함
- v-xxx 형식의 다양한 디렉티브 사용할 수 있음
- Model
- 데이터 요소
- JSON 형태로 표현
- ViewModel
- Controller 역할을 하며 Vue 객체가 담당함
- 가장 중요한 역할은 View와 Model의 연결
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vuejs 기본구조</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <!-- view --> <div id="app"> {{ message }} </div> <script> // Model let model = { message : '안녕하세요 Vue!' }; // ViewModel - Vue 객체 var app = new Vue({ el: '#app', // 연결할 View 선택 data: { // 사용할 Model 설정 message: model.message } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vuejs data종류</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <!-- data 종류 --> message:{{ message }}<br> age:{{ age }}<br> phones:{{ phones[0] }} {{ phones}}<br> author:{{ author.name }} {{author.age}}<br> isMarried:{{ isMarried }}<br> <hr> <!-- {{}}을 mustache(이중중괄호)라고 부른다.--> 1) message:{{ message }}<br> 2) age(산술연산):{{ age+1 }}<br> 3) 30보다 적냐?(비교연산):{{age<30}}<br> 4) 이름길이:{{ author.name.length}}<br> 5) 결혼여부(3항연산자):{{ isMarried?"기혼":"미혼" }}<br> 6) 문자열_대문자함수:{{message.toUpperCase()}}<br> 7) 문자열_소문자함수:{{message.toLowerCase()}}<br> 7-1) 문자열 대문자와 소문자 출력 함수:{{upper_lower_case()}}<br> 8) 문자열_부분열함수:{{message.slice(0,3)}}<br> 9) 문자열_치환함수:{{message.replace("Vue","vuex")}}<br> 10) 수치_정수변환함수:{{Number.parseInt(height)}}<br> 11) 배열_join함수:{{phones.join(":")}} </div> <script> var app = new Vue({ el: '#app', data: { message: '안녕하세요 Vue!', age: 20, height: 178.535, phones: [100, 200, 300], author: { name: "홍길동", age: 20 }, isMarried: true }, // data methods: { upper_lower_case() { return this.message.toUpperCase() + ' || ' +this.message.toLowerCase() } } }); </script> </body> </html>
- 📋 실행 📋
2) Vue 객체
- Vue 객체의 생성자는 옵션 객체를 통해서 필요한 속성들을 설정
- 속성 종류
- el : Vue로 만든 화면이 그려지는 인스턴스의 시작 지점 (CSS로 요소 지정)
- data : 인스턴스의 데이터 속성으로 model을 나타냄
- template : 화면에 표시할 HTML, CSS 등 마크업 요소를 정의하는 속성 (Vue의 데이터 및 기타 속성들도 함께 화면에 렌더링)
- methods : 이벤트 및 화면 동작 메서드로 속성들은 function
- created : 라이프 사이클 커스터마이징 로직
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vuejs template</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> This will be replaced ! </div> <script> var app = new Vue({ el: '#app', data: { message: '안녕하세요 Vue!' }, template: `<div>{{message}}</div>` //template 속성값에는 반드시 root태그 필요 }); </script> </body> </html>
- 📋 실행 📋
3) Vue 객체의 유효범위 (scope)
- Vue 객체의 유효범위는 Vue 객체가 관리하는 HTML의 범위 (el 속성에 지정된 영역)
- 해당 범위에서만 Vue가 동작하기 때문에 지정된 scope를 벗어나면 {{}} 표현식 등 단순한 문자열로 처리
- Vue 객체 동작 과정
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vuejs app scope</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> scope범위포함:{{ message }} </div> scope범위 미포함:{{ message }} <script> var app = new Vue({ el: '#app', data: { message: '안녕하세요 Vue!' } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
📕 directive
1. directive 개념
- 지시자(directive)
- 지시자로서 Vue에서 template에 데이터를 표현하는 용도로 사용되는 속성
- Vue의 디렉티브들은 v-를 접두어로 가짐
- 지시자 종류
- 텍스트 표현 : v-text, v-html, v-once
- html 속성 바인딩 : v-bind
- 양방향 데이터 바인딩 : v-model
- 제어문 : v-show, v-if, v-else, v-else-if
- 반복문 : v-for
1) 텍스트 표현
- v-text
- innerText 속성에 연결되며 문자열 그대로 화면에 표현
- Model과의 반응성이 유지됨
- v-html
- innerHtml 속성에 연결되며 문자열 중 html 태그를 파싱해서 화면에 나타냄
- Model과의 반응성이 유지됨
- v-once
- v-text, v-html에 부가적으로 사용
- v-once는 1번 연동 후 반응성이 제거됨
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-text, v-html, v-once</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <ul> <li>{{ message }}</li> <li><span v-text="message"></span></li> <li v-html="message"></li> <li v-once>{{message}}</li> </ul> </div> <script> var app = new Vue({ el: '#app', data: { message: '<h1>안녕하세요 Vue!</h1>' } }); </script> </body> </html>
- 📋 실행 📋
2) html 속성 바인딩 (단방향 바인딩)
- v-bind
- html의 속성인 id, class, style 등에 model 값을 연결할 때 사용하는 단방향 바인딩
- 연동하려는 대상에 따라 v-bind:id, v-bind:class, v-bind:style 등 사용
- 축약형으로 v-bind 생략 가능
- html의 class 속성의 경우 여러 값 설정 가능
- 객체 형태로 값을 전달하는데 boolean 타입의 model값을 이용해서 해당 class를 on/off 처리
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-bind</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .red-accent { color: red; } </style> </head> <body> <div id="app"> <ul v-bind:id="mid"> <li v-bind:class="mclass">클래스바인딩</li> <li :style="mstyle">스타일바인딩</li> <li><a :href="link.mhref" :title="link.mtitle">go</a></li> </ul> <img v-bind:src="user_imgs[0]" /><br/> <img :src="user_imgs[1]" /> </div> <script> var app = new Vue({ el: '#app', data: { mid: "list", mclass: "red-accent", mstyle: "color:blue", link: { mhref: "http://vuejs.org", mtitle: "Vuejs" }, user_imgs : ["https://reqres.in/img/faces/1-image.jpg", "https://reqres.in/img/faces/2-image.jpg"] , } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-bind 멀티 class속성</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .set1 { color: red; } .set2 { background-color: blue } .set3 { font-style: italic; } </style> </head> <body> <div id="app"> <div :class="{set1:s1, set2:s2, set3:s3}"> Hello </div> </div> <script> var app = new Vue({ el: '#app', data: { s1: true, s2: false, s3: false, } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-bind 동적 전달 인자</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .set1 { color: red; } </style> </head> <body> <div id="app"> <div :[mid]="xxx" :[mclass]="s1"> Hello </div> </div> <script> var app = new Vue({ el: '#app', data: { mid: "id", mclass: "class", xxx: "v1", s1: "set1" } }); </script> </body> </html>
- 📋 실행 📋
3) 양방향 데이터 바인딩
- v-model
- view와 model 사이의 양방향 데이터 바인딩에 사용되는 디렉티브
- 사용자로부터 값을 입력 받는 <input>, <select>, <textarea> 등에서 사용
- 사용자가 입력한 값을 Model에 반영시킴
- Model이 변경되면 입력 요소의 값도 변경됨
- 다중 선택이 지원되는 <select>, checkbox의 경우의 값은 배열 객체와 연결되고 나머지 요소들은 단일 값과 연결됨
- 기능 추가하기 위한 수식어 제공 ('.' 이용해서 설정, 여러 속성 chaining 가능)
- lazy : 입력 폼에서 change 이벤트가 발생하면 데이터 동기화 (입력 후에 Enter치면 반영됨)
- number : 입력 값을 parseInt 또는 parseDouble을 이용해 number 타입으로 저장 (첫 숫자 이후의 문자는 무시됨)
- trim : 입력 문자열의 앞 뒤 공백을 자동으로 제거
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-model</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div> 이름1:<input v-model="username" /><br> <!-- 이름2(lazy):<input v-model.lazy="username" /><br> --> 입력한 이름:{{username}}<br> <hr/> 나이1:<input v-model="age" /><br> 나이2(number):<input v-model.number="age" /><br> 입력한 나이:{{age}}<br> <hr/> 주소1:<input v-model="address" /><br> 주소2(trim):<input v-model.trim="address" /><br> 입력한 주소:{{address}}<br> 입력한 주소:{{address.length}}<br> <hr/> 출생연도(lazy.number):<input v-model.lazy.number="year"><br> 입력연도:{{year}}<br> <hr/> 배열1:<input type="checkbox" v-model="team" value="A">A<br> 배열2:<input type="checkbox" v-model="team" value="B">B<br> 배열3:<input type="checkbox" v-model="team" value="C">C<br> 선택한 배열:{{team}} <hr/> <input type="checkbox" v-model="isFlag">user Image변경 <br/> <img :src="isFlag?user_imgs[0]:user_imgs[1]" /> </div> </div> <script> var app = new Vue({ el: '#app', data: { username: "", age: '', address: '', year: '', team: [], user_imgs : ["https://reqres.in/img/faces/1-image.jpg", "https://reqres.in/img/faces/2-image.jpg"], isFlag: true } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-model 2</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .red-accent { color: red; } </style> </head> <body> <div id="select"> <label>가장 좋아하는 캐릭터는?</label> <input type="text" v-model.trim="favorite"><br> <label>팀 멤버를 선택하세요</label> <input type="checkbox" value="1" v-model="team"><label>아이언맨</label> <input type="checkbox" value="2" v-model="team"><label>토르</label> <input type="checkbox" value="3" v-model="team"><label>헐크</label><br> <label>출생 연도는?</label> <input type="text" v-model.lazy.number="year"><br> </div> <hr> <div id="result"> <p>당신의 선택은 <ul> <li>캐릭터:{{favorite}}</li> <li>팀:{{team}}</li> <li>출생년도:{{year}}</li> </ul> </p> </div> <script> let model = { favorite: "", team: [], year: "" } var select = new Vue({ el: '#select', data: model }); var result = new Vue({ el: '#result', data: model }); </script> </body> </html>
- 📋 실행 📋
4) 제어문
- v-show
- 조건 불일치시 렌더링 방식 : display=none으로 처리
- 이에 따른 비용 : 초기 렌더링 비용이 큼
- 유용한 경우 : 자주 바뀔 때
- v-if
- 조건 불일치시 렌더링 방식 : 렌더링하지 않음
- 이에 따른 비용 : 토글 비용이 큼
- 유용한 경우 : 자주 바뀌지 않을 때
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-if, v-show</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input type="checkbox" v-model="isShow" />Image 보여주기 isShow 값은 {{isShow}} <br/> <img src="images/error.png" width="20" height="20" v-show="isShow" /><br/> 나이:<input v-model.number="age" /> <br/> <img v-show="age < 0" src="images/error.png" width="20" height="20"> <hr> 당신은: <span v-if="age < 0">입력오류</span> <span v-else-if="age < 8">미취학</span> <span v-else-if="age < 19">미성년</span> <span v-else>성년</span> </div> <script> var app = new Vue({ el: '#app', data: { age: '', isShow:false, } }); </script> </body> </html>
- 📋 실행 📋
5) 반복문
- v-for
- 유니크한 속성명을 이용해서 :key 설정
- 조건문과 연계 가능
- 문법
- v-for="항목 in 배열|객체" :key="값"
- 배열
- <v-for="contact in cotacts" :key="유일값">
- <v-for="(contact, index) in contacts" :key="유일값">
- 객체
- v-for="(val, key) in regions"
- v-for="(val, key, indexl) in regions"
- template 태그
- <template>는 여러 요소를 묶어서 반복 렌더링 할 때 요소를 묶어주는 태그
- 실제 렌더링 내용에는 포함되지 않고, 단지 요소들을 그룹핑하는 용도로 사용
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-for</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div> <h1>배열 반복1</h1> <ul> <li v-for="(value) in heros">{{value}}</li> </ul> <h1>배열 반복2</h1> <ul> <li v-for="(value,index) in heros" :key="index">{{index}}:{{value}}</li> </ul> <h1>객체 반복1</h1> <ul> <li v-for="(value,key) in hero">{{key}}:{{value}}</li> </ul> <h1>객체 반복2</h1> <ul> <li v-for="(value,key,index) in hero" :key="index">{{index}}:{{key}}:{{value}}</li> </ul> </div> </div> <script> var app = new Vue({ el: '#app', data: { heros: ["A", "B", "C"], hero: { name: "홍길동", age: 20, address: "서울" } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>directive v-for template</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <template v-for="(value, index) in heroes"> <div> <p>등장순서:{{index}}, 이름:{{value}}</p> </div> <hr v-if="index % 2==0"> </template> </div> <script> var app = new Vue({ el: '#app', data: { heroes: ["아이언맨", "헐크", "토르", "캡틴아메리카", "비전"], ironMan: { name: "토니스타크", nickName: "깡통" } } }); </script> </body> </html>
- 📋 실행 📋
📕 Vue 객체
1. Vue 객체
- Vue 객체
- MVVM 패턴에서 ViewModel을 담당하는 Vue.js의 핵심 객체
- Vue 생성자 함수에 옵션 객체를 전달해서 생성함
- 속성은 Vue 객체 외부에서 접근하기 위해서 $options 속성 이용
1) el
- elment, View를 연결하는 속성
- Vue로 만든 화면이 그려지는 인스턴스의 시작 지점
- el 지정할 때는 CSS 선택자를 이용해서 HTML의 DOM 속성 지정
- 반드시 하나만 지정
- 여러 개가 선택되더라도 맨 처음 요소만 사용됨 (ID기반 접근 필요)
- Vue 객체 외부에서 el요소에 접근하기 위해서는 $el 속성 이용
2) data
- Model을 연결하는 속성으로 JSON 객체
- 인스턴스의 데이터 속성으로 객체를 이용해 여러 값 저장
- Vue 객체 외부에서 data에 접근하기 위한 내장옵션으로 $data 사용
- data 속성들은 모두 Vue 객체의 속성으로 관리
- data 속성들은 모두 Vue 객체의 속성으로 관리
3) template
- 화면에 표시할 HTML, CSS 등 마크업 요소를 정의하는 속성
- Vue의 데이터 및 기타 속성들도 함께 렌더링
4) created
- 라이프 사이클 커스터마이징 로직
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue 객체</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> {{ message }} </div> <script> var app = new Vue({ el: '#app', data: { message: '안녕하세요 Vue!' } }); console.log("Vue객체:", app) console.log("el접근:", app.$el) console.log("message 접근1:", app.$data.message) console.log("message 접근2:", app.message) </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
2. Methods, computed, watch 속성
1) methods
- 이벤트 및 화면 동작 메서드
- Vue 객체에서 사용할 메서드들을 객체로 등록하는 속성
- 등록된 메서드는 Vue 객체에서 직접 호출하거나 디렉티브 표현식 등에서 data 처럼 사용 가능
- 호출할 때 반드시 () 사용
- 화면이 다시 렌더린 될 때마다 화면에서 사용되고 있는 methods들은 모두 다시 실행됨
- 호출 시점에 메서드가 실행되어 값을 반환하기 때문에 캐시 기능이 없음
2) computed
- 함수를 속성으로 갖는 객체
- methods 속성과 달리 생성 시점에 계산된 값을 반환하고, 이후에는 캐시값을 사용
- 호출시 반드시 함수명만 사용
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>vue 객체 methods, computed</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input type="number" v-model="num" /><br /> num : {{num}}<br /> message : <input type="text" v-model="message" /> <p>원본 메시지: "{{ message }}"</p> <p>역순으로 표시한 메시지: "{{ reversedMessage }}"</p> </div> <script> var app = new Vue({ el: "#app", data: { message: "Hello", num: 0, }, // methods 함수는 기본적으로 호출할때마다 매번 호출된다. (캐시기능없음) // 반드시 () 이용 // message 아닌 num 변경해도 계속 수행됨 (불필요한 렌더링 계속 발생) methods: { // reversedMessage() { // 위와 동일 // reversedMessage: function () { // console.log("methods reverseMessage"); // return this.message.split("").reverse().join(""); // olleH // }, }, // methods // computed 함수는 기본적으로 한번만 호출된다. ( 캐싱 기능 ) // () 사용안함 computed: { reversedMessage: function () { console.log("computed reverseMessage") return this.message.split("").reverse().join(""); }, }, }); // computed </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue 객체 methods, computed 화면 재렌더링시</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input type="number" v-model="num"> 값:{{num}}<br> {{xxx()}}{{xxx()}} {{yyy}}{{yyy}} </div> <script> var app = new Vue({ el: '#app', data: { num: 0 }, // methods 함수는 기본적으로 호출할때마다 매번 호출된다. (캐시기능없음) // 반드시 () 이용 // 화면이 다시 랜더링 될때마다 화면에서 사용되고 있는 methods들이 모두 다시 실행된다 methods: { xxx() { console.log("xxx"); } }, // computed 함수는 기본적으로 한번만 호출된다. ( 캐싱 기능 ) // () 사용안함 // 화면이 다시 랜더링 여부와 상관없이 실행 안됨. computed: { yyy() { console.log("yyy"); } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dvue 객체 methods, computed Data 변경시</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input type="number" v-model="num"> 값:{{num}}<br> {{xxx()}}{{xxx()}} {{yyy}}{{yyy}} </div> <script> var app = new Vue({ el: '#app', data: { num: 0 }, // methods 함수는 기본적으로 호출할때마다 매번 호출된다. (캐시기능없음) // 반드시 () 이용 // 화면이 다시 랜더링 될때마다 화면에서 사용되고 있는 methods들이 모두 다시 실행된다 methods: { xxx() { console.log("xxx", this.num); } }, // computed 함수는 기본적으로 한번만 호출된다. ( 캐싱 기능 ) // () 사용안함 // 화면이 다시 랜덩링 여부와 상관없이 실행 안됨. // 함수안에서 data 접근코드 지정하고 data가 변경되면 호출된다. computed: { yyy() { console.log("yyy", this.num); } } }); </script> </body> </html>
- 📋 실행 📋
3) watch
- methods, computed와 유사하게 함수를 관리하는 객체
- 기능은 데이터 변경시 특정 동작 처릭 가능한 함수들을 관리함
- watch함수를 등록할 때 함수의 이름은 반드시 변경할 모니터링할 객체의 속성명과 같아야함
- 함수 파라미터 값으로 old값과 new값을 전달 받을 수 있음
- 긴 시간이 필요로하는 비동기 처리에 적합함
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue 객체 watch/title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input type="number" v-model='v1'>+<input type="number" v-model='v2'>={{sum}} </div> <script> var app = new Vue({ el: '#app', data: { v1: 0, v2: 0, sum: 0 }, // 1. watch 속성의 함수명은 반드시 data 속성명과 일치해야 된다. // 2. methods 및 computed 함수도 data 속성이 변경되면 자동으로 호출되지만, // 반드시 명시적으로 함수명을 지정한 경우에만 해당된다. // 하지만 watch는 명시적으로 호출하지 않아도 자동으로 호출된다. watch: { v1(newValue, oldValue) { console.log("v1값 변경됨", oldValue, newValue); this.sum = Number.parseInt(newValue) + Number.parseInt(this.v2); }, v2(newValue, oldValue) { console.log("v2값 변경됨", oldValue, newValue); this.sum = Number.parseInt(this.v1) + Number.parseInt(newValue); } } }); bookList = [ {title: "java", price:100}, {title: "java2", price:200}, {title: "java3", price:300} ] </script> </body> </html>
- 📋 실행 📋
3. filters
- {{}} 표현식 또는 v-bind에서 텍스트 형식화하는 기능 제공
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue 객체 filter 지역적 </title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <span>원본:{{name}}</span><br> <span>대문자로:{{name|toCap}}</span><br> <span>첫 4글자만 대문자:{{name|subStr(0,4)|toCap}}</span> </div> </div> <script> var app = new Vue({ el: '#app', data: { name: 'HongKilDong', price: 1000000000 }, filters: { toCap(param) { return param.toUpperCase(); }, subStr(param, start, end) { console.log(start, end); return param.substr(start, end); } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue 객체 filter 전역적</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <span>원본1:{{name}}</span><br> <span>대문자로1:{{name|toCap}}</span><br> </div> <div id="app2"> <span>원본2:{{name}}</span><br> <span>대문자로2:{{name|toCap(1,2)}}</span><br> </div> <script> //전역객체 Vue.filter('toCap', (param, s, e) => { console.log(s, e); return param.toUpperCase(); }); var app = new Vue({ el: '#app', data: { name: 'EvanYou' } }); var app2 = new Vue({ el: '#app2', data: { name: 'JohnResig' } }); </script> </body> </html>
- 📋 실행 📋
4. 라이프 사이클 메서드
1) Vue 라이프 사이클 hook method
- Vue 객체는 생성에서부터 화면 연결, 값 변경에 대한 처리 등 관여하다가 처리됨
- 특정 시점에 콜백되는 라이프 사이클 훅 메서드
- beforeCreate
- Vue 객체가 생성되고 데이터에 대한 관찰 기능 및 이벤트 감시자 설정 전에 호출됨
- 거의 보이지X
- created
- Vue 객체가 생성된 후 데이터에 대한 관찰 가능
- computed, methods, watch 설정이 완료된 후
- beforeMount
- 아직 마운트가 시작되기 전에 호출
- DOM 구성 이전이므로 화면 건드리기에는 부적절함
- mounted
- DOM 요소가 el에 의한 가상 DOM으로 대체된 후에 호출됨
- 화면에 대한 완벽한 제어가 가능한 시점
- 부모의 mouted hook이 자식의 mounted hook보다 늦게 실행됨 (부모는 자식의 mouted hook이 끝날 때까지 대기)
- beforeUpdate
- 데이터가 변경되어서 가상DOM이 다시 렌더링 되기 전에 호출됨
- updated
- 데이터 변경으로 가상 DOM이 다시 렌더링되고 패티된 후에 호출됨
- 훅이 호출된 시점은 이미 가상 DOM이 렌더링된 후이므로 DOM에 대한 추가 작업 필요
- beforeDestroy
- Vue 객체가 제거되기 전에 호출됨
- destroyed
- Vue 객체가 제거된 후에 호출됨
- 훅이 호출될 때 Vue 객체의 모든 디렉티브 바인딩이 해제되고 이벤트 연결도 제거됨
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue 객체 lifecycle hook</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <h3 v-html="num"></h3> <button @click="plus">+</button> </div> <script> var app = new Vue({ el: '#app', data: { num: 0, title: "hook method" }, methods: { plus() { return ++this.num; } }, beforeCreate() { // 아직 el 이나 data가 연동되지 않았다. console.log("beforeCreate", this.title, this.$el); }, created() { // data, computed, methods, watch 설정이 완료된다. console.log("created", this.title, this.$el); }, beforeMount() { // data와 el이 준비 되었지만 아직 화면이 대체되지 않았다. console.log("beforeMount", this.title, this.$el.innerHTML); console.log("beforeMount", document.querySelector("#app").innerHTML); }, mounted() { // data와 el이 준비 되었으며 화면이 Virtual DOM으로 대체되었다. console.log("mounted", this.title, this.$el.innerHTML); console.log("mounted", document.querySelector("#app").innerHTML); }, beforeUpdate() { // + 를 누르면 값이 업데이트 되려고 한다. 값은 변경되었으나 화면 갱신전이다. console.log("beforeUpdate", this.num, document.querySelector("#app").innerHTML); }, updated() { // 값이 업데이트 되었고 화면도 갱신 되었다. console.log("updated", this.num, document.querySelector("#app").innerHTML); }, beforeDestory() { // 객체가 삭제 되기 직전이다. console.log("beforeDestory", this.title, this.$el); }, destroy() { // 객체가 삭제된 이후이다. console.log("destroy", this.title, this.$el); } }); console.log(app); </script> </body> </html>
- 📋 실행 📋
📕 이벤트
1. 이벤트 처리
- DOM 요소에 이벤트 등록
- v-on:evnentname=이벤트핸들러 사용
- @eventname=이벤트핸들러 사용
- Vue에서 처리할 수 있는 이벤트 종류는 DOM 객체의 이벤트 처리와 동일
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs 이벤트 처리</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> 현재 잔고: <span>{{balance}}</span><br/> <label>거래 금액</label> <input type="number" v-model="amount"> <button v-on:click="balance += parseInt(amount)">입금</button><br/><br/> <button @click="withdraw">출금1-콜백</button> <button @click="withdraw()">출금2-직접호출</button> </div> <script> var app = new Vue({ el: '#app', data: { amount: 0, balance: 1000 }, methods: { withdraw() { if (this.balance >= this.amount) { this.balance -= this.amount; } else { alert("잔액 부족"); } } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
2. 이벤트 객체
- DOM에서 발생하는 이벤트의 상세 정보 (어떤 요소에서 발생했는지 또는 입력한 키보드 키값은 무엇인지)는 이벤트 객체를 통해서 전달
1) 이벤트 객체에서 사용 속성/함수
- 공통 속성
- target : 이벤트 소스 (이벤트가 발생한 DOM 객체)
- path : 배열로 target부터 window까지 조상을 찾아가는 경로
- 키보드 이벤트 속성
- altKey/shiftKey/ctrlKey : alt/shift/ctrl 키가 눌렸는지 여부 (true/false)
- keyCode : 이벤트를 발생시킨 키보드의 고유 키 코드 (enter : 13)
- charCode : keypress 이벤트에서 눌린 unicode 캐릭터 코드
- 마우스 이벤트 속성
- clientX/clientY : viewport 영역에서 이벤트가 발생한 좌표로 스크롤된 길이에 영향을 받지 않음
- pageX/pageY : document 영역에서 이벤트가 발생한 좌표로 스크롤된 길이에 영향을 받음
- screenX/screenY : screen 영역에서 이벤트가 발생한 좌표
- 공통 함수
- preventDefault : 기본 이벤트 동작을 중지시킴
- stopPropagation : 이벤트 전파를 막음
2) 이벤트 핸들러 호출 방식
- Vue에서 이벤트 핸들러를 등록할 때 콜백 방식/직접 호출 방식 모두 가능
- 이벤트 핸들러 등록만 하는 형태는 시스템이 콜백하는 형태
- 파라미터로 이벤트 객체가 자동으로 전달됨
- 실행하는 형태는 직접 함수를 호출하는 것이고. 시스템이 콜백하지X
- 이벤트 객체를 직접 활용할 수 없고, $event 이용해서 명시적으로 전달해야 함
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs 이벤트 객체</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div @click="clicked" @mouseover="over()" @mouseout="out($event)"> Hello </div> </div> <script> var app = new Vue({ el: '#app', data: { amount: 0, balance: 1000 }, methods: { clicked(e) { console.log("clicked 콜백됨,", e); }, over(e) { console.log("over 콜백 안됨,", e); }, out(e) { console.log("over 콜백 안됨. 따라서 명시적으로 $event 지정,", e); } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs 이벤트 바인딩</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> #canvas { width: 100px; height: 100px; background-color: yellow; } </style> </head> <body> <div id="app"> <select v-model="your_event"> <option value="mousemove">mousemove</option> <option value="click">click</option> </select> >>> {{your_event}} <div id="canvas" @[your_event]="actionPerform($event)"></div> status:<input type="text" name="status" v-model="status"><br> x:<input type="text" name="x" v-model="x"><br> y:<input type="text" name="y" v-model="y"> </div> <script> var app = new Vue({ el: '#app', data: { your_event: 'mousemove', status: '', x: 0, y: 0, }, methods: { actionPerform(e) { this.status = e.type; this.x = e.clientX; this.y = e.clientY; }, } }); </script> </body> </html>
- 📋 실행 📋
- 이벤트 객체를 직접 활용할 수 없고, $event 이용해서 명시적으로 전달해야 함
3. 이벤트 수식어
- 이벤트 속성을 이용할 때 직접 이벤트 객체를 사용하기 보다 간단한 이벤트 수식어(Event Modifier) 이용해서 처리 가능
- 공통 이벤트 수식어
- prevent : 이벤트의 기본 동작 방지
- <form>의 submit 전송 방지
- <a>의 클릭으로 페이지 전환 방지
- prevaentDefault() 대체
- stop : 하나의 이벤트가 여러 요소에 걸쳐 발생할 때 전파 중단
- stopPropagation() 대체
- capture : 캡쳐링 상태에서 이벤트 처리
- self : 버블링(raising) 상태에서 이벤트 처리
- once : 한번만 이벤트 발생시키고 이후는 동작하지 않음
- passive : 혹시나 있을지 모를 preventDefault() 실행 안되게 함 (동작 보장)
- prevent : 이벤트의 기본 동작 방지
- 키보드 이벤트 수식어
- 숫자 : 입력되는 키에 대한 키 코드 입력 값 제한
- 대표적인 키들은 상수로 등록됨
- 🗒️ 예시 : enter, tab, delete, esc, space, up, down, left, right, ctrl, altm shift
- 조합되는 키의 경우 수식어 연결 사용 (@keyup.ctrl.67="event_name")
- 숫자 : 입력되는 키에 대한 키 코드 입력 값 제한
- 마우스 이벤트 수식어
- left, right, middle : 각각 마우스의 어떤 버튼이 클릭 됐는지로 제한
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs 기본 이벤트 처리 방지_이벤트 수식어 사용 전</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <h2>preventDefault</h2> <form action="https://www.google.com/" @submit="login"> ID:<input type="text" placeholder="ID는 4글자 이상입니다." v-model="id" ref="myId"> <input type="submit"> </form> <h2>stopPropagation</h2> <div style="background-color: yellow;" @click="parent"> parent <div style="background-color: green;" @click="child"> child </div> </div> <h2>keyup.enter</h2> num1:<input type="text" @keyup="knum"><br> </div> <script> var app = new Vue({ el: '#app', data: { id: "" }, methods: { login(e) { if (this.id.length >= 4) { e.target.submit(); } else { e.preventDefault(); alert("ID를 4글자 이상으로 넣어주세요"); this.$refs.myId.focus(); } }, parent(e) { console.log("parent"); }, child(e) { console.log("child"); e.stopPropagation(); }, knum(e) { // console.log(e.keyCode); //enter는 13 if (e.keyCode == 13) { console.log("knum"); } } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs 기본 이벤트 처리 방지_이벤트 수식어 사용 후</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <h2>preventDefault</h2> <form action="https://www.google.com/" @submit.prevent="login"> ID:<input type="text" placeholder="ID는 4글자 이상입니다." v-model="id" ref="myId"> <input type="submit"> </form> <h2>stopPropagation</h2> <div style="background-color: yellow;" @click="parent"> parent <div style="background-color: green;" @click.stop="child"> child </div> </div> <h2>keyup.enter</h2> num1:<input type="text" @keyup="knum"><br> num2:<input type="text" @keyup.enter="knum"><br> <button @click.once="one">한번만트리거됨</button> </div> <script> var app = new Vue({ el: '#app', data: { id: "" }, methods: { login(e) { if (this.id.length >= 4) { e.target.submit(); } else { e.preventDefault(); alert("ID를 4글자 이상으로 넣어주세요"); this.$refs.myId.focus(); } }, parent(e) { console.log("parent"); }, child(e) { console.log("child"); }, knum() { console.log("knum"); }, one() { console.log("one"); } } }); </script> </body> </html>
- 📋 실행 📋
4. 화면에서 다른 요소 참조
- DOM 이벤트 처리시 화면의 다른 DOM 요소를 참조하는 경우
- 자바스크립트에서 document.querySelector("#target")와 같이 DOM 요소를 지정함
- Vue에서는 DOM에 직접 접근하는 일이 없음
- 필요시 ref 속성으로 DOM 요소에 표시하고, Vue 객체 내부에서는 $refs 속성으로 접근 가능
<div id="app">
<form @submit.prevent="login">
<label for="id">아이디</label>
<input type="text" name="id" id="id" @keyup.prevent.enter="next" v-model.lazy="id"/>
<!-- ref 속성으로 DOM에 표시 -->
<input type="password" ref="pass" @keyup.enter="search"/>
<input type="button" value="제출"/>
</form>
</div>
<script>
let vi = new Vue({
el: "#app",
data: {
id: "",
},
methods: {
next() {
// ref 속성으로 접근 가능
let pass = this.$refs.pass;
console.log(pass);
pass.focus();
},
login() {
console.log("로그인 처리");
},
search() {
console.log("id: " + this.id);
}
}
});
</script>
- 📋 코드 📋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs ref 참조</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> num:<input type="text" ref="xyz"><br> <button @click="ok">OK</button> </div> <script> var app = new Vue({ el: '#app', methods: { ok() { console.log(this.$refs.xyz.value); } } }); </script> </body> </html>
- 📋 실행 📋
📕 컴포넌트
1. component
- component 개념
- 재사용 가능한 블록 형태의 요소를 의미
- Vue에서의 컴포넌트도 조합해서 화면을 구성할 수 있는 블록
- 재사용성 강화 목적
- SPA(Single Page Application) 적용시 컴포넌트 사용 필수
- 등록 방식에 따른 component 종류
- 지역 컴포넌트 : 특정 Vue 객체에서만 유효한 범위를 가짐
- 전역 컴포넌트 : 여러 Vue 객체에서 공통으로 사용 가능
2. 전역 컴포넌트
- Vue.component(tagName, options)
- 전역 컴포넌트는 Vue 객체의 component 속성 이용해서 등록
- tagName
- 컴포넌트의 이름
- 사용자 정의 HTML 태그으이 이름
- 모두 소문자 사용, 케밥 표현식 사용 (단어의 연결은 -(하이픈) 사용)
- HTML태그와 다르게 빈 태그라도 <My-component> 식으로 사용X
- options
- 컴포넌트의 속성을 설정하는 옵션 객체
- template, data 등 선언됨
- template : 화면에 보유줄 모습인 html 태그 자성
- data : Vue 객체에서 사용될 data 선언
- 컴포넌트 사용시 주의할 점
- template : 반드시 root element가 존재하고, 그 안에 다른 element가 나와야 함
- <!-- root 존재하는 경우는 가능 --> <template id="body-template"> <div> <div></div> </div> <template> <!-- root 없는 경우는 불가능--> <template id="body-template"> <div></div> <div></div> </template>
- data : 컴포넌트의 options에 정의하는 data는 반드시 함수로 작성하고, 함수 내부에서 리턴되는 객체**를 사용
- /* data: { cuttent: 0, } */ // return 되는 함수로 data 정의 data: function() { return { current: 0, }; }
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>전역 컴포넌트 사용 전</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div> <h1>검색기능</h1> 검색어:<input /> <hr> <ul> <li>야구</li> <li>농구</li> <li>축구</li> </ul> </div> </div> <script> var app = new Vue({ el: '#app' }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>전역 컴포넌트 사용 후</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div> <search-component></search-component> <hr> <list-component></list-component> </div> </div> <template id="list-template"> <ul> <li>야구</li> <li>농구</li> <li>축구</li> </ul> </template> <script> // 컴포너트의 태그명은 일반적으로 케밥표기법을 선호한다. var search = Vue.component("search-component", { //template은 반드시 root태그 필수 template: ` <div> <h1>검색기능</h1> 검색어:<input /> </div> ` }); var list = Vue.component("list-component", { template: "#list-template" }); var app = new Vue({ el: '#app' }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>전역 컴포넌트 사용 후 Data 사용</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div> <search-component></search-component> <hr> <list-component></list-component> </div> </div> <template id="list-template"> <ul> <li v-for="value in hobby">{{value}}</li> </ul> </template> <script> // 컴포너트의 태그명은 일반적으로 케밥표기법을 선호한다. var search = Vue.component("search-component", { //template은 반드시 root태그 필수 template: ` <div> <h1>검색기능</h1> 검색어:<input /> </div> ` }); var list = Vue.component("list-component", { template: "#list-template", //data 사용 ==> 반드시 함수로 작성하고 리턴되는객체 사용 data: function () { return { hobby: ["야구", "농구", "축구","수영"] } } }); var app = new Vue({ el: '#app' }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>전역 컴포넌트 Data 공유문제</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div> <date1-component></date1-component> <hr> <date2-component></date2-component> </div> </div> <template id="date-template"> <div> <button @click="increment">+</button> {{num}} </div> </template> <script> // 공유 데이터 var current = { num: 0 }; // 컴포너트의 태그명은 일반적으로 케밥표기법을 선호한다. var date1 = Vue.component("date1-component", { //template은 반드시 root태그 필수 template: "#date-template", data: function () { return current; }, methods: { increment() { this.num = this.num + 1; } } }); var date2 = Vue.component("date2-component", { //template은 반드시 root태그 필수 template: "#date-template", data: function () { return current; }, methods: { increment() { this.num = this.num + 1; } } }); var app = new Vue({ el: '#app' }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>전역 컴포넌트 Data 공유 문제 해결</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div> <date1-component></date1-component> <hr> <date2-component></date2-component> </div> </div> <template id="date-template"> <div> <button @click="increment">+</button> {{num}} </div> </template> <script> // var current = { num: 0 }; // 컴포너트의 태그명은 일반적으로 케밥표기법을 선호한다. var date1 = Vue.component("date1-component", { //template은 반드시 root태그 필수 template: "#date-template", data: function () { return { num: 0 }; }, methods: { increment() { this.num = this.num + 1; } } }); var date2 = Vue.component("date2-component", { //template은 반드시 root태그 필수 template: "#date-template", data: function () { return { num: 0 }; }, methods: { increment() { this.num = this.num + 1; } } }); var app = new Vue({ el: '#app' }); </script> </body> </html>
- 📋 실행 📋
3. 지역 컴포넌트
- 지역 컴포넌트는 Vue 객체를 구성하면서 components 속성을 추가해서 등록함
new Vue(
components: {
'component-name':'component 내용'
}
};
- 나머지 작성 방법은 전역 컴포넌트와 동일
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>지역 컴포넌트</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> {{message}} <local-component></local-component> </div> <script> var app = new Vue({ el: '#app', data: { message: '안녕하세요 Vue!' }, components: { 'local-component': { template: `<h1>지역컴포넌트{{num}}</h1>`, data: function () { return { num: 200 } } } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>지역 컴포넌트와 전역 컴폰너트 비교</title> <!-- 개발버전, 도움되는 콘솔 경고를 포함. --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> {{message}} <global-component></global-component> <local-component></local-component> </div> <hr> <div id="app2"> <global-component></global-component> <!-- <local-component></local-component> --> </div> <script> var global = Vue.component("global-component", { template: `<h3>global component</h3>` }); var app2 = new Vue({ el: '#app2' }); var app = new Vue({ el: '#app', data: { message: '안녕하세요 Vue!' }, components: { 'local-component': { template: `<h3>지역컴포넌트{{num}}</h3>`, data: function () { return { num: 200 } } } } }); </script> </body> </html>
- 📋 실행 📋
📕 컴포넌트 통신
1. 컴포넌트 통신
- 각각의 컴포넌트는 개별적으로 고유한 유효 범위를 가지기 때문에 다른 컴포넌트의 데이터를 직접 참조X
- 컴포넌트간의 자료 전달 방식은 관계에 따라 다른 방식 사용
- 부모/자식 (상하위) 컴포넌트의 관계
- 상위 ➡️ 하위로의 전달은 props 속성 이용
- 하위 ➡️ 상위로의 전달은 이벤트 통해서 데이터 전달
- 상하위 관계가 아닌 경우
- 이벤트 버스 이용
- 이벤트 버스 이용
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs 기본 구조</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> #t1 { background-color: yellow; } #t2 { background-color: blue; } #t3 { background-color: green; } </style> </head> <body> <div id="app"> <top1-component></top1-component> </div> <template id="top1"> <div id="t1"> <h1>top1</h1> <top2-component></top2-component> </div> </template> <template id="top2"> <div id="t2"> <h2>top2</h2> <top3-component></top3-component> </div> </template> <template id="top3"> <div id="t3"> <h3>top3</h3> </div> </template> <script> const t1 = Vue.component('top1-component', { template: "#top1" }); const t2 = Vue.component('top2-component', { template: "#top2" }); const t3 = Vue.component('top3-component', { template: "#top3" }); var app = new Vue({ el: '#app', }); </script> </body> </html>
- 📋 실행 📋
- 부모/자식 (상하위) 컴포넌트의 관계
2. props 속성 (상위➡️하위 데이터 전달)
- 상위 컴포넌트의 데이터를 하위 컴포넌트에서 사용할 때 - 하위 컴포넌트의 props 속성에 선언한 변수(속성명)에 상위 컴포넌트가 태그의 '속성명=값' 형태로 전달
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs props</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <child-component xyz="hello" xyz2="hi"></child-component> <child-component :xyz="message" xyz2="hi"></child-component> </div> <template id="child"> <div> <h1>child</h1> {{xyz}}<br> {{xyz2}}<br> </div> </template> <script> //자식 const t1 = Vue.component('child-component', { template: "#child", props: ['xyz', 'xyz2'] }); //부모 var app = new Vue({ el: '#app', data: { message: "안녕하세요" } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs props 속성명</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <!-- 전달할 데이터 키는 캐밥 표기법으로 지정--> <child-component xyz="hello" user-name="hi" user-age="20"></child-component> <child-component :xyz="message" user-name="hi"></child-component> </div> <template id="child"> <div> <h1>child</h1> {{xyz}}<br> {{userName}}<br> {{userAge}}<br> {{xxx()}} </div> </template> <script> //자식 const t1 = Vue.component('child-component', { template: "#child", props: ['xyz', 'user-name', 'userAge'] //받는 쪽에서는 카멜표기법 가능 , methods: { xxx() { console.log(this.userAge, this.xyz, this.userName); // this.user-name은 안됨 } } }); //부모 var app = new Vue({ el: '#app', data: { message: "안녕하세요" } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs props 배열</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <top1-component :lang-data="language"></top1-component> </div> <template id="progrmming"> <div> <h1>프로그램 언어</h1> <ul v-for="(lang,index) in langData"> <li>{{lang}}</li> </ul> </div> </template> <script> //자식 const t1 = Vue.component('top1-component', { template: "#progrmming", props: ['langData'] }); //부모 var app = new Vue({ el: '#app', data: function () { return { language: ['자바', 'SQL', 'Vue.js'] } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html> <head> <title>Vuejs 배열 활용_예방접종 샘플</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <h1>예방 접종 대상자</h1> <h3>1차 대상자</h3> <contact-component :contacts="list1"></contact-component> <h3>2차 대상자</h3> <contact-component :contacts="list2"></contact-component> </div> <template id="contactTemplate"> <table border="1"> <tr> <th>번호</th> <th>이름</th> <th>전화번호</th> <th>주소</th> </tr> <tr v-for="contact in contacts"> <td>{{contact.no}}</td> <td>{{contact.name}}</td> <td>{{contact.phone}}</td> <td>{{contact.address}}</td> </tr> </table> </template> <script> const t1 = Vue.component("contact-component", { template: `#contactTemplate`, props: ["contacts"], }); var app = new Vue({ el: "#app", data: { list1: [ { no: 10, name: "홍길동", phone: "010-1111-1111", address: "서울" }, { no: 9, name: "이순신", phone: "010-4444-4444", address: "전라" }, { no: 8, name: "강감찬", phone: "010-3333-3333", address: "경기" }, { no: 7, name: "임꺽정", phone: "010-9999-9999", address: "전라" }, { no: 6, name: "윤동주", phone: "010-8888-8888", address: "서울" }, ], list2: [ { no: 5, name: "윤봉길", phone: "010-7777-7777", address: "제주" }, { no: 4, name: "한용운", phone: "010-6666-6666", address: "부산" }, { no: 3, name: "백범", phone: "010-5555-5555", address: "강원" }, { no: 2, name: "안창호", phone: "010-2222-2222", address: "울산" }, { no: 1, name: "이육사", phone: "010-0909-0909", address: "서울" }, ], }, }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs props slot 1</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> p { color: red; } </style> </head> <body> <div id="app"> <child-component :xyz="message"> <!-- 자식에게 전달할 html태그는 일반적으로 template 사용함.--> <template> <div> <p>자식에게 전달할 html</p> </div> </template> </child-component> </div> <template id="child"> <div> <h1>child</h1> <h2>부모에서 자식에게 문자열 전달: props</h2> {{xyz}} <h2>부모에서 자식에게 html 전달: slot</h2> <slot></slot> <slot></slot> </div> </template> <script> //자식 const t1 = Vue.component('child-component', { template: "#child", props: ['xyz'] }); //부모 var app = new Vue({ el: '#app', data: function () { return { message: "안녕하세요" } } }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs props slot2</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> p { color: red; } </style> </head> <body> <div id="app"> <child-component :xyz="message"> <template v-slot:header> <div> <p>자식에게 전달할 html,header</p> </div> </template> <template v-slot:body> <div> <p>자식에게 전달할 html,body</p> </div> </template> <template v-slot:footer> <div> <p>자식에게 전달할 html,footer</p> </div> </template> </child-component> </div> <template id="child"> <div> <h1>child</h1> <h2>부모에서 자식에게 문자열 전달: props</h2> {{xyz}} <h2>부모에서 자식에게 html 전달: slot</h2> <slot name="body"></slot> <slot name="header"></slot> <slot name="footer"></slot> </div> </template> <script> //자식 const t1 = Vue.component('child-component', { template: "#child", props: ['xyz'] }); //부모 var app = new Vue({ el: '#app', data: function () { return { message: "안녕하세요" } } }); </script> </body> </html>
- 📋 실행 📋
3. $emit 사용자정의 이벤트 (하위➡️ 상위 데이터 전달)
- 하위 컴포넌트의 데이터를 상위 컴포넌트로 전달하기 위해서 사용자 정의 이벤트를 발생시켜서 데이터 전달
- 하위 컴포넌트가 이벤트 발생시키면 이벤트를 통해서 사위 컴포넌트의 함수를 동작시키는 형태
- 이벤트를 발생시킬 때에는 Vue 객체의 $emit 함수 사용
- 📋 코드 📋
- <!DOCTYPE html> <html> <head> <title>Vuejs emit 1</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <!-- <button @click="receive">parent</button> --> <hello-component v-on:xyz="receive"></hello-component> </div> <template id="helloTemplate"> <div> <button @click="send">child</button> </div> </template> <script> Vue.component('hello-component', { template: `#helloTemplate`, methods: { send: function (e) { //부모에게 xyz 이벤트 발신 this.$emit('xyz'); } } }); var app = new Vue({ el: '#app', methods: { receive: function () { console.log("parent.receive"); } } }) </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html> <head> <title>Vuejs emit 2</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <!-- <button @click="receive">parent</button> --> <hello-component v-on:xyz="receive"></hello-component> </div> <template id="helloTemplate"> <div> <button @click="send">child</button> </div> </template> <script> Vue.component('hello-component', { template: `#helloTemplate`, methods: { send: function (e) { //부모에게 xyz 이벤트 발신 this.$emit('xyz', '홍길동', 20); } } }); var app = new Vue({ el: '#app', methods: { receive: function (name, age) { console.log("parent.receive", name, age); } } }) </script> </body> </html>
- 📋 실행 📋
4. 이벤트 버스 (상/하위 레벨 아닌 데이터 전달)
- 상/하위 레벨이 아닌 컴포넌트 간은 직접적인 정보 전달이 불가능
- 두 컴포넌트 간 직접적으로 이벤트를 연결하기 위해서 이벤트 버스 이용
- 두 컴포넌트 간 직접적으로 이벤트를 연결하기 위해서 이벤트 버스 이용
- let eventBus = new Vue();
- 이벤트 버스는 단순한 Vue 객체
- 이벤트 버스는 데이터를 보내려는 쪽과 데이터를 받는 쪽 모두에서 사용
- 보내는 쪽에서 이벤트를 발신(emit)하고, 받는 쪽에서는 v-on 이용하여 이벤트 수신
- 📋 코드 📋
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vuejs event bus</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <comp-1></comp-1> <comp-2></comp-2> </div> <script> //이벤트 버스 생성 let eventBus = new Vue(); // < !--입력 컴포넌트-- > Vue.component('comp-1', { template: `<input type="text" @change="trans">`, methods: { trans(e) { eventBus.$emit("gogo", e.target.value); } } }); // < !--출력 컴포넌트-- > Vue.component('comp-2', { template: `<span>{{msg}}</span>`, data: function () { return { msg: "" } }, created() { eventBus.$on("gogo", this.update); }, methods: { update(data) { this.msg = data; } } }); var app = new Vue({ el: '#app' }); </script> </body> </html>
- 📋 실행 📋
- 📋 코드 📋
- <!DOCTYPE html> <html> <head> <title>Vuejs event bus_Todo 샘플</title> <script src="https://unpkg.com/vue"></script> <!-- 이벤트 버스 객체 생성--> <script> var eventBus = new Vue(); </script> <!-- input 컴포넌트 생성--> <template id="inputTemplate"> <div> <input type="text" @keyup.enter="add" v-model="mesg"> </div> </template> <script> Vue.component('input-component', { template: `#inputTemplate`, data: function () { return { mesg: '' } }, methods: { add: function () { eventBus.$emit('xyz', this.mesg); } } }); </script> <!-- child2 컴포넌트 생성--> <template id="listTemplate"> <div> <ul v-for="(a,idx) in toDoList"> <li>{{a}}<button @click="del(idx)">삭제</button> </li> </ul> </div> </template> <script> Vue.component('list-component', { template: `#listTemplate`, data: function () { return { toDoList: [] } }, created: function () { eventBus.$on('xyz', this.add) }, methods: { add: function (m) { this.toDoList.push(m); }, del: function (idx) { console.log(idx); this.toDoList.splice(idx, 1); } } }); </script> </head> <body> <div id="app"> <input-component></input-component> <list-component></list-component> </div> <script> var app = new Vue({ el: '#app' }) </script> </body> </html>
📕 Vue Router
1. Vue Router
1) Vue Router 개념
- SPA는 서버에 웹 페이지를 요청하여 새로 갱신하지 않고, 처음의 한 페이지를 가지고 있다가 내용만 교체하는 형태로 동작
- SPA 방식에서 router 이용하여 웹 페이지간(사식은 컴포넌트) 이동
- Vue Router는 뷰에서 라우팅 기능을 지원하는 공식 라이브러리
- 기본 동작은 hash(#)가 path에 연결되는 HASH 기반 동작
- html 영역
- <router-link to="target_URL"> : 페이지 이동 태그로 화면에서 <a> 클릭시 target_URL로 이동
- <router-view> : 페이지 표시 태그로 변경되는 URL에 따라 해당 컴포넌트를 뿌려주는 영역
- javascript 영역
// Vue Router 생성 const router = new VueRouter({ // 라우팅 경로 정의 : {name: "path_alias", path: "요청 URL", component: "표시할 컴포넌트"} routes: [{ path: "/", component: null}, {path: "/sub1", component: sub1}, {path: "/sub2", component: sub2}] }); // Vue 객체에 Router 설정 new Vue({ el: "#app", router: router })
2) Vue Router 주요 기능
- 중첩된 경로, 뷰를 매핑 처리
- 컴포넌트 기반의 라우팅 구현 가능
- Vue.js의 화면 전환 효과(transition) 적용 가능
- 히스토리 모드와 해시 모드 사용
- 쿼리스트링, 파라미터, 와일드카드를 이용한 라우팅
3) Vue Router 구성 파악
- 개발자 도구 사용
2. 동적 및 객체 바운딩 라우팅
1) 동적 라우팅
- 라우터를 이용해서 컴포넌트를 연결할 때, 해당 컴포넌트에 동적으로 필요한 파라미터 전달할 경우
- URL을 path variable 형태로 작성해서 컴포넌트에 전달
- '경로/:변수' 형태로 사용
- html 영역
<router-link to="/user/hong">hong</router-link> <router-link to="/user/jang">jang</router-link>
- VueRouter 영역
const vr = new VueRouter({ routes: [{ path: "user/:id", component: user }] })
- Vue.component 영역
const user = Vue.component("comp-user", { template: "<div>여기는 서브user: {{$route.params.id}}</span></div>" })
2) 객체 바인딩 이용한 라우팅
- 라우팅 구현시 경로를 지정할 때는 문자열 형태 뿐만 아니라 v-bind를 이용핸 객체 바인딩 및 프로그래밍 처리 가능
- v-bind를 이용한 객체 바인딩
- <router-link>의 to에 v-bind:to(축약 형식은 :to) 구문을 이용해서 객체 형태로 바인딩 시킬 수 있음
- 세밀한 링크 설정 가능
- 객체 바인딩 구성
- path 속성 : 전달하려는 경로
- query 속성 : JSON 형식, query string 형태로 전달되는 파라미터 ($route.query로 접근 가능)
- name 속성 : path 기반이 아닌 routes 요소의 이름 기반으로 이동 가능
- params 속성 : name 자체에는 path variable을 설정 할 수 없으므로 name 형태에서 파라미터를 전달할 때 사용, JSON 형식이며 내부적으로 전달되는 파라미터 ($route.params로 접근 가능)
- 프로그래밍 방식 라우팅
- 직접 link 클릭해서 이동할 수 있지만 프로그래밍적으로 특정 component로 이동해야 하는 경우 Router에게 이동에 필요한 정보를 전달해야함
- VueRouter는 push() 함수 제공
- query 전달 : path 및 name 속성 모두 가능
- params 전달 : name 속성만 가능
3. 중첩 라우팅, named view
1) nested router
- 페이지를 구성하다 보면 상위 요소가 선택되어야 의미있는 하위 요소들이 존재
- 🗒️ 예시 : 특정 사용자의 프로필을 보거나, 그 사람이 작성한 글ㅇ르 보는 상황
- VueRouter 구성
- route 정보에 path, component와 함께 추가로 chidren 속성을 이용해서 하위 route 작성
- VueRouter 영역
- 직접 profile 호출하더라도 상위 컴포넌트인 user를 만들고 profile rntjdgkrp ehla
const router = new VueRouter({ routes: [{ // 상위 컴포넌트에 대한 route 정보 포함 path: "/user/:id", component: userComp, // 자식 컴포넌트에 대한 route 정보 포함 children: [ {path: "profile", component: profileComp}, // 자식의 Path에는 '/' 사용X {path: "posts", component: postComp}] }] });
- html 영역
- 각각의 링크에 대한 <route-link>를 작성하면 되는데, 하위 컴포넌트의 경로는 상위 컴포넌트의 경로를 포함해서 작성
<div id="app"> <p> <router-link to="/user/hong">사용자 홈<router-link/> <!-- 상위 컴포넌트의 경로를 포함함 --> <router-link to="/user/hong/prifile">사용자 프로필</router-link> <router-link to="/user/hong/posts">사용자 포스트</router-link> </p> <!-- component들이 보일 자리 --> <router-view></router-view> </div>
- component 영역
// 하위 component 정의 let profileComp = Vue.component('profile-comp', { template: `<div>사용자의 프로필입니다.<\div>` }); let postComp = Vue.component('post-comp', { template: `<div>사용자의 작성글입니다.<\div>` }); let userComp = = Vue.component('user-comp', { template: `<div>사용자의{{$route.params.id}} <router-view></router-view><\div>` });
2) named view
- 이름이 붙여진 <router-view>
- 동시에 여러 개의 컴포넌트가 화면에 표시되어야 하는 경우 컴포넌트들을 특정 <router-view>에 배치되도록 <router-view name="이름">형식으로 지정
- html 영역
<div id="app"> <router-view name="header"></router-view> <!-- 이름이 없는 router-view: default --> <router-view></router-view> <router-view name="footer"></router-view> </div>
- VueRouter 영역
- 기존의 route 정보를 구성할 때는 요소와 path가 component였다면, named view가 사용되는 경우 path 하나에 여러 개의 components 사용
const vr = new VueRouter({ routes: [{ // 경로 하나에 여러 개의 컴포넌트 path: "/", components: { header: headerComp, default: bodyComp, footer: footerComp, } }] });
- 기존의 route 정보를 구성할 때는 요소와 path가 component였다면, named view가 사용되는 경우 path 하나에 여러 개의 components 사용
- Vue.component 영역
const headerComp = { template: "<p>이것은 header</p>" }; const bodyComp = { template: "<p>이것은 body</p>" }; const footerComp = { template: "<p>이것은 footer</p>" };
- 실행 결과
4. navigation guard
1) navigation guard 개념
- router를 이용한 navigation 과정에서 라우터가 동작하기 전/후에 전처리 및 후처리를 가능하게 해주는 방법
- 전역적으로 지정하거나 route별 지정 가능
- 🗒️ 예시
- 로그인하지 않은 사용자에게 로그인하도록 유도
- 권한이 없는 사용자의 접근을 차단
- navigation이 발생하는 로그를 작성
- html 영역
<div id="app"> <h1>navigation guard test</h1> <p> <router-link to="/login">login</router-link>| <router-link to="/important">중요한 페이지 보러 가기</router-link>| <router-link to="/important?id=hong">중요한 홍 페이지 보러가기</router-link>| <router-link to="/normal">그냥 일반</router-link>| </p> <hr> <router-view></router-view> </div>
- VueRouter 영역
const router = new VueRouter({ routes: [ {path: "/login", component: LoginComp}, {path: "/important", component: ImportantComp}, {path: "/normal", component: NormalComp} ] });
- 전처리 guard 영역
const needLogin = ["/important"]; router.beforeEach((to, from, next) => { console.log(`경로 로깅: from : ${from.path} ==> to: ${to.path}`) if (needLogin.includes(to.path) && to.query.id !== "hong") { next("/login"); // 현재의 이동이 중단되고 지정된 페이지로 리다이렉션 } else { next(); // 그냥 다음 route 호출 } });
- 후처리 guard 영역
router.afterEach((to, from) => { console.log(`router 작업 완료 : from : ${from.path} ==> to: ${to.path}`) });
- route별 guard 영역
const router = new VueRouter({ routes: [ {path: "/login", component: LoginComp}, {path: "/important", component: ImportantComp}, {path: "/normal", component: NormalComp, beforeEnter(to, from, next) { console.log(`route별 guard: from : ${from.path} ==> to: ${to.path}`) next(); } } ] });
2) 전역 가드 작성 (전처리)
- router.beforeEach 함수 이용하면 모든 라우터 요청이 처리되기 전에 동작하는 가드 설정 가능
- beforeEach함수는 to, from, next를 파라미터로 갖는 callback을 받음
- to : 호출 대상 route
- from : 현재 route
- next : next() 함수로서 파라미터에 따라 동작이 달라짐
3) next() 함수
- next() 함수가 호출되지 않으면 어떠한 component 교체도 발생되지 않음
- 종류
- next() : 그냥 다음 route인 to를 연결
- next("/redirect_path") : to에 대한 연결을 중지하고 지정한 path로 리다이렉션함 ({path: ""} 형식도 가능함)
- next(false) : to로의 이동을 중지하고 다시 from으로 이동
- next(Error) : Error 객체를 전달받으면 to로의 이동을 중지하고 route.onError()에 등록된 콜백을 수행함
📒 Vue-CLI
📕 Vue-CLI
1. SPA vs MPA
- 웹 어플리케이션의 UI 화면을 구현할 때 사용 가능한 형태
- SPA와 MPA 동작 방식의 차이점은 Ajax 동작으/Form 전송으로 구분
1) SPA (Single Page Application)
- Ajax 이용해 데이터만 전달 (Client Side Rendering)
- UI화면과 관련된 리소스를 처음 요청시 서버로부터 몽땅 받아낸 후 클라이언트에서 모든 HTML/CSS/JS 가지고 있음
- 이후 Ajax 통신을 통해 변경하고자 하는 데이터만 받아옴
- 장점
- SPA는 사용 중 리소스 로딩이 없기 때문에 부드럽게 화면 전환이 이루어짐
- 서버 입장에서 템플릿(화면)을 만드는 연산이 클라이언트로 분산되기 때문에 부담이 줄어듦
- 컴포넌트별로 개발하기 때문에 생산성 향상
- 모바일 앱에서도 동일한 패턴의 Rest API 사용 가능
- 단점
- URL이 변경되지 않기 때문에 검색엔진의 색인화가 어려움
- 초기 구동 비용이 MPA 대비 상대적으로 비쌈
2) MPA (Multi Page Application)
- 요청 시마다 전체 HTML 로딩 (Server Side Rendering)
3) SFC (Single File Component)
- 파일 하나가 하나의 컴포넌트가 됨
- 하나의 컴포넌트를 만들 때 필요한 요소들이 하나의 파일에 모이는 것
- 화면을 구성하는 3가지 요소들을 하나로 묶어 주는 것
- 만들어진 모듈은 import와 export를 이용해서 서로 간에 참조가 가능함
- Vue는 .vue 확장자 파일을 이용
- SFC 파일 구조
- <template> : HTML 작성
- 화면을 구성하는 html 부분
- 반드시 하나의 root 태그 가져야함
- 기존 작성과 차이점은 하나의 template만 존재하기 때문에 별도의 id값 지정X
- <style> : CSS
- 스타일을 담당하는 css 코드
- 기본적으로 전역 레벨이 되어서 다른 컴포넌트에 영향을 줄 수 있기 때문에 현재 컴포넌트에만 국한시킬 경우 scoped 속성 지정
- <script> : JavaScript 영역
- 컴포넌트를 만들어서 export 해주는 역할
- 객체는 하나만 만들고 default로 export함
- <template> : HTML 작성
4) Vue를 위한 전처리 작업
- .vue로 개발된 파일들은 WebPack과 같은 모듈 빌더 툴을 이용해서 html 파일로 변환
- 단순히 파일이 변환되는 것이 아니라 Babel 같은 시스템을 이용
- ECMA6 등 높은 버전의 스크립트를 ECMA5로 다운 그레이드 시켜주기 때문에 하위 브라우저 호환성 해결
- Vue-CLI는 복잡한 WebPack을 보다 편리하게 사용할 수 있도록 도와주는 도구
2. Vue-CLI 개발
1) Vue-CLI 설치
npm install -g @vue/cli
2) Project 생성
vue create 프로젝트명
3) Project 실행
npm run serve
// http://localhost:8080/ 요청
3. 생성된 프로젝트 구조
1) node_modules
- 앱 개발과 배포에 필요한 npm 패키지들이 저장됨
2) public
- 배포 버전을 빌드할 때 필요한 파일
- 웹팩을 이용해서 이 파일을 로드한 뒤 설정을 추가해서 빌드 버전이 완성됨
- public/index.html
- 최종 화면에 보여줄 index.html파일의 template
-
영역에 컴파일된 Vue 컴포넌트가 삽입됨
- <%=BASE_URL%>
- 웹팩의 내용을 참조하는 것
- 변경이 필요하면 프로젝트 root 경로에 vue.config.js 파일을 만들고 내용 수정 가능
3) src
- 컴포넌트들을 구성하는 영역으로 Vue가 컴파일하는 대상
- assests는 image와 같은 자원들이 저장되는 곳으로 component에서 이곳의 파일들을 사용하는 경우 자동으로 배포됨
- src/main.js
- 최종적으로 Vue 컴포넌트들을 조합해서 배포시 index.html에 표시됨
import Vue from "Vue"; // 핵심 객체 Vue import import App from "./App.vue"; // 개발하려는 SFC 첫 화면인 App.vue를 import Vue.config.productionTip = true; // Vue 앱이 처음 실행될 때 나오는 경고문을 출력한 것인지 물어봄 (상용인 경우 false로 지정) new Vue({ // id가 app인 html 태그에 App.vue 내용을 표시하는 문장 render: (h) => h(App), }).$mount("#app");
4) package.json
- 프로젝트에 대한 전반적인 설정 저장
- node.js를 이용해서 개발을 하다 보면 node_modules 경로에 다운로드 받은 많은 라이브러리들이 저장됨
- 개발 배포시 많은 용량이라 실제 배포할 때는 node_moduls의 내용을 번들링(묶어서) 배포함
- 배포 후 그것을 사용하려면 package.json이 있는 폴터에서 npm install 명령을 이용하면 관련 dependency가 한꺼번에 다운로드됨
📕 axios 라이브러리
1. axios
1) axios 개념
- Ajax 처리를 위한 비동기 처리 라이브러리
2. 기본 API
- ajax를 편리하게 처리할 수 있도록 다양한 API 제공
- 종류
- axios.request(config)
- axios.get(url [,config])
- axios.delete(url [,config])
- axios.head(url [,config])
- axios.options(url [,config])
- axios.post(url, data [,config])
- axios.put(url, [,data ,config])
- axios.patch(url, [,data ,config])
3. Config 구성요소
- axios 함수에 전달하는 config 객체 속성
let axios = axios({ url: "./food.json", // 호출할 서버의 경로 method: "get", // 사용하는 http method (post/get/put/delete), default는 get params: { name: "hong" }, // url (쿼리스트링을 구성하는 파라미터 요소) data: { age: 10, addr: "seoul" }, // request body를 통해서 서버로 전송되는 값 (post, put, patch에서 사용) });
- 요청에 대한 응답 결과는 then과 catch 콜백함수로 처리
axios.then( success_callback ).catch( error_callback ).finally( finally_callback ); { // 서버가 출력한 값은 언제나 data 속성 안에 존재함 data: {}, // HTTP status code status: 200, // HTTP status message from the server response statusText: 'OK', // 'headers' the headers that the server responsed with All header names are lower cased headers: {}, // 'config' is the config that was provided to 'axios' for the request config: {} }
📕 Vuex
1. Vuex 개념
- Vue의 상태관리 라이브러리
- 여러 컴포넌트간 공유 데이터가 있을 때 사용함
- 그냥 컴포넌트가 자신의 데이터를 가지고 사용할 때는 사용할 필요가 없음
- 기존 상태 관리의 문제점
- A 컴포넌트에서 B 컴포넌트로 값을 전달하려면 2번의 이벤트와 1번의 props 사용이 필요함
- A 컴포넌트에서 B 컴포넌트로 값을 전달하려면 2번의 이벤트와 1번의 props 사용이 필요함
2. Vuex 라이브러리
- 어플리케이션마다 하나의 저장소만을 사용
- 추적 및 디버깅 용이
- 컴포넌트들에서 상태 정보를 안전하게 접근 가능
- 컴포넌트가 공유하는 상태 데이터는 전역에서 저장소 객체(store)를 통해서 관리
- props를 이용한 전달, 이벤트 호출 필요X
- 저장소는 컴포넌트의 data와 같아서 컴포넌트와 저장소 객체의 데이터 연결하기 위한 작업만 필요
- 컴포넌트와 Vuex와의 관계
- 컴포넌트에서 어떤 데이터를 변경하려면 Vuex의 store와 통신함
- 컴포넌트에서 저장소에 변경을 반영하려면 commit을 호출하면서 mutation을 부름
- mutation은 state를 변경시킴
- state 변경은 component에 반영됨
- 만약 비동기로 처리할 일이 있다면 actions을 통해서 처리함
3. Vuex 사용 방법
1) Vuex 설치
npm install vuex --save
2) Vuex 사용
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
3) 프로젝트 생성
vue create vuex_counter
npm install vuex --save
4. Vuex 활용
1) Vuex store 생성
- 모듈 시스템에서 Vuex의 store를 만들 때 Vue.use()를 이용해서 Vuex 반드시 추가
- Vuex.store는 반드시 state와 mutations 속성을 가짐
- state는 객체 형태로 공유 데이터를 선언
- mutations에는 state의 데이터를 변경하는 함수를 정의
- 함수는 컴포넌트들이 $store.commit을 호출하면 실행되는 콜백 함수
- 함수의 파라미터로는 2개가 전달되는데, 하나는 store 객체이고 다른 하나는 변경할 값
mutatios: { methods(state, payload) { // payload에 전달된 값을 이용해서 state 수정함 } }
- increment나 decrement는 별도의 파라미터를 받지 않지만, set의 경우 payload를 받아서 state의 counter에 할당함
2) Vue 객체에 store 속성 추가
- Vuex.store를 추가할 때는 store 속성을 이용하여 다른 컴포넌트에 주입함
- 다른 컴포넌트에서 필요하다면 this.$store로 사용 가능
import Vue from "vue"; import App from "./App.vue"; Vue.config.productionsTip = false; import store from "./js/myVueXStore"; new Vue({ // store 속성에 Vuex.store 등록함 // Component에서 store가 필요하면 this.$store store, render: (h) => h(App), }).$mount("#App");
3) Counter.vue 생성
- Vuex.store에 등록된 공유 데이터는 반드시 Vuex 통해서 관리 해야지 직접적으로 값 변경하면 X
- 공유 데이터를 가져올 때는 computed(읽기 속성)으로 가져오고, 값을 반영해야할 경우 this.$store.commit를 이용
- 비 공유 데이터인 newValue는 data에서 관리하므로 직접 set/get 가능함
4) helper method 사용
- 기존의 Counter.vue를 mapState와 mapMutations를 이용하는 helper 버전으로 변경
- 만약 선언된 mutations 함수 이름과 component에 선언한 함수의 이름이 다른 경우에는 명시적으로 지정할 수 있음
...mapMutations({ increment: "increment", // 사용할 함수 이름 : mutation 함수 이름 decrement: "decrement", setval: "serVal", })
- 이름 재지정은 mapState나 mapActions, mapGetters에도 동일하게 적용됨
...mapState({ storecounter: "counter", asyncounter: "asnccounter" ]),
5) 비동기 호출시 공통 데이터 관리
- actions 함수 작성
- 첫 번째 파라미터가 state가 아니라 Vuex.store임
- store 객체를 통해 state와 mutations 등 다른 actions 메서드들도 호출 가능
const state = new Vuex.Store({ state: {. . .}, mutations: { mutation_method(state, payload) { // }, }, actions : { action_method(state, payload) { // store를 통해 state mutation은 물론 다른 action 사용 가능 } } })
- actions 함수 호출
- commit 대신 dispatch 메서드 사용
import {mapState} from "vuex"; import Constant from "../js/Constant"; export default { data() { return { date: "" }; }, computed: { ...mapState(["movielist"]) // 저장소의 이름과 동일하게 자동 연결 }, methods: { [Constant.BoxOFFICE](payload) { this.$store.dispatch(Constant.BOXOFFICE, payload); // 호출 } } };
6) getters
- 조회 용도 (일종의 전처리)
- 특정 조건 조회를 위한 코드가 여러 컴포넌트에서 반복되면 중복 코드로 인해서 유지 보수성이 떨어짐
- gettets는 store에 존재하며 state의 내용 중 필요한 것만 또는 필요한 형태로 참조할 수 있음
- 컴포넌트(boxOfficeComponent)에서 호출할 때는 store의 getters를 통해서 호출
export default { computed: { // ...mapState(["movielist"]) // 저장소의 이름과 동일하게 자동 연결 movielist() { return this.$store.getters.under; } } }
728x90
반응형