IntelliJ 스프링부트 그래들로 생성하기

이번에는 IntelliJ IDE에서 Spring Boot Project를 Gradle로 생성하는 방법을 설명해보겠습니다. IntelliJ가 처음이신 분들에게 도움이 되실꺼라 생각이 되면서 저도 잊어먹지 않기 위해(정말 간단하지만..) 글로 남겨 놓을려고 합니다.





1) Create New Project 클릭





2) Spring Initializr 클릭




3) Project Metadata 설정 (Type에서 Gralde Project 선택)





4) Gradle에 추가할 디펜던시 선택







5) 임포트 설정 Gradle 




6) Gradle Build 중..



끝~

Vuex란?

Vuex는 Vue.js 애플리케이션에서 상태 관리 패턴을 지원하는 라이브러리이다. 애플리케이션 내부의 모든 컴포넌트들이 공유하는 집중화된 상태 정보 저장소 역할을 하며 상태 변경을 투명하게 할 수 있다. 각 컴포넌트가 공유하는 상태 데이터는 전역에서 저장소(store)객체를 통해서 관리한다. 이와 같은 방식으로 자식으로 또 그 자식으로 props를 이용해 속성을 계속해서 전달하지 않아도 되고 상태 데이터를 변경하기 위해 부모 콤포넌트로 이벤트를 발생시키지 않아도 된다. 


Vuex를 사용하는 이유

1. 중앙 집중화된 상태 정보 관리가 필요할때

2. 상태 정보가 변경되는 상황과 시간을 추적하고 싶을때

3. 컴포넌트에서 상태 정보를 안전하게 접근하고 싶을때

위의 그림을 보면 화살표가 한방향으로만 흘러간다. "단방향 데이터 흐름"이라는 용어를 쓰는데 전체적인 처리 흐름은 다음과 같다.


1) 컴포넌트가 액션을 일으킨다.(버튼 클릭 이벤트 같은)

2) 액션에서는 외부 API를 호출한 뒤 그 결과를 이용해 변이(mutation)을 일으킨다.

3) 변이에서는 액션의 결과를 받아 상태를 설정한다. 이단계는 추적할 수 있기 때문에 vue.js Dev Tool을 이용하면 상태 변경의 내역을 모두 확인할 수 있다.

4) 변이에 의해 변경된 상태는 다시 컴포넌트에 바인딩되어 화면을 갱신한다.


위의 그림에서 점선으로 표시된 영역이 Vuex 저장소(store) 객체 영역이다. 저장소가 상태(State:데이터), 변이(Mutation), 액션(Action)을 모두 관리한다. 저장소는 애플리케이션의 상태를 중앙집중화하여 관리한는 컨테이너이며 일반적인 전역 객체와는 달리 저장소의 상태를 직접 변경하지 않는다. 반드시 변이를 통해서반 변경한다.


주의할점은 변이의 목적은 상태의 변경이라는 것이다. 상태의 변경과 관련이 없는 작업이 변이 내부에서 수행되지 않도록 해야한다. 또한 변이는 동기적인 작업이다. 비동기 처리는 변이를 통해서 수행되지 않는다. 변이 내부에서 비동기적 처리를 수행해도 애플리케이션의 기능은정상적으로 작동될 수 있지만 사후 스냅샷을 캡쳐한 후에 나중에 상태가 변경되기 때문에 변이에 의해 데이터가 어떻게 변경되었는지를 추적할 수 없게 된다.





상태와 변이

상태(state)와 변이(muation)은 Vues 저장소(store) 내부의 핵심 요소이다. 상태는 애플리케이션에서 관리해야 할 중요한 데이터이며, 변이는 상태를 변경하는 함수들을 보유하고 있는 객체이다. 전역에서 Vue.user(Vuex) 코드의 실행으로 애플리케이션 내부의 모든 컴포넌트가 저장소의 상태, 변이 객체에 접근할 수 있다. 반드시 변이를 통해서반 변경되도록 해야한다. 



Vuex 라이브러리 다운로드

yarn add vuex 

OR 

npm install -save vuex



export default {
ADD_TODO: "addTodo",
DONE_TOGGLE: "doneToggle",
DELETE_TODO: "deleteTodo"
}

[src/Constant.js]




import Vue from 'vue';
import Vuex from 'vuex';
import Constant from '../Constant'
Vue.use(Vuex)

const store = new Vuex.Store({
state: {
todolist: [
{ todo: "영화보기", done: false },
{ todo: "주말 산책", done: true },
{ todo: "ES6 학습", done: false },
{ todo: "주말 야구장", done: false },
]
},
mutations: {
[Constant.ADD_TODO]: (state, payload) => {
if (payload.todo !== "") {
state.todolist.push({ todo: payload.todo, done: false })
}
},
[Constant.DONE_TOGGLE]: (state, payload) => {
state.todolist[payload.index].done =
!state.todolist[payload.index].done
},
[Constant.DELETE_TODO]: (state, payload) => {
if (payload.todo !== "") {
state.todolist.splice(payload.index, 1)
}
}
}
})

export default store;

[src/store/index.js]


Vuex를 전역에서 사용할 수 있도록 4행의 Vue.use(Vuex) 코드를 미리 작성해야한다. 또한 state, mutation 정보를 전달하여 Vuex.Store 객체를 생성한다. 이 예제에서는 List 컴포넌트가 보유하던 상태 데이터를 저장소가 관리하도록 했다.


"모든 컴포넌트의 상태 데이터를 Vuex로 관리할 필요는 없다"


하나의 컴포넌트 내부에서만 사용되는 상태이거나 중요하지 않은 상태이면 Vuex 저장소에 관리할 필요는 없다. Vuex가 유용한 경우는 여러 컴포넌트가 상태 데이터를 공유하는 경우이다.


변이(mutation) 객체의 메소드들은 첫번째 인자가 상태(state)이다. 두번째 인자 payload는 변이에 필요로 하는 데이터 타입이다. addTodo 작업의 경우는 todolist 데이터에 새로운 todo를 추가하기 때문에 두번재 인자 payload를 통해서 todo를 전달한다. 만약 변이를 일으킬 때 필요한 인자가 여러 개라면 payload를 객체 형태로 전달하면 된다.


이제 src/main.js를 수정해서 vue인스턴스를 생성할 때 store 객체를 전달한다. 아래의 코드를 작성하면 각 자식 컴포넌트에서 저장소(Store) 객체를 this.$store으로 접근할 수 있게된다.


import Vue from 'vue'
import TodoList from './components/TodoList.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
store,
render: h => h(TodoList)
}).$mount('#app')

[src/main.js]


이렇게 작성하면 Vuex를 사용하는 기본적인 셋팅은 끝났다. 기존에 작성한 todolist 의 list.vue 파일을 vuex를 이용하는 방법을 아래와 같이 수정하겠다.



<script tpye="text/javascript">
import Constant from '../Constant'

export default {
name: 'List',
computed: {
todolist() {
return this.$store,state.todolist
}
},
methods: {
checked: function(done) {
if (done) return {
checked: true
};
else return {
checked: false
}
},
doneToggle: function(id) {
this.$store.commit(Constant.DONE_TOGGLE, {id:id})
},
delete: function(id) {
this.$store.commit(Constant.DELETE_TODO, {id:id})
}
}
}
</script>


위의 소스를보면 List.vue 파일에는 로컬 데이터가 없다. 저장소(store) 객체가 Vue 인스턴스에 주입되었기 때문에 this.$store.state와 같이 저장소의 상태(state)에 접근할 수 있다. 이것을 속성처럼 이용할 수 있도록 하기 위해 계산형 속성(Computed Property)를 사용한다. 


화면에서 일어나는 이벤트를 받아 처리하는 메서드에서 변이(mutation)을 일으키기 위해서 this.$store.commit() 메서드를 호출한다. this.$store.commit() 메서드의 첫 번째 인자는 변이의 이름이다. 변이에 전달할 인자는 payload 인자를 이용하면 된다. 변이의 이름은 단순한 문자열이기 때문에 오타를 내기 쉽다. 그래서 위에 작성한 Constant.js와 같은 상수를 만들어 사용할 것을 권장하는 것이다.




헬퍼 메서드

이전 까지는 계산형 속성으로 this.$store.state를 직접 리턴하는 코드를 작성했고, 메서드에서도 변이를 직접 커밋하는 코드를 작성했는데 이와 같은 방법은 조금 불편한다.


이러한 불편함을 덜어주기 위해 mapState, mapMutations와 같은 컴포넌트 바인딩 헬퍼 메서드를 제공한다. 이 밖에도 mapGetters, mapActions 메서드도 있다.


<template>
<ul id="todolist">
<li v-for="a in todolist" :key="a.id" :class="checked(a.done)"
@click="doneToggle(a.id)">
<span>{{a.todo}}</span>
<span v-if="a.done">(완료)</span>
<span class="close" @click.stop="deleteTodo({id:a.id})">
&#x00D7;</span>
</li>
</ul>
</template>
<script tpye="text/javascript">
import Constant from '../Constant'
import { mapState, mapMuations } from 'vuex'

export default {
name: 'List',
computed: mapState(['todolist']),
methods: {
checked: function(done) {
if (done) return {
checked: true
};
else return {
checked: false
}
},
...mapMuations([
Constant.DELETE_TODO,
Constant.DONE.TOGGLE
])
}
}
</script>



Getter

게터는 저장소 수주느이 계산형 속성이라고 말할 수 있다. 컴포넌트에서 계산형 속성이 필수가 아니듯 게터 또한 저장소 내에서 필수로 사용하는 것ㅇ느 아니다. 하지만 적절하게 사용하면 컴포넌트에서 코드의 작성이 편리해진다. 












Action

'Vue.js' 카테고리의 다른 글

[Vue.js] axios 서버통신  (2) 2018.12.22
[Vue.js] Vue CLI(GUI)  (0) 2018.12.18
[Vue.js] Component  (0) 2018.12.18
[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16

axios를 이용한 서버통신

서버와 통신하기 위한 라이브러리는 fetch, superagent, axios 등이 있으며 Vue.js 플러그인으로 개발된 vue-resource라는것도 존재하나 Vue.js 창시자인 에반 유는 axios를 사용할 것을 권장하고 있다고 한다.


서비스 API

S네트워크 사용에 제약이 따를 경우 로컬에서 실행할 수 있는 API코드를 다운로드하여 실행하는 연락처 서비스 API이다.


다운로드 >> https://github.com/STEPANOWON/CONTACTSVC



[저수준 API]

axios(config)

axios(url, config]


[각 메서드별 별칭]

axios.get(url[, config])

axios.delete(url[, config])

axios.psot(url[, data[, config])

axios.put(url[, data[, config])

axios.head(url[, config])

axios.options(url[, config])



axios 사용법


yarn add axios 


Or


npm install --save axios






axios 프로젝트 생성



1) vue cli로 프로젝트 생성

vue create contactsapp

2) axios 추가 

yarn add axios OR npm install --save -axios

3) http proxy 설정

Vue CLI가 생성하는 프로젝트 템플릿 코드에서는 약간의 설정 파일만 작성하면 웹팩 개발서버를 이용해 프로시 서버 기능을 사용할 수 있다. 프로젝트 최상위 디렉토리에 vue.config.js파일을 생성하고 아래의 코드를 작성한다.

module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localshot:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}


이렇게 하면 개발용 서버에 /api/contacts를 요청하게되면 http://localshot:3000/contacts로 요청일 전달 도니다.  만약 위의 서비스 API 서버를 로컬에서 실행하지 않는다면 위의 target 값을 http://sample.bmaster.kro.kr 으로 지정하면 된다.




