MVVM 패턴의 ViewModel 레이어에 해당하는 화면단 라이브러리
- 데이터 바인딩과 화면 단위를 컴포넌트 형태로 제공하며, 관련 API 를 지원하는데에 궁극적인 목적이 있음
- Angular에서 지원하는 양방향 데이터 바인딩을 동일하게 제공
- 하지만 컴포넌트 간 통신의 기본 골격은 React의 단방향 데이터 흐름(부모 -> 자식)을 사용
- 다른 프런트엔드 프레임워크(Angular, React)와 비교했을 때 상대적으로 가볍고 빠름.
Backend 로직과 Client 의 마크업 & 데이터 표현단을 분리하기 위한 구조로 전통적인 MVC 패턴의 방식에서 기인하였다. 간단하게 생각해서 화면 앞단의 화면 동작 관련 로직과 뒷단의 DB 데이터 처리 및 서버 로직을 분리하고, 뒷단에서 넘어온 데이터를 Model
에 담아 View
로 넘겨준다.
Vue.js 시작하기
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
Vue를 사용할 때 필수로 생성이 필요한 코드
인스턴스 생성 시 Vue 개발자 도구에서 Root 컴포넌트로 인식
new Vue({ // instance option properties template: "", el: "#app", data: {} methods: {} created: function() {} });
el
: app이라는 ID를 가진 태그를 찾아서 인스턴스를 붙여준다. 태그에 인스턴스를 붙여주면 view의 기능과 속성을 조작 가능data
: 뷰의 반응성(Reactivity)이 반영된 데이터 속성template
: 화면에 표시할 요소 (HTML, CSS 등)methods
: 화면의 동작과 이벤트 로직을 제어하는 메서드created
: 뷰의 라이프 사이클과 관련된 속성watch
: data에서 정의한 속성이 변화했을 때 추가 동작을 수행할 수 있게 정의하는 속성
화면의 영역을 영역별로 구분해서 코드로 관리
- 화면의 영역을 구분하여 개발할 수 있는 뷰의 기능
- 코드의 재사용성이 올라가고 빠른 화면 제작 가능
전역 컴포넌트
는 기본적으로 모든 인스턴스에 등록이 되어 있음지역 컴포넌트
는 인스턴스마다 새로 생성이 필요- 다만 서비스를 구현할 때는 하나의 인스턴스에 컴포넌트를 붙여 나가는 형식으로 진행
예시<h1>component 사용예제</h1> <div id="app"> <h2>친구 목록 입니다.</h2> <friends-component v-bind:friends="friends"></friends-component> <h2>동물 친구 목록 입니다.</h2> <friends-component v-bind:friends="friends2"></friends-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let app = new Vue({ el : "#app", data : { friends : ["김구라", "해골", "원숭이"], friends2: ["강아지", "고양이", "두더지"] }, components: { "friends-component": { template: ` <ul> <li v-for="item in friends">{{ item }}</li> </ul> `, props : ["friends"] } } }); </script>
컴포넌트 간에 데이터를 전달할 수 있는 컴포넌트 통신 방법
상위 컴포넌트에서 하위 컴포넌트로 내려보내는 데이터 속성
상위 컴포넌트의 템플릿
<!-- 상위 컴포넌트 --> <div id="app"> <!-- 하위 컴포넌트에 상위 컴포넌트가 갖고 있는 값을 전달함 --> <child-component v-bind:프롭스 속성 명="상위 컴포넌트의 data 속성"/> </div>
하위 컴포넌트의 컴포넌트
var childComponent = { props: ['프롭스 속성 명'] }
예 시
<body> <div id="app"> <!-- <app-header v-bind:프롭스 속성 이름="상위 컴포넌트의 데이터 이름"></app-header> --> <app-header v-bind:propsdata="message"></app-header> <app-content v-bind:propsdata="num"></app-content> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var appHeader = { template: '<h1>{{ propsdata }}</h1>', props: ['propsdata'] // 하위 컴포넌트의 프롭스 속성명 } var appContent = { template: '<div>{{ propsdata }}</div>', props: ['propsdata'] // 하위 컴포넌트의 프롭스 속성명 } new Vue({ el: '#app', components: { 'app-header': appHeader, 'app-content': appContent }, data: { message: 'hi', num: 10 } }) </script> </body>
같은 레벨의 컴포넌트 간 통신
동일한 상위 컴포넌트를 가진 하위 컴포넌트들 간의 통신은 아래와 같이 해야 한다.
Child(하위)
->Parent(상위)
->Children(하위 2개)
📢 컴포넌트 간의 직접적인 통신은 불가능하도록 되어 있는게 Vue 의 기본 구조
상위 ↔ 하위 관계가 아닌 컴포넌트 간의 통신을 위해 Event Bus를 활용할 수 있다.
Event Bus를 사용하기 위해 새로운 뷰 인스턴스를 아래와 같이 생성한다.
// 화면 개발을 위한 인스턴스와 다른 별도의 인스턴스를 생성하여 활용 var eventBus = new Vue(); new Vue({ // ... });
이벤트를 발생시킬 컴포넌트에서
$emit()
호출eventBus.$emit("refresh", 10);
이벤트를 받을 컴포넌트에서 $on() 이벤트 수신
// 이벤트 버스 이벤트는 일반적으로 라이프 사이클 함수에서 수신 new Vue({ created: function() { eventBus.$on("refresh", function(data) { console.log(data); // 10 }); } });
만약, eventBus의 콜백 함수 안에서 해당 컴포넌트의 메서드를 참고하려면 vm 사용
new Vue({ methods: { callAnyMethod() { // ... } }, created() { var vm = this; eventBus.$on("refresh", function(data) { console.log(this); // 여기서의 this는 이벤트 버스용 인스턴스를 가리킴 vm.callAnyMethod(); // vm은 현재 인스턴스를 가리킴 }); } });
사용 예시 1
<body> <h1>자식 component 에서 발생하는 이벤트</h1> <div id="app"> <my-component v-on:mom="feed"></my-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component("my-component", { template: ` <div> <button v-on:click="callMom">엄마!</button> </div> `, methods : { callMom() { // this.$emit("이벤트명", 전달할 data) this.$emit("mom", "배고파"); } } }); let app = new Vue({ el : "#app", methods: { feed(data) { alert(data); } } }); </script> </body>
사용 예시 2
<body> <h1>event emit 예제</h1> <div id="app"> <!-- list 라는 props "delete" 이벤트, "update" 이벤트가 특정 시점에 발생한다. --> <friend-component v-bind:list="members" v-on:delete="deleteMember" v-on:update="updateMember"></friend-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component("friend-component", { template: ` <ul> <li v-for="(item, index) in list"> {{ item }} <button v-on:click="updateItem(index)">수정</button> <button v-on:click="deleteItem(index)">삭제</button> </li> </ul> `, props : ["list"], methods : { deleteItem(i) { this.$emit("delete", i); }, updateItem(i) { const newName = prompt("수정할 이름을 입력하세요"); //this.$emit("update", {i:i, newName:newName}); this.$emit("update", {i, newName}); } } }); let app = new Vue({ el : "#app", data : { members: ['김구라', '해골', '원숭이'] }, methods: { deleteMember(index) { //push 또는 splice 하면 배열의 방의 사이즈가 변경이 되기 때문에 UI 업데이트가 자동으로 된다. //this.members.splice(index, 1); //{ } 없이 return 예약어 없이 리턴할 값만 작성해도 된다. //새로운 배열의 참조값으로 덮어쓰면 모델이 변경된 것이기 때문에 UI 업데이트가 자동으로 된다. this.members = this.members.filter((item, i) => index != i); }, updateMember(data) { //아래처럼 배열을 변경하면 변경이 감지가 안되기 때문에 화면 업데이트가 안된다. //this.members[data.i] = data.newName; //아래의 2가지 방법중 하나로 배열을 변경해야 한다. //Vue.set(this.members, data.i, data.newName); // $set(수정할 배열의 참조 값, 수정할 인덱스, 수정할 값) //this.$set(this.members, data.i, data.newName); // this.members = 아이템이 수정된 새로운 배열의 참조값 //item 이 수정된 새로운 배열의 참조값을 얻어내서 모델을 변경해도 화면 update 가 일어난다. this.members = this.members.map(() => { //만일 수정할 index 라면 if (index == data.i) { //수정할 데이터를 리턴해준다. return data.newName; } return item; }); } } }); </script> </body>
사용자 입력 폼 예시
<template> <!-- button의 submit 동작 시 submitForm 메서드 호출--> <form v-on:submit.prevent="submitForm"> <div> <label for="username">id: </label> <input id="username" type="text" v-model="username"> </div> <div> <label for="password">pw: </label> <input id="password" type="password" v-model="password"> </div> <button type="submit">login</button> </form> </template> <script> import axios from 'axios'; export default { data: function() { return { username: '', password: '', } }, methods: { submitForm: function() { // event.preventDefault(); -> form 의 새로고침/이동 동작을 막기 위해 사용했던 것을 v-on:submit.prevent 로 해결 var url = ''; var data = { username: this.username, password: this.password } axios.post(url, data) .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); }); } } } </script>
뷰에서 권고하는 HTTP 통신 라이브러리이다.
Promise
기반(JS 비동기 처리 패턴)이며 상대적으로 다른 HTTP
통신 라이브러리들에 비해 문서화가 잘되어 있고 API
가 다양하다
Install Axios
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
사용 예시
<body> <div id="app"> <button v-on:click="getData">get user</button> <div> {{ users }} </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> new Vue({ el: '#app', data: { users: [] }, methods: { getData: function() { var vm = this; axios.get('https://jsonplaceholder.typicode.com/users/') .then(function(response) { console.log(response.data); vm.users = response.data; }) .catch(function(error) { console.log(error); }); } } }) </script> </body>
fetch 를 이용한 json 요청
<body> <h1>ajax 요청을 통해서 받아온 데이터 사용하기</h1> <p>페이지 전환 없이 서버에 요청하는 것을 ajax 라고 생각하면 된다.</p> <div id="app"> <br> <table> <thead> <tr> <th>번호</th> <th>작성자</th> <th>제목</th> </tr> </thead> <tbody> <!-- 글번호는 pk 이기 때문에 --> <tr v-bind:key="tmp.num" v-for="tmp in list"> <td>{{tmp.num}}</td> <td>{{tmp.writer}}</td> <td>{{tmp.title}}</td> </tr> </tbody> </table> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> new Vue({ el : "#app", data: { list: [], }, created() { //Vue 가 준비가 되었을 때 (root component 가 준비되었을 때) 최초 한 번 호출된다. //fetch 를 이용해서 서버에 데이터를 요청한다. fetch("../cafe/json_list.jsp") .then(res => res.json()) .then(data => { //data 는 글목록이 들어 있는 배열이다. this.list = data; }); }, methods: {}, }) </script> </body>