<template>
<div id="app">
<div class="container">
<div class="form-group">
<button @click="fetchContacts">1페이지 연락처 조회</button>
</div>
<div class="form-group">
<input type="text" v-model="name"
placeholder="이름을 입력합니다"/>
<input type="text" v-model="tel"
placeholder="전화번호를 입력합니다"/>
<input type="text" v-model="address"
placeholder="주소 입력합니다"/>
<button @click="addContact">연락처 1건 추가</button>
</div>
<div class="form-group">
<input type="text" v-model="no"/>
<button @click="fetchContactOne">연락처 1건 조회</button>
</div>
<div class="form-group">
<input type="text" v-model="no"/>
<input type="text" v-model="name"
placeholder="이름을 입력합니다"/>
<input type="text" v-model="tel"
placeholder="전화번호를 입력합니다"/>
<input type="text" v-model="address"
placeholder="주소 입력합니다"/>
<button @click="updateContact">수정</button>
</div>
<div class="form-group">
<input type="text" v-model="no"/>
<input type="file" ref="photofile" name="photo"/>
<button @click="changePhoto">파일 변경</button>
</div>
</div>
<span>JSON 출력</span>
<div id="result" class="container">
<xmp> {{ result }} </xmp>
</div>
</div>
</template>

<script>
import axios from 'axios';

export default {
name : "app",
data() {
return {
no: 0,
name: '',
tel: '',
address: '',
result: null
}
},
methods : {
fetchContacts : function() {

},

addContact : function() {

},

fetchContactOne : function() {

},

updateContact : function() {

},

deleteContact : function() {

},

changePhoto : function() {

}
}
}
</script>

<style>
@import url("http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.css");
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osz-font-smoothing: grayscale;
text-align: center;
color : #2c3e50;
margin-top : 60px;
}

.container {
border: solid 1px gray;
padding: 10px;
margin-bottom: 10px;
text-align: left;
}

#result {
text-align: left;
padding: 20px;
border: solid 1px black;
}

.form-group {
border: dashed 1px gray;
padding: 5px 5px 5px 20px;
}
</style>

[src/AppAxiosTest.vue]




import Vue from 'vue'
//import App from './App.vue'
import App from './AppAxiosTest.vue'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
}).$mount('#app')

[src/main.js]




axios 요청 방법

axios 저수준 API를 이용하는 예제이다. 아래의 코드를 fetchContact메서드에 작성한다.


fetchContacts : function() {
axios({
method: 'GET',
url : '/api/contacts',
params : {
page : 1,
pagesize : 5
}
}).then((response) => {
console.log(response);
this.result = response.data;
}).catch((ex)=> {
console.log("ERR!!!!! : ", ex)
})
},

[ 저수준 axios 메소드 작성법 ]



axios 저수준 메서드의 특징은 모든 전달값을 config 객체로 전달한다는 특징이 있다. Promise 객체는 요청이 성공적이라면 then이 호출되며 요청이 실패하면 catch가 호출된다. 저수준 메서드가 아닌 별칭 메서드 get을 이용하여 fetchContactOne을 작성하겠다.



fetchContactOne : function() {
axios.get('/api/contacts/' + this.no)
.then((response) => {
console.warn(response);
this.result = response.data
})
},

[GET]



POST 메서드에서는 주로 axios.post(url, data, config) 형태를 주로 사용한다. 다음은 addContact 에 POST를 이용해 작성해본 코드이다.



addContact : function() {
axios.post('/api/contacts',
{ name:this.name, tel:this.tel, address:this.address }
).then(response => {
console.warn(response)
this.result = response.data
this.no = response.data.no
}).catch((ex) => {
console.warn("ERROR!!!!! : ",ex)
})
},

[POST]



updateContact : function() {
axios.put('/api/contacts/' + this.no,
{ name:this.name, tel:this.tel, address:this.address }
).then(response => {
console.warn(response)
this.name = '';
this.tel = '';
this.address = '';
this.result = response.data
}).catch((ex) => {
console.warn("ERROR!!!!! : ",ex)
})
},

[PUT]



deleteContact : function() {
axios.delete('/api/contacts/' + this.no)
.then(response => {
console.warn(response)
this.result = response.data
}).catch((ex) => {
console.warn("ERROR!!!!! : ",ex)
})
},

[DELETE]



파일 업로드 기능도 axios로 구현하기 위해서는 위에 작성한 <input tpye="file" ../> 필드를 직접 참조해야한다. 해당 태그에 보면 ref="photofile" 이라고 ref 옵션을 사용한걸 볼 수 있다. 


changePhoto : function() {
var data = new FormData();
var file = this.$ref.photofile.files[0]
data.append('photo', file)

axios.post('/api/contacts' + this.no + '/photo', data)
.then(response => {
console.warn(response)
this.result = response.data
this.no = response.data.no
}).catch((ex) => {
console.warn("ERROR!!!!! : ", ex)
})
}

[File 전송]



FormData 객체를 생성하고 this.$ref.photofile과 같이 ref 옵션을 이용해 파일 필드를 직접 참조할 수 있다. 이 필드의 값을 FormData 객체에 추가한뒤 서버로 요청하게 된다.




Vue Instance 내부에서 axios 이용하기

Vue 인스턴스 내부에서 axios를 이용하기 위해 Vue.prototype에 axios를 추가하면 간단하게 사용 할 수있다. main.js에 아래의 내용을 추가한다.


import Vue from 'vue'
//import App from './App.vue'
import App from './AppAxiosTest.vue'
import axios from '.axios'

Vue.prototype.$axios = axios;
Vue.config.productionTip = false

new Vue({
render: h => h(App),
}).$mount('#app')

[src/main.js]


이렇게 작상하면 Vue 인스턴스 내부에서는 axios를 따로 improt하지 않아도 this.$axios를 이용해서 사용할 수 있다. AppAxiosTest.vue 파일의 fetchContactone 메서드는 아래와 같이 변경할 수 있다.


fetchContactOne : function() {
this.$axios.get('/api/contacts/' + this.no)
.then((response) => {
this.result = response.data
})
},




axios 사용 시 주의 사항

axios를 사용하면서 then() 처리를 할 때는 ECMAScript6의 화살표 함수를 사용할 것을 권장한다. 데이터를 수신한 후에 Vue 인스턴스 내부의 데이터를 변경해야 하는 경우가 있는데 데이터 옵션을 액세스하기 위해서는 this 객체가 Vue인스턴스를 참조할 수 있어야 한다. then() 내부에서 화살표 함수를 하용하지 않으면 this가 vue 인스턴스를 참조하지 않기 때문에 밖에서 별도의 변수에 this를 할당한 후에 클로저 방식으로 접근해야 하는 불편함이 발생한다. 

















'Vue.js' 카테고리의 다른 글

[Vue.js] Vuex를 이용한 상태 관리 (store, mutation, getter)  (0) 2018.12.23
[Vue.js] Vue CLI(GUI)  (0) 2018.12.18
[Vue.js] Component  (0) 2018.12.18
[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16

Vue CLI

Vue CLI는 Vue.js 애플리케이션을 빠르게 개발할 수 있는  관련된 기능을 모두 제공하는 Vue.js 개발 도구이자 시스템이다. 개발자들에게 표준화된 개발의 기준선을 제공하고 정해진 틀 속에서 개발이 가능하면서도 기본 제공되거나 제 3자가 제공하는 다양한 플러그인과 프리셋을 로딩하여 프로젝트의 초기 설정과 개발을 손쉽게 할 수 있도록 도와준다. 따라서 개발자가 프로젝트 구성을 설정하는데 드는 노력을 줄이고 개발에 좀더 집중할 수 있도록한다.






1. Vue CLI 구성요소

1) CLI : @vue/cli

컴퓨터 내부 어디에서나 실행할 수 있도록 npm을 이용해 전역에 설치하며 터미널창에서 vue 명령어를 실행할 수있도록 한다. vue 명령어로 수행할 수 있는 기능은 다음과 같다.


- 새로운 Vue 애플리케이션 프로젝트를 생성할 수 있음

- vue 단일 파일 컴포넌트를 설정 없이 실행하여 테스트할 수 있음

- GUI 환경으로 프로젝트를 생성하거나 관리할 수 있음



2) CLI : @vue/cli-service

CLI 서비스는 프로젝트가 생성될 때 개발 의존성으로 설치되는 구성요소이다. CLI 서비스의 내부는 웹펙(webpack)과 웹팩 개발 서버(webpack-dev-server) 기반으로 작성되어 있다. CLI 서비스를 이용해 수행할 수 있는 기능은 다음과 같다


- 프로젝트를 웹팩 개발 서버 기반으로 구동 가능

- 프로젝트 소스코드를 리소스를 빌드하고 번들링할 수 있음

- 프로젝트의 코드를 테스트할 수 있음



3) CLI 플러그인

CLI 플러그인은 Vue CLI로 생성된 프로젝트 추가적인 기능을 제공하는 npm 패키지이다. CLI도구를 이용해 프로젝트를 생성할 때 추가할 플러그인을 선택할 수 있으며, 프로젝트가 생성된 이후에도 vue add 명령어를 이용해 플러그인을 추가할 수 있다. 또한 CLI 플러그인은 기본적으로 제공되는 것도 있지만 누구나 플러그인을 만들어 제공할 수 있다.


대표적인 CLI 플러그인 

router

vuex

vue-cli-plugin-vuetify



Vue CLI를 설치하기 위해서 npm 패키지 매니저를 이용해 전역 수준으로 설치한다.


npm install -g @vue/cli (windows)

sudo npm install -g @vue/cli (mac)





2. 프로젝트 생성과 기본 사용법

Vue CLI를 이용해 프로젝트를 생성할 때는 vue create 명령어를 사용한다.


vue create [프로젝트명]


vue create 명령어로 프로젝트를 생성할 때 몇 단계릐 절차를 거친다. 첫 단계는 프리셋을 포함하는 단계이다. default로 하면 babel, eslint 프러그인만 포함된다. 


(생성 화면)


프리셋과 플러그인을 선택하는 질문들이 나온다 알아서 셋팅해주고 마무리하면 된다.


(CLI로 프로젝트를 생성 완료한 모습)





생성한 프로젝트의 구조를 VSCode로 실행해보면 위의 사진과 같다.


src : 개발자가 작성하는 소스 코드를 배치하는 디렉터리

assets -> 여러가지 자원 정보들이 저장되는곳, 이 디렉터리에 저장한 파일을 Vue 캠포넌트에서 사용하는 경우 빌드 과정에서 자원으로 인식되어 배포버전을 만들어낼때 함께 배포된다.

components -> Vue 컴포넌트를 작성하기 위한 디렉터리. 하지만 꼭 Vue 컴포넌트가 이 디렉토리로만 들어가는것은 아니다.


public : 배포 버전을 빌드할 때 필요한 파일


node_modules : app 개발과 배포에 필요한 npm 패키지들이 저장되는 디렉터리


dist : 작성한 앱 코드를 빌드하여 만든 배포 버 저장하는 디렉터리. Vue 컴포넌트들은 모두 js 파일로 트랜스파일되어 몇 개의 js 파일로 번들링되고 난독화하여 저장한다.





vue-cli-service는 vue CLI 설치 시에 프로젝트 단위로 설치되느니 실행 명령어이다. vue-cli-service의 사용 방법은 다음과 같다.


vue-cli-service [command] [options]

command 

1) serve : 웹팩 개발 서버를 이용해 프로젝트 코드를 실행한다.실행 도중 소스코드가 변경되고 저장되면 즉시 브라우져에 화면이 반영된다.

2) build : 빌드하여 배포 버전의 소스 코드를 새엉하여 지정 디렉터리에 저장한다. 빌드된 버전의 코드가 저장되는 기본 경로는 dist 디렉터리이다.

3) lint : eslint 기능을 이용해 코드의 표준화되지 않은 부분을 검사하고 교정한다.

4) inspect 현재 프로젝트의 웹팩 설정 정보를 보여준다.


option

1) --open : 서버 시작시 브라우져가 같이 실행

2) --copy : 서버 시작시 URL 주소가 클립보드에 복사

3) --host : 호스트 주소 설정 (default : 0.0.0.0 localhost)

4) --port :  포트 설정 (default : 8080)

etc..


만일 개발 서버를 구동하면서 웹 브라우져를 자동으로 열고 싶다면 --open 옵션을 추가해주면된다. package.json 파일의 script 옵션에 server 값을 다음과 같이 변경하면 npm run serve만으로도 간단히 실행할 수 있다.

"name": "test1",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --open --port 3000",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},

(프로젝트 내부의 package.json의 script 부분)


이제 실행을 해보자. vs

VSCode의 터미널에 npm run serve 입력


(정상 실행된모습)

자동으로 웹브라우져가 실행되면서 결과가 나오면 성공적이다.




3. 플러그인

Vue CLI를 이용해 생성한 프로젝트는 크게 서비스와 플러그인으로 구성되어 있다. 서비스는 하나이지만 플러그인은 여러 개를 선택할 수있다. 대부분의 플러그인들의 이름은 @vue/cli-plugin으로 시작한다. 플러그인을 이용하면 프로젝트의 웹팩 구성을 변경하고 명령이나 기능을 추가한느 작업을 선택적으로 할 수 있다.


Vue.CLI의 기본 프리셋으로 프로젝트를 생성하면 @Vue/cli-plugin-babel, @Vue/cli-plugin-eslint의 두 가지 플러그인이 설치된다. 추가하고 싶은 플러그인 이있다면 다음의 명령어로 추가하면된다


vue add [플러그인]


채표적인 플러그인중 하나인 vuex, rotuer를 설치해보자 


vue add router



이제 다시 npm run serve를 실행하여 정상적으로 설치되었는지 확인해보자



home과 about 페이지가 정상적으로 동작한다. 




3. vue.config.js

vue CLI의 내부는 웹팩이라는 모듈 번들러 도구를 이용하도록 만들어져있다. 하지만 CLI 서비스는 모두 캡슐화되어 있기 때문에 내부의 웹펙에 대해 웹펙 설정 파일을 이용해 직접 설정할 수 없다. 대신 웹팩 설정을 위해 vue.config.js라는 파일을 프로젝트 내부에 작성한다.


나중에 axios를 이용한 http 통신을 할때 웹팩 개발 서버의 proxy를 설정하기 위해 여기를 수정할 것 이다.




4. Vue CLI GUI 도구

Vue CLI는 명령창이나 터미널 창을 이용해 프로젝트를 생성하고 관리할 수 있지만 브라우져를 통해

GUI 도구를 이용할 수 있다.


프로젝트를 생성하기 위한 기본 디렉터리를 하나 만들고 명령창이나 터미널을 열어 해당 디렉토리로 이동한다. 그리고 나서 vue ui 명령어를 실행하면 아래와 같은 화면이 뜰껏이다.


(VSCode 터미널에서 폴더 생성후 vue ui 실행한 모습)



(실행하면 웹브라우져가 구동된다)




(하단의 새 프로젝트를 만들어보세요 클릭)



(폴더와 프로젝트명 설정후 다음버튼 클릭)



(기본 디폴트 프리셋 선택후 프로젝트 만들기 클릭)


(Vue Project가 대쉬보드 형태로 확인할 수 있다. 작업 목록 클릭)





(Serve 선택후 시작을 누르면 애플리케이션이 구동된다)


스크린샷에서 보이는것처럼 단순히 실행만 할 수 있는게 아니라 각종 통계정보가 같이 나온다.








'Vue.js' 카테고리의 다른 글

[Vue.js] Vuex를 이용한 상태 관리 (store, mutation, getter)  (0) 2018.12.23
[Vue.js] axios 서버통신  (2) 2018.12.22
[Vue.js] Component  (0) 2018.12.18
[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16

Component 

대규모 어플리케이션인 경우 확장자가 .vue인 단일 파일 컴포넌트(SIngle File Component)형태로 많이 개발되지만 이번에는 컴포넌트 개념부터 정확히 잡아보려고 한다.


대규모 어플리케이션에서 사용되는 단일 파일 컴포넌트를 작성하기 위해서는 ECMAScript 6에 대한 지식이 있어야한다. 


컴포넌트의 장점은 아래와 같다.


1. 뛰어난 재사용성

한 애플리케이션에서 내부에 사용되는 UI와 기능은 반복되는 것들이 꽤 많다. 반복되는 부분을 컴포넌트로 작성해두면 여러 곳에서 재사용할 수 있어 생상선을 높일 수 있다.


2. 테스트가 용이하다.

컴포넌트 단위로 기능을 테스트할 수 있기 때문이다. Karma, Mocha와 같은 단위 테스트도구를 이용해 쉽게 테스트 할 수 있다.


3. 디버깅이 편하다.

Vue devtools와 같은 도구를 이용해 컴포넌트 단위로 전달된 속성(Props)을 손쉽게 살펴볼 수있고, 부모 컴포넌트로 전달된 이벤트 정보를 손쉽게 파악이 가능하다.




1. Component 조합

Vue.js는 컴포넌트들을 조합해 전체 애플리케이션을 작성한다. 컴포넌트들은 부모0자식 관계로 트리 구조를 형성하는데 부모 컴포넌트가 자식 컴포넌트를 포함하는 형태이다. 컴포넌트들은 속성(Props)을 통해서 자식 컴포넌트로 정보를 전달할 수 있다. 전달 방향은 주로 부모에게 자식(단방향), 양방향으로 데이터를 전달할 수 있지만 애플리케이션의 복잡도가 높아지고 유지 보수에 어려움이 있어 권장하지는 않는다.




부모 컴포넌트는 자식 컴포넌트로 속성을 전달할 수 있고, 자식 컴포넌트는 부모 컴포넌트로 이벤트를 발신할 수 있다. 속성 전달과 이벤트 발식이 부모-자식 컴포넌트 간의 상호 작용을 일으키는 방법이다.


data, methods, computed, watch 옵션과 같은 대부분의 Vue 인스턴스의 옵션을 컴포넌트 수준에서도 사용할 수 있다. 한가지 주의할 점은 data 옵션이다. 컴포넌트 기반으로 개발할 때 data 옵션은 각 컴포넌트의 로컬 상태를 관리하기 위한 용도로 사용한다. 또한 하나의 컴포넌트를 애플리케이션에서 여러 번 사용할 경우에 모두 다른 상태 정보를 가져야한다. 




2. Component 작성


컴포넌트를 작성하는 메서드는 다음과 같다.


Vue.component(tagname,option)

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

option : 컴포넌트에서 렌더링할 templet등을 지정


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


작성된 컴포넌트를 사용할 때에는 등록한 태그명을 사용한다. Vue 컴포넌트의 template 옵션에 템플릿 문자열을 사용했다. 이와 같은 방식을 인라인 템플릿이라고 하는데 권장 하는 방법은 아니다. 


<body>
<script tpye="text/javascript">
Vue.component('hello-component', {
template: '<div>hello world</div>'
})
</script>
<div id="app">
<hello-component></hello-component>
<hello-component></hello-component>
<hello-component></hello-component>
</div>
<script tpye="text/javascript">
var v = new Vue({
el: '#app'
})
</script>
</body>


이제 VSCode의 Plugin중 하나인 live-server로 핫리로딩이라는 기능을 통해 코드가 변경되어 저장되면 브라우저 화면에 즉시 갱신되도록 할 예정이다.


npm을 이용해 아래의 명령어로 설치한다


npm intsall -g live-server (windows)

sudo npm intsall -g live-server (mac)


그후 VSCode의 ctrl + `키를 눌러 터미널 실행후 아래의 명령어로 실행 


live-server [--port=포트명]


PS C:\vue2> live-server --port=8090
Serving "C:\vue2" at http://127.0.0.1:8090
Ready for changes
GET /favicon.ico 404 33.162 ms - 150
Change detected C:\vue2\index.html
Change detected C:\vue2\index.html
Change detected C:\vue2\event.html
Change detected C:\vue2\index.html


(실행후 dev tools 모습)



템플릿을 지정할 때 인라인 방식으로도 가능하지만 템플릿 문자열을 포함하고있는 <template> 태그, <script type="text/x-template"> 태그의 di를 지정해도 가능하다.




3. DOM Template 구문 작성시 주의사항

컴포넌트를 이용해 개발하면서 템플릿 문자열을 사용할 때 주의할 점이 있다. HTML요소들은 자식 요소로 포함시킬 수 있는 요소들이 정해져 잇는 경우가 있고, 이 사항을 브라우저가 구문 분석을 숳행한다. 이러한 경우에 Vue 컴포넌트가 사용되면 떄떄로 오류가 발생한다.


<body>
<script tpye="text/javascript">
Vue.component('option-component', {
template: '<option>hello</option>'
})
</script>
<div id="app">
<select>
<option-component></option-component>
<option-component></option-component>
</select>
</div>
<script tpye="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: '#app'
})
</script>
</body>


<select> 태그 안에서 <opton-component>라는 태그를 사용할수 있다라는 것이 브라우져에 등록되어 있지 않다. 그래서 브라우져는 이 태그들을 구문 분석하는 작업을 먼저 수행한 후 Vue 컴포넌트를 렌더링한다. 구문 분석단계에서 DOM 요소가 올바르지 않다고 판단하기에 제대로 렌더링하지 않는 문제가 발생한다.


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



요렇게 is특성을 사용하면 정상적으로 렌더링 되는걸 확인할 수있다. 하지만 .vue 확장자를 상용하는 단일 파일 컴포넌트를 작성하는 경우에는 굳이 is 특성을 사용하지 않아도 된다.


한가지 더 주의할 점은 템플릿 문자열에서의 루트 요소는 하나여야 한다는 것이다. 만일 템플리 내부에서 여러 요소를 작성해야 한다면 <div>로 감싸주어 하나의 루트 요소가 되게끔 해주어야한다.




4. Component Data 옵션

컴포넌트 내부의 로컬 상태 정보를 저장하기 위해 data옵션을 사용할 수 있다. 하지만 이제까지 작성했듯이 data 옵션에 객체를 직접 지정하면 컴포넌트가 정상적으로 렌더링되지 않고 오류가발생된다.


<template id='timeTemplate'>
<div>
<span>{{nowTS}}</span>
<button @click="timeClick">현재 시간</button>
</div>
</template>
<body>
<script tpye="text/javascript">
Vue.component('time-component', {
template: '#timeTemplate',
data: {
nowTS: 0
},
methods: {
timeClick: function(e) {
this.nowTS = (new Date()).getTime();
}
}
})
</script>
<div id="app">
<time-component></time-component>
<time-component></time-component>
</div>
<script tpye="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: '#app'
})
</script>
</body>



정상적으로 렌더링 되려면 data 옵션에 함수가 주어져야 한다. 정확하게 표현하자면 '함수가 호출되어 리턴된 객체가 data 옵션에 주어진다'라고 표현 할 수있다. 아래와 같이 수정하면 정상적으로 출력된다.


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

data 옵션에 함수를 지정하는 이유는 동일한 컴포넌트가 여러 번 사용되더라도 동일한 객체를 가리키는 것이 아니라 함수가 호출될 때마다 만들어진 객체가 리턴되기 때문이다. 매번 만들어진 객체가 리턴되기 때문에 서로 다른 객체를 참조한다.




5. props와 event

부모 컴포넌트와 자식 컴포넌트 사이에 속성(props)와 이 벤트를 이용해서 상호작용하여 통신할 수 있다. Vue 컴포넌트들이 부모-자식 관계로 형성되었을 때 각 컴포넌트 내부의 데이터는 캡슐화 되기 때문에 다른 컴포넌트에서 접근할 수 없다. 따라서 부모 컴포넌트에서 자식 컴포넌트로 필요한 정보를 전달하기 위해서는 속성(props)을 이요해야한다. 주의할 점은 부모에서 자식으로 단방향으로만 전달이 가능하다.


반대로 자식 컴포넌트에서 부모 컴포넌트로 전달하는 방법은 이벤트를 이요한다. v-on 디렉티브를 이용해서 이벤트를 처리하는 방법이다. 



5.1) props를 이용한 정보 전달

props를 이요한 정보전달 방법은 간단한다 . Vue 컴포넌트를 정의할 때 props라는 옵션을 작성하고 props명을 배열로 나열하면된다.

<template id='listTemplate'>
<li>{message}}</li>
</template>
<body>
<script tpye="text/javascript">
Vue.component('list-component', {
template: '#timeTemplate',
props: ['message']
})
</script>
<div id="app">
<list-component message="Hello"></list-component>
<list-component message="안녕하세요"></list-component>
</div>
<script tpye="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: '#app'
})
</script>
</body>

list-componenet 컴포넌트를 작성하며 message라는 이름의 속성을 정의했다. 이 속성을 통해서 전달된 정보는 9행에서 템플릿 문자열을 통해서 출력된다. 속성을 통해서 정보를 전달하기 위해서는 컴포넌트를 사용할때 특성처럼 전달한다.


컴포넌트 작성시 속성명을 부여할때 카멜 표기법(camel casing)을 사용했다면 태그에서 속성명을 사용할 정보를 전달할 때는 반드시 케밥(kebob casing)을 사용해야 한다. 태그 작성시 특성은 대소문자를 구분하지 않기 때문이다.


ex) myMessage (x) => my-message(o)


속성을 정의할때 배열 형태로 나여할 수도 있지만 속성에 대한 엄격한 유효성 검증이 필요하다면 배열이 아닌 객체 형태를 사용할 수 있다. 


<script tpye="text/javascript">
Vue.component('list-component', {
template: '#listTemplate',
props: {
message: {
type: String,
default: '안녕하세여'
},
count: {
type: Number,
required: true
}
}
})
</script>
<div id="app">
<list-component message="Hello" count="100"></list-component>
<list-component message="안녕하세요" count="21"></list-component>
<list-component message="Ni hao"></list-component>
<list-component count="1000"></list-component>
</div>


배열 형식이 아닌 객체 형식으로 지정했는데 message의 속성은 문자형이며 기본값으로 '안녕하세여'라는 값을 갖고 있다. count 속성은 숫자형이고 필수 입력해야하는 속성이다.


실제로 실행을 해보면 정상출력되나 콘솔에 에러가 찍힌다. 이유인 즉슨 넘겨주는 속성 count가 Number아 아니라 String 형태로 들어가기 때문이다. "21"과 같은 리터럴은 자바스크립트 구문으로 인식되지 않고 문자열 값으로 그대로 전달된다.


이 문제를 해결하기 위해서는 v-bind 디렉티브를 이용한다.


<div id="app">
<list-component message="Hello" v-bind:count="100"></list-component>
<list-component message="안녕하세요" :count="21"></list-component>
<list-component message="Ni hao"></list-component>
<list-component :count="1000"></list-component>
</div>


한 가지 더 주의할 부분이 있는데 속성으로 전달할 값이 배열이나 객체인 경우이다. 이경우에 기본값(default value)를 부여하려면 반드시 함수를 사용해야한다.


countries 속성에 입력한 값이 정상적으로 props에 들어가있는걸 확인할 수있다.




5.2) event를 이용한 정보 전달

event를 이용해서 전달하는 방법은 사용자 정의 이벤트를 활용한다. 자식 컴포넌트에서 이벤트를 발신(emit)하고 부모 컴포넌트에서 v-on 디렉티브를 이용해 이벤트를 수신한다.


<!-- 자식놈 -->
<style>
.buttonstyle {
width: 120px;
height: 30px;
text-align: center;
}
</style>
<template id='childTemplate'>
<div>
<button class="buttonstyle" @click="clickEvent"
:data-lang="buttonInfo.value">{{buttonInfo.text}}</button>
</div>
</template>
<script tpye="text/javascript">
Vue.component('child-component', {
template: '#childTemplate',
props: ['buttonInfo'],
methods: {
clickEvent: function(e) {
this.$emit('timeclick', e.target.innerText,
e.target.dataset.lang);
}
}
})
</script>
<!-- 자식 끝 -->
<!-- 부모 -->
<template id='parentTemplate'>
<div>
<child-component v-for="s in buttons" :button-info="s"
@timeclick="timeclickEvent"></child-component>
<hr/>
<div>{{msg}}</div>
</div>
</template>
<script tpye="text/javascript">
Vue.component('parent-component', {
template: '#parentTemplate',
props: ['buttons'],
data: function() {
return {
msg: ""
}
},
methods: {
timeclickEvent: function(k, v) {
this.msg = k + ", " + v
}
}
})
</script>
<!-- 부모 끝 -->
<body>

<div id="app">
<parent-component :buttons="buttons"></parent-component>
</div>
<script tpye="text/javascript ">
Vue.config.devtools = true;
var v = new Vue({
el: '#app',
data: {
buttons: [{
text: "hello",
value: "영어"
}, {
text: "신짜오",
value: "베트남어"
}, {
text: "니하오",
value: "중국어"
}]
}
})
</script>
</body>


buttonInfo 속성을 정의하였고 이 속성은 부모 컴포넌트로부터 값을 전달받아 버튼 리스트를 생성한다.  자식 컴포넌트를 사용하는 부모 컴포넌트는 buttons 속성과 msg 데이터 옵션을 포함하고 있다. 데이터 옵션은 해당 컴포넌트 내에서만 사용하기 위해 정의한다. buttons 속성은 vm Vue 인스턴스의 buttons 데이터를 전달 받아 v-for 디렉티브를 사용해 반복적으로 생성되는 자식 컴포넌트 배열 값을 바인딩한다.


자식 컴포넌트 내부에서 버튼이 클릭되면 $emit() 메서드를 통해 timeclick 이벤트를 발신한다. 부모 컴포넌트에서는 v-on 디렉티브를 통해 timeclick이벤트를 처리하는 것이다. 


timeclickEvent(k, v) 함수는 두개의 인자값을 받는데 바로 buttonInfo.text와 buttonInfo.value 값이다. 




5.3) 이벤트 버스를 이용한 데이터 전달

부모 - 자식 간계 이외에도  손자 증손자 관계인 컴포넌트들 사이에도 정보를 전달하는 방법이 있다. 바로 이벤트 버스이다. 비어있는 Vue 인스턴스를 만들어 사용하면 된다.


예제에서는 두개의 컴포넌트로 나누었다. 값을 입력하는 input-component와 todolist를 나타내는 부분 list-component이다. 이 두개의 컴포넌트 사이에 데이터의 상호작용이 필요하다. 


<head>
<meta charset="utf-8">
<title>hello vue.js</title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<style>
* {
box-sizing: border-box;
}
.header {
background-color: purple;
padding: 30px 30px;
color: yellow;
text-align: center;
}
.header:after {
content: "";
display: table;
clear: both;
}
</style>
</head>
<script tpye="text/javascript">
var eventBus = new Vue()
</script>
<style>
ul {
margin: 0;
padding: 0;
}
ul li {
cursor: pointer;
position: relative;
padding: 8px 8px 8px 40px;
background: #eee;
font-size: 14px;
transition: 0.2s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul li:hover {
background: #ddd
}
ul li.checked {
background: #BBB;
color: #fff;
text-decoration: line-through
}
ul li.checked::before {
content: '';
position: absolute;
border-color: #fff;
border-style: solid;
border-width: 0px 1px 1px 0px;
top: 10px;
left: 16px;
transform: rotate(45deg);
height: 8px;
width: 8px;
}
.close {
position: absolute;
right: 0px;
top: 0px;
padding: 12px 16px 12px 16px
}
.close:hover {
background-color: #f44336;
color: white;
}
</style>
<template id='list-template'>
<ul id="todolist">
<li v-for="a in todolist" :class="checked(a.done)"
@click="doneToggle(a.id)">
<span>{{a.todo}}</span>
<span v-if="a.done">(완료)</span>
<span class="close" @click.stop="deleteTodo(a.id)">&#x00D7;</span>
</li>
</ul>
</template>
<script tpye="text/javascript">
Vue.component('list-component', {
template: '#list-template',
created: function() {
eventBus.$on('add-todo', this.addTodo);
},
data: function() {
return {
todolist: [{
id: 1,
todo: "영화보기",
done: false
}, {
id: 2,
todo: "주말 산책",
done: true
}, {
id: 3,
todo: "ES6 공부",
done: false
}, {
id: 4,
todo: "Vue.js 공부",
done: false
}, ]
}
},
methods: {
checked: function(done) {
if (done) return {
checked: true
};
else return {
checked: false
}
},
addTodo: function(todo) {
if (todo !== "") {
this.todolist.push({
id: new Date().getTime,
todo: todo,
done: false
})
}
},
doneToggle: function(id) {
var index = this.todolist.findIndex(function(item) {
return item.id === id
})
this.todolist[index].done = !this.todolist[index].done;
},
delete: function(id) {
var index = this.todolist.findIndex(function(item) {
return item.id === id
})
this.todolist.splice(index, 1)
}
}
})
</script>
<style>
.input {
border: none;
width: 75%;
height: 35px;
padding: 10px;
float: left;
font-size: 16px;
}
.addbutton {
padding: 10%px;
width: 25%;
height: 35px;
background: #d9d9d9;
color: #555;
float: left;
text-align: center;
font-size: 13px;
cursor: pointer;
transition: 0.3s;
}
.addbutton:hover {
background-color: #bbb;
}
</style>
<template id='inputTemplate'>
<div>
<input class="input" type="text" id="task" v-model.trim="todo"
placdholder="입력 후 엔터!" @keyup.enter="addTodo">
<span class="addbutton" @click="addTodo">추 가</span>
</div>
</template>
<script tpye="text/javascript">
Vue.component('input-component', {
template: '#inputTemplate',
data: function() {
return {
todo: ""
}
},
methods: {
addTodo: function() {
eventBus.$emit('add-todo', this.todo);
this.todo = "";
}
}
})
</script>

<body>
<div id="todolistapp">
<div id="header" class="header">
<h2>Todo List App</h2>
<input-component></input-component>
</div>
<list-component></list-component>
</div>
<script tpye="text/javascript">
var vm = new Vue({
el: "#todolistapp"
})
</script>
</body>






'Vue.js' 카테고리의 다른 글

[Vue.js] axios 서버통신  (2) 2018.12.22
[Vue.js] Vue CLI(GUI)  (0) 2018.12.18
[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16
[Vue.js] computed Property (계산형 속성)  (0) 2018.12.15

Vue.js Event 처리

동적인 화면을 구성할때 다이나믹한 UI는 HTML 요소에서 발행하는 이벤츠 처리를 통해서 구현되는경우가 많다. HTML 문서에서 발생하는 이벤트는 다양하다 키보드를 누를 때 발생하는 keyup, keypress, keydown 이벤트 마우스 클릭할 때 발생하는 click, doubleclick 이벤트, 마우스를 움직일 때 발생하는 mousemove 이벤트 등이 있다. 




1. 인라인 이벤트 처리

Vue.js에서는 v-on 디렉티브를 이용해서 처리할 수있다. 가장 많이 쓰이는 Click 이벤트 처리를 예제로 만들어 보았다.

<html>

<head>
<meta charset="utf-8">
<title>hello vue.js</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/
3.3.7/css/bootstrap.min.css">
</head>
<style>
.layout1 {
margin: 30px 30px 30px 30px;
}
</style>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<body>
<div id="example" class="container layout1">
<p>
<input tpye="text" v-model="amount" class="form-control" /></p>
</p>
<p>
<button id="deposit" v-on:click="balance += parseInt(amount)"
class="btn btn-primary">예금</button>
<button id="deposit" v-on:click="balance -= parseInt(amount)"
class="btn btn-primary">인출</button>
</p>
<h3>계좌 잔고 : {{balance}}</h3>
</div>
<script tpye="text/javascript">
var vm = new Vue({
el: '#example',
data: {
balance: 0,
amount: 0
}
})
</script>
</body>
</html>

예제는 간단하지만 디자인을 입히기 위해 bootstrap을 사용했다. amount 데이터 속성을 v-model로 양방향 바인딩하였고 사용자가 입력한 값은 amount에 즉시 반영된다. v-on:click 디렉티브를 이용해 클릭 이벤트 처리를 숭행한다. 예금 버튼을 클릭할때는 balance에 amount를 누적하고 인출 버튼을 클릭할 때는 amount를 balance에서 차감한다.


v-on 디렉티브는 @로 줄여 쓸수도 있다. v-on:click 대신에 @click으로 변경해도 정상적으로 동작한다.


v-on:click="balance += parseInt(amount)"


위의 코드는 실행 코드를 직접 연결하고 있다. 이와 같은 코드는 약간의 문제를 안고있는데 예를 들어 예금하거나 인출하려는 금액은 마이너스 값을 허용하지 않고, 인출 금액은 계좌 잔고보다 많아서는 안된다면 이 처리를 위한 코드를 인라인 이벤트에 모두 작성하기 어렵다. 그렇기 때문에 현실적인 이벤츠 처리 방법이라 하기 힘들다.




2. 이벤트 핸들러 메서드

앞의 예제에 이어 Vue 인스턴스에 등록한 메서드를 이벤트 처리 함수로 연결해보겠다. 

<html>

<head>
<meta charset="utf-8">
<title>hello vue.js</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/
3.3.7/css/bootstrap.min.css">
</head>
<style>
.layout1 {
margin: 30px 30px 30px 30px;
}
</style>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>

<body>
<div id="example" class="container layout1">
<p>
<input tpye="text" v-model="amount" class="form-control" /></p>
</p>
<p>
<button id="deposit" v-on:click="deposit" class="btn btn-primary">예금</button>
<button id="deposit" v-on:click="withdraw" class="btn btn-primary">인출</button>
</p>
<h3>계좌 잔고 : {{balance}}</h3>
</div>
<script tpye="text/javascript">
var vm = new Vue({
el: '#example',
data: {
balance: 0,
amount: 0
},
methods: {
deposit: function(e) {
var amt = parseInt(this.amount);
if (amt <= 0) {
alert("0보다 큰 값을 예금해야한다. ")
} else {
this.balance += amt;
}
},
withdraw: function(e) {
var amt = parseInt(this.amount);
if (amt <= 0) {
alert("0보다 큰 값을 인출할 수 있다. ")
} else if (amt > this.balance) {
alert("잔고보다 많은 금액을 인출할 수 있다. ")
} else {
this.balance += amt;
}
}
}
})
</script>
</body>

</html>


deposit, withdraw 메서드를 작성하였고 메서드 내부에는 앞서 이야기했던 금액과 계좌 잔고에 따른 유효성 검사 기능을 코드로 작성했다. 


이와 같이 복잡한 기능은 메서드를 미리 작성해두고 v-on 디렉티브로 참조해서 이벤트 처리를 수행한다.





3. 이벤트 객체

이벤트를 처리하는 메서드는 첫 번째 파라미터로 이벤트 객체를 전달받는다. deposit 함수를 보면 function(e) {...}와 같이 이벤트 객체를 전달받고 있고 이 이벤트 객체를 통해서 이용할 수 있는 정보가 많다.


Vue.js의 이벤트 객체는 W3C 표준 HTML DOM Event 모델을 그대로 따르면서 추가적인 속성을 제공한다. 그렇기 때문에 기존의 순수 자바스크립트에서 사용하던 이벤트 객체의 정보를  거의 대부분 그대로 이용할 수있다. 


이벤트 객체의 주요 공통 속성

속성명 

설명 

 target

 이벤트가 발생한 HTML 요소를 리턴함 

 currentTarget

 이벤트 리스너가 이벤트를 발생시키는 HTML 요소를 리턴함

 path

 배열값. 이벤트 발생 HTML 요소로부터 documnet, window 객체로까지 거슬러 올라가는 경로를 나타냄

 bubbles

현재의 이벤트가 버블링을 일으키는 이벤트인지 여부를 리턴함 

 cancelable

기본 이벤트를 발지할 수 잇는지 여부를 리턴함 

 defaultPrevented

기본 이벤트가 발지되었는지 여부를 나타냄 

 eventPhase

이벤트 흐름의 단계를 나타냄

1 : 포착

2 : 이벤트 발생

3 : 버블링 

 srcElement

 IE에서 사용되던 속성으로 target과 동일한 속성



키보드 이벤트 관련 속성

 속성명

설명 

 alrtKey

 ALT 키가 눌러졌는지 여부를 나타냄(true/false)

 shiftKey 

 Shift 키가 눌러졌는지 여부를 나타냄(true/false) 

 ctrlKey

 CTRL 키가 눌러졌는지 여부를 나타냄(true/false) 

 metakey

 메타키가 눌러져있는지 여부를 나타냄 window key or command key 

 key

 이벤트에 의해 나타나는 키의 값을 리턴함 대소문자 구분함 

 code

 이벤트를 발생시킨 키의 코득밧을 리턴함 

 keyCode

 이벤트를 발생시킨 키보드의 고유 키보드

 charCode

 keypress 이벤트가 발생될 때 Unicode 캐릭터 코드를 리턴함 

 location

 디바이스에서의 키 위칫값, 일반 키보드는 이 밧이 모두 0 


이벤트 객체 주요 메서드

메서드 명 

설명 

 preventDefault()

 기본 이벤트의 자동 실행을 중지시킴 

 stopPropagation() 

 이벤트의 전파를 막음 





4. 기본 이벤트


몇몇 HTML 요소는 개발자가 이벤트를 연결하지 않았음에도 뭔가 실해되는 기능을 가지고있는 것들이 있따. 예를 들어 <a> 요소는 클릭 이벤트 처리를 하지 않았음에도 클릭하면 href 특성에 정의된 경로로 화면을 이동시킨다. HTML 문서나 요소에 어떤 기능을 실행하도록 이미 정의되어 있는 이벤트를 기본 이벤트라고 부른다.

ex) <a>, <form>, <input>

이와 같이 평상시에 당연하다고 여기는 것 중 으이외로 많은 부분이 기본 이벤트로 처리되고 있다. 하지만 미리 정의 된 기능을 실행할 수 있기 때문에 편리하지만 걸림돌이 되기도 한다. 따라서 기본 이벤트 실행을 중지시킬 수 있는 방법을 알아 둘 필요가 있다.

<head>
<meta charset="utf-8">
<title>hello vue.js</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap
/3.3.7/css/bootstrap.min.css">
</head>
<style>
html,
body {
margin: 0;
padding: 0;
}
#example {
height: 98vh;
min-height: 100%;
padding: 5px;
}
</style>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>

<body>
<div id="example" v-on:contextmenu="ctxStop">
<a href="https://facebook.com" @click="confirmFB">페이스북</a>
</div>
<script tpye="text/javascript">
var vm = new Vue({
el: '#example',
methods: {
ctxStop: function(e) {
e.preventDefault();
},
confirmFB: function(e) {
if (!confirm("페이스북으로 이동할까요?")) {
e.preventDefault();
}
}
}
})
</script>
</body>

내부에서는 기본 이벤트 실행을 막기 위해서 이벤트 객체가 제공하는 preventDefault()메서드를 호출한다. confirmFB 메서드는 사용자에게 확인을 받기 위해서 confirm() 함수를 사용한다. 사용자가 확인 버튼이 아닌 취소 버튼을 클릭하면 preventDefault()메서드가 호출된어 기본 이벤트의 실행을 중지시킨다.


contextmenu 이벤트가 발생할 때 호출하는 ctxStop 메서드는 무조건 preventDefault() 메서드를 호출한다. 이로서 내장 컨텍스트 메뉴는 나타나지 않게 된다. 최근에 이 기본 이벤트의 실행을 막는 주된 이유는 브라우져 화면에서 오른쪽 마우스버튼을 클릭할 때 내장 컨텍스트 메뉴 대신 개발자가 직접 작성한 메뉴를 나타내기 위한 경우가 많다. 




5. 이벤트 전파와 버블링


HTML 문서의 이벤트 처리는 3단계를 거친다. 1단계는 무선 내의 요소에서 이벤트가 발생했을 때 HTML 문서의 밖에서부터 이벤트를 발생시킨 HTML 요소까지 포착해 들어가는 이벤트 포착단계 이다.

2단계는 이벤트를 발생시킨 요소에 다다르면 요소의 이벤트에 연결된 함수를 직접 호출시키는 이벤트 발생 단계이다. 마지막 3단계는 이벤트가 발생한 요소로부터 상위 요소로 거슬러 올라가면서 동일한 이벤트를 호출시키는 버블링 단계이다. 일반적으로 2단계, 3단계에서 연결된 이벤트 함수가 호출 된다.


(요런 느낌?)



<style>
#outer {
width: 200px;
height: 200px;
border: solid 2px black;
background-color: aqua;
position: absolute;
top: 100px;
left: 50px;
padding: 10px 10px 10px 10px
}
#inner {
width: 100px;
height: 100px;
border: solid 2px black;
background-color: yellow;
}
</style>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>

<body>
<div id="example">
<div id="outer" @click="outerClick">
<div id="inner" @click="innerClick"></div>
</div>
</div>
<script tpye="text/javascript">
var vm = new Vue({
el: '#example',
methods: {
outerClick: function(e) {
console.log("### OUTER CLICK")
console.log("Event Phase : ", e.eventPhase)
console.log("Current Target : ", e.currentTarget)
console.log("Target : ", e.target)
},
innerClick: function(e) {
console.log("### INNER CLICK")
console.log("Event Phase : ", e.eventPhase)
console.log("Current Target : ", e.currentTarget)
console.log("Target : ", e.target)
}
}
})
</script>
</body>



위의 예제를 실행후 개발자도구에서 #inner 요소를 클릭(노란색 박스)하면 outer click 이벤트가 같이 실행된 것을 알 수 있다. 일반적으로는 이벤트 버블링은 막아야 할 작업니다. #inner를 클릭했을 때 상위요소로의 이벤트 전파(Propagation)를 막아야 한다. 이를 위해 이벤트 객체의 stopPropagation() 메서드를 호출한다. 

outerClick: function(e) {
console.log("### OUTER CLICK")
console.log("Event Phase : ", e.eventPhase)
console.log("Current Target : ", e.currentTarget)
console.log("Target : ", e.target)
e.stopPropagation();
},

위의  e.stopPropagation(); 이 소스를 넣으면 더 이상 버블링이 일어나지 않는 것을 확인 할 수 있다. 이렇게 수정 할 수도 있지만 이 부분도 이벤트 수식어로 대체할 수 있다.


1) .stop : 이벤트 전파를 중단

2) .capture : CAPTUREING_PHASE 단계에서만 이벤트가 발생

3) .self : RAISING_PHASE 단게일 때만 이벤트 발생



<div id="example">
<div id="outer" @click.stop="outerClick">
<div id="inner" @click.stop="innerClick"></div>
</div>
</div>






6. 이벤트 수식어

.prevent, .stop, .self 와 같은 이벤트 수식어를 살펴보았지만 이 밖에도 다양한 이벤트 수식어가 제공된다.


1) once 수식어

once 수식어는 한 번만 이벤트를 발생시킨다. 

ex) v-on:click.once


2) 키코드 수식어

키보드 관련 이벤트를 처리할 때 사용할 수 있는 수식어이다. 키보드의 키를 누를 때 고유의 키코드 값을 가질 때만 이벤트를 발생 시킬 수있다. 

'Vue.js' 카테고리의 다른 글

[Vue.js] Vue CLI(GUI)  (0) 2018.12.18
[Vue.js] Component  (0) 2018.12.18
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16
[Vue.js] computed Property (계산형 속성)  (0) 2018.12.15
[Vue.js] 기본 디렉티브  (0) 2018.12.15

Vue 인스턴스

var app = new Vue({}) 형식으로 생성한 객체들을 Vue 인스턴스라고 부른다. 때로는 뷰모델을 의미하는 vm을 삽입해서 vue vm 인스턴스라고도 칭한다.




1. el, data computed 옵션

data 옵션은 data 옵션에 주어진 모든 속성들을 Vue 인스턴스 내부에서 직접이용되지 않고 Vue 인스턴스와 Data옵션에 주어진 객체 사이에 프록시를 두어 처리한다. 


<body>
<div id="test">
{{ name }}
</div>
<script tpye="text/javascript">
var model = {
name: "홍길동"
}
var vm = new Vue({
el: '#test',
data: model
})
</script>
</body>


data 옵션 값이 vue 인스턴스에 의해 프록시 처리되어 vm.name과 같이 사용할 수 있음을 알 수 있다. 직접 data 옵션을 통해 접근하고 싶다면 vm.$data.name과 같이 접근할 수 있다. 내장 옵션들은 모두 $식별자를 앞에 붙이고 있는데 이름 충돌을 피하기 위한 것이다.



el 옵션은 Vue 인스턴스에 연결할 HTML DOM 요소를 지정한다. 주의할 점은 여러개 요소에 지정할 수 없다는 것이다. el 옵션 값으로 '#test'와 같이 css 선택자 표현을 사용하였으므로 혹시 CSS 클래스 선택자를 사용하면 여러 HTML 요소에 Vue 인스턴스를 연결할 수 있지 않을까 생각할 수도 있지만 여러개 요소 중 첫 번째 요소에만 연결된다.

실행 도중 동적으로 Vue 인스턴스와 HTML 요소를 연결할 수 있지만, 가능하다면 el 옵션은 Vue 인스턴스를 생성할 때 미리 지정할 것을 권장한다. ( vm.$mount("#text")와 같이 $mount()를 이용해 동적 연결 가능)


computed 옵션은 지정한 값은 함수지만 Vue 인스턴스는 프록시 처리하여 마치 속성처럼 취급한다.

<body>
<div id="test">
<input type="text" v-model="num" />
<br/> 1부터 입력된 수까지의 합 : <span>{{ sum }}</span>
</div>
<script tpye="text/javascript">
var model = {
name: "홍길동"
}
var vmSum = new Vue({
el: '#test',
data: {
num: 0
},
computed: {
sum: function() {
var n = Number(this.num);
if (Number.isNaN(n) || n < 1) return 0;
return ((1 + n) * n) / 2;
}
}
})
</script>
</body>


sum은 함수인데 vmSum.sum으로 속성 접근 방식을 사용했을 때 정상 실행되는 모습을 볼 수있다. 이러한 이유로 계산형 속성(Computed Property)라고 부르는 것이다. 물론 Vue 인스턴스의 모든 옵션 저보를 다루는 $Option속성을 이용하면 실제 함수를 확인할 수있다.



계산형 속성은 읽기 전용이라 생각할 수 있지만 set메서드를 지정하면 쓰기 작업도 가능하다. (Getter / Setter)


<body>
<div id="test">
금액 : <span>{{ amount }}</span>
</div>
<script tpye="text/javascript">
var model = {
name: "홍길동"
}
var vm = new Vue({
el: '#test',
data: {
amt: 1234567
},
computed: {
amount: {
get: function() {
var s = new String("" + this.amt)
var result = ""
var num = 0
for (var i = s.length - 1; i >= 0; i--) {
result = s[i] + result
if (num % s == 2 && i !== 0)
result = "," + result
num++
}
return result;
},
set: function() {
if (typeof(amt) === "string") {
var result = parseInt(amt.replace(/,/g, ""))
if (isNaN(result)) this.amt = 0;
else this.amt = result;
} else if (typeof(amt) === "number")
this.amt = amt;
}
}
}
})
</script>
</body>


get/set method를 생성해보았다. get method는 data 속성인 amt 값을 숫자 3마리마다 쉼표를 넣어 리턴하고 문자로 변환하기 우해 String 객체를 생성한다.  set method는 문자열을 입력받으면 쉼표를 제거한 뒤 숫자 값으로 변환하여 amt 데이터 속성에 할당한다. 쉼표를 제거하기 위한 정규표현식을 사용했다.




2. Method

Vue 인스턴스에서 사용할 메서드를 등록하는 옵션이다. 등록된 메서드는 Vue 인스턴스를 이용해 직접 호출할 수도 있고, 디렉티브 표현식, 콧수염 표현식에서도 사용할 수 있다.


<body>
<div id="test">
<input type="text" v-model="num" /><br/> 1부터 입력된 수까지의 합 : <span> {{sum()}} </span>
</div>
<script tpye="text/javascript">
var model = {
name: "홍길동"
}
var vm = new Vue({
el: '#test',
data: {
amt: 1234567
},
methods: {
sum: function() {
var n = Number(this.num)
if (Number.isNaN(n) || n < 1)
return ((1 + n) * n) / 2;
}
}
})
</script>
</body>


메서드를 호출 하기 위해서는 {{ sum() }} 형식으로 호출한다. 얼핏보면 계산형 속성과 차이점을 느끼기 어렵다. 


메서드와 계산형 속성을 사용했을 때 최종적인 결과물은 같아 보이지만 내부 작동 방식에서는 차이가 있다. 계산형 속성은 종속된 값에 의한 결괏값이 캐싱된다는 점이다. 위의 예제에서는 vmSum.num이 값이라면 계산형 속성인 sum은 캐싱된 결괏값을 바로 리턴하지만 위의 sum() 메서드를 매번 실행한다는 점이다. 


computed : {
sum: function() {
console.log(Date.now())
var n = Number(this.num)
if (Number.isNaN(n) || n < 1)
return ((1 + n) * n) / 2;
}
},


위의 소스에 console.log를 이용해 시간을 확인해보면  계산된 속성을 여러 번 액세스해도 콘솔에 타임스탬프를 출력하는 코드가 실행되지 않는다. 이 실행 결과는 캐싱된 값을 출력하고 있다는 의미를 한다. 이것이 메서드와 계산형 속성의 차이점이다.


이와 같이 메서드를 사용할 것인지, 계산형 속성을 사용할 것인지 결정할 때의 고려사항 중 하나가 캐싱 여부이다.


주의할점이 있는데 바로 ECMAScript6가 제공하는 화살표 함수는 사용해서는 안된 다는것이다. 화살표 함수 내부에서는 this가 vue 인스턴스를 가리키지 않고, 전역 객체를 가리키지 때문이다. 




3. 관찰 속성

Vue.js 에서 하나의 데이터를 기반으로 다른 데이터를 변경할 필요가 있을 때 흔히 사용 할 수 있는 것으로 계산형 속성이 있다. 이외에도 관착 송성이란 것을 사용할 수 있는데 주로 긴 처리 시간이 필요한 비동기 처리에 적합하다는 특징을 가지고 있다.


<body>
<div id="example">
x : <input type="text" v-model="x" /><br/> y : <input type="text" v-model="y" /><br/>
</div>
<script tpye="text/javascript">
var model = {
name: "홍길동"
}
var vm = new Vue({
el: '#example',
data: {
x: 0,
y: 0,
sum: 0
},
watch: {
x: function(v) {
console.log("## x 변경")
var result = Number(v) + Number(this.y);
if (isNaN(result))
this.sum = 0
else
this.sum = result
},
y: function(v) {
console.log("## y 변경")
this.y = v
var result = Number(this.x) + Number(v);
if (isNaN(result))
this.sum = 0
else
this.sum = result
}
}

})
</script>
</body>



watch 옵션에 등록되는 것은 속성의 이름과 해당 속성이 변경되었을 때 호출할 함수이다. 함수는 인자를 전달받는데, 이것은 변경된 속성의 값이다. 값이 바뀔 때마다 매번 함수가 호출된다는 점이다. 이 예제는 굳이 관찰 속성을 사용하지 않아도 된다. 이런 경우라면 계산형 속성에서도 값을 출력할 수 있다. 보통 watch 옵션이 쓰일 때에는 비동기 처리를 할 때이다.


비동기 처리의 가장 대표적인 예가 외부 서버와의 통신이다. JQeury의 AJAX 기능이나 Promise 기반의 HTTP Client 기능을 수행하는 axios, fetch와 같은 라이브러리도 있다. 


관찰 속성을 이용해 연락처 검색 기능을 제공하는 비동기 요청 예제를 만들어보았다.


<html>
<head>
<meta charset="utf-8">
<title>hello vue.js</title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/
es6-promise.auto.min.js"></script>
</head>
<style>
#list {
width: 600px;
border: 1px solid black;
border-collapse: collapse;
}
#list td,
#list th {
border: 1px solid black;
text-align: center;
}
#list>thead>tr {
color: yellow;
background-color: purple;
}
</style>
<body>
<div id="example">
<p>
이름 : <input type="text" v-model="name"
placeholder="두자 이상 입력하세요" />
</p>
<table id="list">
<thead>
<tr>
<th>번호</th>
<th>이름</th>
<th>전화번호</th>
<th>주소</th>
</tr>
</thead>
<tbody>
<tr v-for="contact in contactlist">
<td>{{contact.no}}</td>
<td>{{contact.name}}</td>
<td>{{contact.tel}}</td>
<td>{{contact.address}}</td>
</tr>
</tbody>
</table>
<div v-show="isProcessing === true">조회중</div>
</div>
<script tpye="text/javascript">
var model = {
name: "홍길동"
}
var vm = new Vue({
el: '#example',
data: {
name: "",
isProcessing: false,
contactlist: []
},
watch: {
name: function(val) {
if (val.length >= 2)
this.fetchContacts();
else
this.contactlist = []
}
},
methods: {
fetchContacts: _.debounce(function() {
this.contactlist = []
this.isProcessing = true
var url = "http://sample.bmaster.kro.kr/
contacts_long/search/" + this.name
var vm = this
fetch(url).then(function(response) {
return response.json()
}).then(function(json) {
vm.contactlist = json
vm.isProcessing = false
}).catch(function(ex) {
console.log('parsing failed', ex)
vm.contactlist = []
vm.isProcessing = false
})
}, 300)
}
})
</script>
</body>
</html>

Vue 객체 내에 관찰자(Watcher)는 name 속성의 변화를 감지하여 함수를 호출한다. 그리고 이 함수에서 다시 두 자 이상이 입력되었다면 fetchContacts 함수를 호출한다. 타이핑을 할 때마다 매번 API를 호출 하는거슨 비효율적이므로 lodash 라이브러리의 _.debounce() 함수를 이용해 일정 시간(300ms)이 지나도록 연속적인 호출이 일어나지 않으면 실제 APIA를 요청하도록 작성했다. 


fetchContacts 메서드에서는 fetch() 함수를 이용해 이름 검색을 수행한다. fetch() 메서드의 리턴 값을 promise 객체이다. promise 객체는 비동기 처리를 위해 주로 사용하는데, fetch()가 호출되고 나서 서버로부터 응답이 오면 .then()에 전달한 함수가 호출된다. 



4. Vue Instance Life Cycle


Vue 인스턴스는 객체로 생성되고 데이터에 대한 관찰 기능을 설정하는 등의 작업을 위해 초기화를 수행한다. 그리고 이 과정에서 다양한 라이프 사이클 훅 메서드를 적용할 수 있다.



beforeCreate : Vue 인스턴스가 생성되고 데이터에 대한 관찰 기능 및 이벤트 감시자 설정 전에 호출됨

created : Vue 인스턴스가 생성된 후에 데이터에 대한 관찰기능, 계산형 속성, 메서드, 감시자 설정이 완료된 후에 호출된다.

beforeMount : 마운트가 시작되기 전에 호출됩니다.

mounted : el에 vue 인스턴스의 데이터가 마운트된 후에 호출됩니다.

beforeUpdate : 가상 DOM이 렌더링, 패치되어 전에 데이터가 변경될 때 호출됩니다. 이 훅에서 추가적인 상태 변경을 수행할 수 있다. 하지만 다시 렌더링되지 않는다.

updated : 데이터의 변경으로 가상 DOM이 다시 렌더링되고 패치된 후에 호출된다. 이 훅이 호출되었을땐 이미 컴포턴트의 DOM이 업데이트된 상태이다.

beforeDestory : Vue 인스턴스가 제거되기 전에 호출된다.

destroyed : Vue 인스턴스가 제거된 후에 호출된다. 모든 디렉티브 바인딩이 해제되고 이벤트 연결도 모두 제거 된다.

















'Vue.js' 카테고리의 다른 글

[Vue.js] Component  (0) 2018.12.18
[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] computed Property (계산형 속성)  (0) 2018.12.15
[Vue.js] 기본 디렉티브  (0) 2018.12.15
vue.js 구조  (0) 2018.12.15

Computed Property (계산형 속성)


앞서 v-bind 디렉티브를 이용한 간단한 데이터 바인딩 방법을 설명했는데 만약 연산 로직이 필요한 경우에는 이 방법만으로는 문제를 해결하기가 쉽지 않다.


Vue.js의 Computed Property(계산형 속성)은 이러한 문제를 해결해주는 방법 중의 하나이다.  Vue 객체를 만들때 computed 속성과 함께 함수를 등록해두면 마치 속성처럼 이용할 수 있다.


<body>
<div id="exmaple">
<input type="text" v-model="num" /><br/>
1부터 입력된 수까지의 합 : <span>{{ sum }}</span>
</div>
<script tpye="text/javascript">
var vmSum = new Vue({
el: '#exmaple',
data: {
num: 0
},
computed: {
sum: function() {
var n = Number(this.num)
if (Number.isNaN(n) || n < 1)
return 0;
return ((1 + n) * n) / 2;
}
}
})
</script>
</body>


위의 소스에서 주의할점이 있는데 바로 this.값이다. 함수 안에서의 this는 vue 객체 자신을 참조한다. 그런데 함수 내부에서 다른 콜백함수를 실행하거나 했을 때는 this가 다른 값으로 연결 될 수 있으므로 주의 해야한다. num 값이 number 타입이라 생각하기 슂미나 html 요소 내부에서는 모두 문자열로 다뤄진다. 그렇기 때문에 Number() 함수나 parseInt() 함수를 이용해 명시적으로 숫자값으로 변환해야한다.



<body>
<div id="exmaple">
<p>
국가명 : <input type="text" v-model="countryname"
placeholder="국가명" />
</p>
<table id="list">
<thead>
<tr>
<th>번호</th>
<th>국가명</th>
<th>수도</th>
<th>지역</th>
</tr>
</thead>
<tbody id="contacts">
<tr v-for="c in filtered">
<td>{{ c.no }}</td>
<td>{{ c.name }}</td>
<td>{{ c.capital }}</td>
<td>{{ c.region }}</td>
</tr>
</tbody>
</table>
</div>
<script tpye="text/javascript">
var model = {
countryname: "",
countries: [{
no: 1,
name: "미국",
capital: "워싱턴 DC",
region: "america"
}, {
no: 2,
name: "프랑스",
capital: "워싱턴 DC",
region: "europe"
}, {
no: 3,
name: "영국",
capital: "워싱턴 DC",
region: "europe"
}, {
no: 4,
name: "중국",
capital: "워싱턴 DC",
region: "asia"
}, {
no: 5,
name: "태국",
capital: "워싱턴 DC",
region: "asia"
}, {
no: 6,
name: "모로코",
capital: "워싱턴 DC",
region: "africa"
}, {
no: 7,
name: "라오스",
capital: "워싱턴 DC",
region: "asia"
}, {
no: 8,
name: "베트남",
capital: "워싱턴 DC",
region: "asia"
}, {
no: 9,
name: "피지",
capital: "워싱턴 DC",
region: "oceania"
}, {
no: 10,
name: "자메이카",
capital: "워싱턴 DC",
region: "oceania"
}, {
no: 11,
name: "솔로몬제도",
capital: "워싱턴 DC",
region: "america"
}, {
no: 12,
name: "나미비아",
capital: "워싱턴 DC",
region: "africa"
}, {
no: 13,
name: "동티모르",
capital: "워싱턴 DC",
region: "asia"
}, {
no: 14,
name: "멕시코",
capital: "워싱턴 DC",
region: "america"
}, {
no: 15,
name: "베네수엘라",
capital: "워싱턴 DC",
region: "america"
}, {
no: 16,
name: "서사모아",
capital: "워싱턴 DC",
region: "oceania"
}, ]
}
var vmSum = new Vue({
el: '#exmaple',
data: model,
computed: {
filtered: function() {
var cname = this.countryname.trim()
return this.countries.filter(function(item, index) {
if (item.name.indexOf(cname) > -1) {
return true;
}
})
}
}
})
</script>
</body>







배열의 filter 메서드는 배열의 아이템 중 조건을 만족하는 아이템을 모아서 새로운 배열을 만들어 리턴하는 기능을 수행한다. filter 메서드는 함수를 인자로 전달하고 이 함수는 다시 두 개의 인자를 전달 받는데, 첫 번째 인자가 배열의 아이템(item) 두번째가 인덱스 번호이다.


계산형 속성인 filtered 함수는 this.countryname 속성값을 cname 변수에 할당하고 있다. 그 이유는 배열 객체의 filter 함수에 의해 호출되는 콜백함수 안에서는 this는 바깥쪽의 this와 다르기 때문이다. 콜백 함수 안쪽의 this는 Vue객체가 아니고 전역 객체를 참조한다. 


텍스트 필드에 국가명의 일부를 입력하면 필터링된 결과가 나타난다. 새롭게 렌더링되는 것이기 때문에 display 스타일 속성을 none으로 지정해서 보이지 않도록 하는 것과는 전혀 다르다. 

'Vue.js' 카테고리의 다른 글

[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16
[Vue.js] 기본 디렉티브  (0) 2018.12.15
vue.js 구조  (0) 2018.12.15
[Vue.js] 개발환경 구축  (1) 2018.12.15

Vue.js 기본 디렉티브



1) v-text, v-html 디렉티브

선언적 렌더링을 위해 html 요소 내부에 템플릿 표현식(콧수염 표현식:mustache Expression)만  사용할 수 있는건 아니다. 동일한 코드를 디렉티브라는 것을 이용해 표현해볼 수 있다.


<div id="simple">
<h2 v-text="message"></h2>
</div>



v-test, {{ }} : InnerText 속성에 연결된다. 태그 문자열을 HTML 인코딩하여 나타내기 때문에 화면에는 태그 문자열이 그대로 나타남

v-html : innerHtml 속성에 연결된. 태그 문자열을 파싱하여 화면에 나타냄


v-html 태그는 <Script> 태그 그대로 바인딩한다. 요즘 문제가되는 XSS(Cross Site Scripting)공격 등에 취약하기 때문에 꼭 필요한 경우가 아니라면 v-text를 사용하는것이 더 안전하다.




2) v-bind 디렉티브

이번에는 v-bind 디렉티브는 요소(Element)의 콘텐트 영역(시작 태그와 종료 태그 사이의 영역)을 설정하는 것이 아닌 요소 객체의 속성들을 바인딩하기 위해 사용한다. 


<body>
<div id="simple">
<input id="a" type="text" v-bind:value="message">
<br/>
<img v-bind:src="imagePath" />
</div>
<script tpye="text/javascript">
var model = {
message: 'v-bind 디렉티브',
imagePath: "http://sample.bmaster.kro.kr/photos/61.jpg"
};
var simple = new Vue({
el: '#simple',
data: model
})
</script>
</body>


v-bind 디렉티브를 통해서 html요소 객체의 속성이 변경되었음을 알 수 있다. v-bind 디렉티브를 매번 작성하는 것이 부담스럽다면 줄여 쓰는 방법이 있다. v-bind:src를 줄여 쓰면 :src로 작성해도 동작한다.




3) v-model 디렉티브


앞에 설명한 디렉티브는 모두 단방향 디렉티브이다. HTML 요소에서 값을 변경하더라도 모델 객체의 값이 바뀌지 않는데 v-model 디렉티브는 양방향 데이터 바인딩을 지원하는 디렉티브이다. 


<body>
<div id="simple">
<input id="a" type="text" v-model="name" placeholder="이름 입력하세요">
<br/> 입력된 이름 :
<h2 v-html="name"></h2>
</div>
<script tpye="text/javascript">
var simple = new Vue({
el: '#simple',
data: {
name: ''
},
})
</script>
</body>


실행 하여 텍스트박스에 이름을 입력하면 H2 태그 모델 객체의 속성이 변경되는걸 확인 할 수 있다.

v-model 디렉티브는 텍스트 박스뿐만 아니라 여러 가지 폼 필드에서도 사용할 수 있다. 여러개의 아이템을 선택할 수 있는 <input type="checkbox"/>나 <select multiple></select>의 경우 모델 객체의 배열 객체와 연결된다. 단일 아이템을 선택할 수 있는 <input type="radio">나 <select/>인 경우에는 모델 객체의 단일 값과 연결된다.




<body>
<div id="simple1">
<div>좋아하는 과일을 모두 골라주세요 : </div>
<input type="checkbox" value="1" v-model="fruits">사과,
<input type="checkbox" value="2" v-model="fruits">키위,
<input type="checkbox" value="3" v-model="fruits">포도,
<input type="checkbox" value="4" v-model="fruits">수박,
<input type="checkbox" value="5" v-model="fruits">참외,
</div>
<hr/>
<div id="simple2">
선택한 과일들 : <span v-html="fruits"></span>
</div>
<script tpye="text/javascript">
var model = {
fruits: []
}

var simple1 = new Vue({
el: '#simple1',
data: model
})

var simple2 = new Vue({
el: '#simple2',
data: model
})
</script>
</body>




위의 예제는 하나의 Model 객체를 두 개의 Vue 객체에서 참조하는 예제이다. simple1이 바인딩하는 Vue 객체는 v-model을 이용해 양방향 데이터 바인딩하여 사용자가 입력하는 값을 뮤모델 개체를 통해 model 객체의 fruits 배열 값을 즉시 바인딩한다. 


v-model 디렉티브는 몇가지 수식어(Modifier)를 지원한다. 수식어는 디렉티브에 특별한 기능을 추가하는 Vue.js 디렉티브의 문법 요소이다. v-model에서 사용할 수 있는 수식어는 아래와 같다.


lazy : 입력폼에 이벤트가 발생할 때 입력한 값을 데이터와 동기화한다. 텍스트 박스에서 입력 후 포커스가 이동하거나 할 때 데이터 옵션값이 변경된다.  <input type="text" v-model.lazy="name"


number : 숫자가 입력될 경우 number 타입의 값으로 자동 현변환된다.


trim : 이 수식어를 지정하면 문자열의 앞뒤 공백을 자동으로 제거한다.




4) v-show, v-if, v-else, v-else-if 디렉티브

v-if 디렉티브는 Vue객체의 data 속성 값에 따라 렌더링 여부를 결정할 수 있는 기능이다. 다른 랭귀지에서도 쓰이는 if문과 동일한 개념으로 생각해도 된다.


v-if와 비슷한 기능을 가진것이 v-show 디렉티브이다. 사실 v-if와 v-show 차이는 실제 렌더링 여부에 있다. v-if 디렉티브는 조건에 부함되지 않으면 렌더링을 하지 않는 반면 v-show는 일단 html 요소를 렌더링 후에 display 스타일 속성으로 화면에 보여줄지 여부를 결정한다.


sample.bmaster.kro.kr/img/error.png에서 이미지를 다운로드 후 작업하는 폴더에 images 하위 폴더를 생성 후 저장한다.



<body>
<div id="account">
예금액 : <input type="text" v-model="amount" />
<img v-if="amount < 0 " src="images/error.png"
title="마이너스는 허용하지 않음"
style="width:15px; height:15px; vertical-align:middle">
</div>
<hr/>
<script tpye="text/javascript">
var simple1 = new Vue({
el: '#account',
data: {
amount: 0
}
})
</script>
</body>


v-if는 조건에 부합되지 않으면 렌더링을 하지 않는다. 그렇기 때문에 자주 화면이 변경되는 부분에 대해서는 v-if 디렉티브보다는 v-show 디렉티브를 사용하는 것이 더 바람직한다. v-show 디렉티브는 조건을 만족하지 않을때 display 스타일 속성을 none으로 설정해 화면에 보이지 않도록 처리한다. 실제로는 렌더링을 하는 것.


v-if, v-else, v-else-if 는 다른 언어에서의 if ~ else if ~ else 구문과 동일한 기능이라고 생각하면 된다.

<body>
<div id="account">
예금액 : <input type="text" v-model="balance" />
<br/>
<span v-if="balance >= 1000000">Gold</span>
<span v-else-if="balance >= 500000">Silver</span>
<span v-else-if="balance >= 200000">Bronze</span>
<span v-else>Basic</span>
</div>
<hr/>
<script tpye="text/javascript">
var simple1 = new Vue({
el: '#account',
data: {
balance: 0
}
})
</script>
</body>





5) v-for 디렉티브


JavaScript의 for문과 유사하다.


<html>
<head>
<meta charset="utf-8">
<title>hello vue.js</title>
<style>
#list {
width: 400px;
border: 1px solid black;
border-collapse: collapse;
}
#list td,
#list th {
border: 1px solid black;
text-align: center;
}
#list>thead>tr {
color: yellow;
background-color: purple;
}
</style>
<script src="http://unpkg.com/vue@2.5.16/dist/vue.js"></script>
</head>

<body>
<div id="exmaple">
<table id="list">
<tr>
<th>번호</th>
<th>이름</th>
<th>전화번호</th>
<th>주소</tr>
</tr>
<tbody id="contacts">
<tr v-for="contact in contacts">
<td>{{ contact.no }}</td>
<td>{{ contact.name }}</td>
<td>{{ contact.tel }}</td>
<td>{{ contact.address }}</td>
</tr>
</tbody>
</table>
</div>
<script tpye="text/javascript">
var model = {
"pageno": 1,
"pagesize": 10,
"totalcount": 100,
"contacts": [{
"no": 100,
"name": "설현",
"tel": "010-1234-1234",
"address": "서울"
}, {
"no": 100,
"name": "혜리",
"tel": "010-1234-1234",
"address": "서울"
}, {
"no": 100,
"name": "하니",
"tel": "010-1234-1234",
"address": "경기"
}, {
"no": 100,
"name": "성소",
"tel": "010-1234-1234",
"address": "제주"
}, {
"no": 100,
"name": "설현",
"tel": "010-1234-1234",
"address": "서울"
}, {
"no": 100,
"name": "혜리",
"tel": "010-1234-1234",
"address": "서울"
}, {
"no": 100,
"name": "하니",
"tel": "010-1234-1234",
"address": "경기"
}, {
"no": 100,
"name": "성소",
"tel": "010-1234-1234",
"address": "제주"
}, {
"no": 100,
"name": "설현",
"tel": "010-1234-1234",
"address": "서울"
}, {
"no": 100,
"name": "혜리",
"tel": "010-1234-1234",
"address": "서울"
}]
}
var simple1 = new Vue({
el: '#exmaple',
data: model
})
</script>
</body>
</html>


model 객체 내부에는 contacts라는 이름의 배열 데이터를 가지고 있는데 이 데티러르 여러번 반복적으로 화면에 나타낸것이다. v-for 의 구문은 원본 데이터가 어떤 형식인가에 따라 사용방법이 조금씩 달라진다ㅣ 배열 또는 유사 배열인 경우에는 contact.name 처럼 작성한다. 


원본 데이터가 객체 인경우에는 조금 달라진다. 객체인 경우는 키를 이용해 값을 접근하는 HashMap 구조 이기 때문에 Key, Value를 얻어낼 수 있는 구조를 사용한다.


<body>
<div id="exmaple">
<select id="regions">
<option disabled="disabled" selected> 지역을 선택하세요 </option>
<option v-for="(val, key) in regions" v-bind:value="key">{{val}}</option>
</select>
</div>
<script tpye="text/javascript">
var regions = {
"A": "Asia",
"B": "America",
"C": "Europe",
"D": "Africa",
"E": "Oceania",
}
var simple1 = new Vue({
el: '#exmaple',
data: {
regions: regions
}
})
</script>
</body>


이번 예제의 v-for 형태는 조금 다른 것을 확인 할 수 있다. (val, key)로 작성되어 있는데 var에는 텍스트가 전달되고, key에는 지역코드가 전달되면서 반복적으로 <option>요소들을 만들어 낸다.


만일 인덱스를 표현해야 한다면 다음과 같이 표현할 수 있다.


<tr v-for="(contact, index) in contacts"> ... </tr>

<option v-for="(val, key, index) in regions" ...> </option>





6) 기타 디렉티브


v-pre -> v-pre는 HTML요소에 대한 컴파일을 수행하지 않는다.


<body>
<div id="exmaple">
<span v-pre>{{ message }}</span>
</div>
<script tpye="text/javascript">
var simple1 = new Vue({
el: '#exmaple',
data: {
message: "Hello World"
}
})
</script>
</body>


위의 예제는 v-pre 디렉티브를 사용한 예제이다. 실제 실행을 해보면 Hello World 문자열이 찍히지 않고 콧수염 표현식 그대로 {{ message }}가 화면에 출력된 모습을 볼 수 있다. 앞서 설명한것 처럼 v-pre는 HTML 요소에 대한 컴파일을 수행하지 않는다.



v-once-> v-once는 HTML요소를 단 한 번만 렌더링하도록 설정한다.


<body>
<div id="exmaple">
<span v-once>{{ message }}</span>
</div>
<script tpye="text/javascript">
var simple1 = new Vue({
el: '#exmaple',
data: {
message: "Hello World"
}
})
</script>
</body>


실제로 실행을 하면 v-pre와는 다르게 Hello World가 찍힌 모습을 확인 할 수있다. v-once 디렉티브는 처음 한번만 렌더링을 수행한다. 그렇기 때문에 Vue 인스턴스의 데이터를 변경하더라도 렌더링을 수행하지 않는다. 초깃값이 주어지면 변경되지 안는 UI를 만들때 사용할 수 있다.

'Vue.js' 카테고리의 다른 글

[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16
[Vue.js] computed Property (계산형 속성)  (0) 2018.12.15
vue.js 구조  (0) 2018.12.15
[Vue.js] 개발환경 구축  (1) 2018.12.15

vue 예제 분석


<html>

<head>
<meta charset="utf-8">
<title>hello vue.js</title>
<script src="http://unpkg.com/vue@2.5.16/dist/vue.js"></script>
</head>

<body>
<div id="simple">
<h2>{{message}}</h2>
</div>
<script tpye="text/javascript">
var model = {
message: 'Hello Vue.js World!'
};

var simple = new Vue({
el: '#simple',
data: model
})
</script>
</body>

</html>


Model 객체는 변수명 그대로 모델 객체이다.

데이터를 가지고 있음


SImple 객체는 Vue 객체이자 뷰모델 객체이다. 

Vue 객체의 el 속성의 html 요소를 나타낸다. 또한 data 속성을 모델 객체를 참조한다.

vue 객체가 html 요소와 데이터를 참조하고 있다. 이제 데이터가 변경되면 뷰모델 객체는 즉시 html 요소에 반영시킨다.


html 요소에서는 {{ }}과 같은 콧수명르 닮은 모양의 템플릿 표현식을 사용해 선언ㅇ적으로 html dom에 데이터를 렌더링한다. 뷰모델 객체의 데이터 속성에서 해당 값을 이 위치에 나타낸다. 콧수염을 닮았다고 해서 콧수염 표현식(Mustache Expression)이 라고도 부르며, 문자열을 덧붙인다는 의미로 보간법(Interpolation)이라고도 한다.



이코드만으로도 모든 작업은 반응형으로 이뤄지는다. 모델을 벼경하면 뷰모델 객체를 통해 html dom에 즉시 변경된다.















'Vue.js' 카테고리의 다른 글

[Vue.js] Event 처리  (0) 2018.12.17
[Vue.js] Vue instance (뷰 인스턴스)  (0) 2018.12.16
[Vue.js] computed Property (계산형 속성)  (0) 2018.12.15
[Vue.js] 기본 디렉티브  (0) 2018.12.15
[Vue.js] 개발환경 구축  (1) 2018.12.15

+ Recent posts