VueJS | Stock-Trader Project Tutorial (4)

이 포스팅은 Max의 Vuejs 강좌 내용을 정리한 것이고, VueJS의 개념을 익히고 연습해보기 위한 튜토리얼을 다룬 글 입니다.


이번 포스팅은 Stock-Trader Project Tutorial의 마지막 편입니다.
Filter, Animation 을 추가하고 vue-resource를 활용해 Firebase와 연결해서 데이터를 저장하고 불러오는 기능까지 구현하여 이번 튜토리얼을 마치려고 합니다.

1. Filter 기능 추가 하기

1. funds 값 표시하기

  • Filter 기능을 적용하기 위해 funds state를 표시할 요소를 Header.vueHome.vue에 추가합니다.
src/components/Header.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<!--...생략...-->
<ul class="nav navbar-nav">
<router-link to="/portfolio" activeClass="active" tag="li"><a>Portfolio</a></router-link>
<router-link to="/stocks" activeClass="active" tag="li"><a>Stocks</a></router-link>
</ul>
<!-- Funds 요소 추가 Start -->
<strong class="navbar-text navbar-right">Funds: {{ funds }}</strong>
<!-- Funds 요소 추가 End -->
<ul class="nav navbar-nav navbar-right">
<li><a href="#" @click="endDay">End Day</a></li>
<!--...생략...-->
</template>

<script>
export default {
computed: {
funds() {
return this.$store.getters.funds;
}
}
}
</script>
src/components/Home.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<h1>Trade or View your Portfolio</h1>
<h6>You may Save & Load your Data</h6>
<h6>Click on 'End Day' to begin a New Day!</h6>
<hr>
<p>Your Funds: {{ funds }}</p>
</div>
</template>

<script>
export default {
computed: {
funds() {
return this.$store.getters.funds;
}
}
}
</script>

2. filter 등록하기

  • main.js에 프로젝트 어디에서나 사용 할 수 있도록 filter를 등록해줍니다.
  • 사용할 filter의 이름은 currency이고 변수 앞에 $ 표시를 붙여주는 단순한 filter입니다.
src/main.js
1
2
3
4
5
6
7
8
9
//...생략...

Vue.use(VueRouter);

Vue.filter('currency', value => {
return '$' + value.toLocaleString();
});

//...생략...

3. filter 사용하기

  • 1번 과정에서 Header.vueHome.vue에 추가한 funds 요소 안에 아래 코드와 같이 파이프라인 뒤에 사용할 필터 이름을 추가해줍니다.
src/components/Header.vue
1
2
3
<!--...생략...-->
<strong class="navbar-text navbar-right">Funds: {{ funds | currency }}</strong>
<!--...생략...-->
src/components/Home.vue
1
2
3
<!--...생략...-->
<p>Your Funds: {{ funds | currency }}</p>
<!--...생략...-->

2. 주문량 제한하는 기능 추가하기

  • Stocks 페이지나 Portfolio 페이지에서 각 Stock 컴포넌트를 통해 사거나 파는 액션을 실행하면서 fundsorder주문량에 따라서 가능한 범위 내에서만 주문할 수 있도록 button 요소에 제한하는 기능을 추가해봅니다.
  • insufficientFunds, insufficientQuantity 메소드를 만들고 buttoninput요소에 추가해줍니다.
  • 상태가 변했을 때 스타일이 변할수 있도록 설정한 클래스를 <style> 에 추가해줍니다.
    • <style scpoed> 와 같이 style 태그 안에 scoped라는 속성을 추가하면 해당 컴포넌트의 요소들에만 영향을 주고 다른 컴포넌트의 요소들에는 전혀 영향이 가지 않도록 할 수 있습니다.
src/components/stocks/Stock.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template>
<!--...생략...-->
<div class="pull-left">
<input
type="number"
class="form-control"
placeholder="Quantity"
v-model.number="quantity"
:class="{danger: insufficientFunds}"
>
</div>
<div class="pull-right">
<button
class="btn btn-success"
@click="buyStock"
:disabled="insufficientFunds || quantity <= 0 || !Number.isInteger(quantity)"
>{{ insufficientFunds ? 'Too Much' : 'Buy'}}
</button>
</div>
<!--...생략...-->
</template>

<style scpoed>
.danger,
.danger:focus {
border: 1px solid red;
}
</style>

<script>
//...생략...
computed: {
funds() {
return this.$store.getters.funds;
},
insufficientFunds() {
return this.quantity * this.propStock.price > this.funds;
}
},
//...생략...
</script>
src/components/portfolio/Stock.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template>
<!--...생략...-->
<div class="pull-left">
<input
type="number"
class="form-control"
placeholder="Quantity"
v-model.number="quantity"
:class="{danger: insufficientQuantity}"
>
</div>
<div class="pull-right">
<button
class="btn btn-success"
@click="sellStock"
:disabled="insufficientQuantity || quantity <= 0 || !Number.isInteger(quantity)"
>{{insufficientQuantity ? 'Too Much' : 'Sell'}}
</button>
</div>
<!--...생략...-->
</template>

<style scpoed>
.danger,
.danger:focus {
border: 1px solid red;
}
</style>

<script>
//...생략...
computed: {
funds() {
return this.$store.getters.funds;
},
insufficientQuantity() {
return this.quantity > this.propStock.quantity;
}
},
//...생략...
</script>

3. End Day 랜덤 시세 변경 기능 추가하기

1. RND_STOCKS mutation 만들기

  • 이전 포스팅에서는 비어 두웠던 RND_STOCKS mutation에 그냥 적당한 선에서 각 stockprice 값이 랜덤하게 변경할 수 있도록 기능을 추가해 줍니다.
src/store/modules/stocks.js
1
2
3
4
5
6
7
// ...생략...
'RND_STOCKS' (state) {
state.stocks.forEach( stock => {
stock.price = Math.round(stock.price * (1 + Math.random() - 0.5));
});
}
// ...생략...

2. endDay 기능 추가하기

  • RND_STOCKS mutation을 실행하는 action인 randomizeStocks을 활용해서 클릭했을 때 실행 할 메소드를 추가해줍니다.
src/components/Header.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<!--...생략...-->
<ul class="nav navbar-nav navbar-right">
<li><a href="#" @click="endDay">End Day</a></li>
<!--...생략...-->
</template>

<script>
import {mapActions} from 'vuex';

export default {
computed: {
funds() {
return this.$store.getters.funds;
}
},
methods: {
...mapActions([
'randomizeStocks'
]),
endDay() {
this.randomizeStocks();
}
}
}
</script>

4. Animation 효과 추가하기

  • VueJS에서는 Animation 효과를 좀 더 명확하고 구조적으로 적용할 수 있는 transition이라는 요소가 있습니다.
    • 자세한 API는 이 링크를 참조해주세요. Transition
  • 먼저 Animation 효과를 적용하기 위해서는 적용할 요소를 transition 요소로 감싸주고 namemode를 지정해줍니다.
  • 그러면 지정된 transition요소의 name 뒤로 -enter-active, -leave-active, 등의 포스트픽스가 붙는 이름의 클래스 안에 각 상태에 맞는 효과를 추가해줍니다.
    • 관련된 보다 자세한 사항들도 위 링크를 참주해주세요.
src/App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<div class="container">
<app-header></app-header>
<div class="row">
<div class="col-md-12">
<!--여기에 animation 이 적용되는 transition 영역을 만들어줍니다.-->
<transition name="slide" mode="out-in">
<router-view></router-view>
</transition>
</div>
</div>
</div>
</template>

<!--...생략...-->

<style>
body {
padding: 30px;
}

.slide-enter-active {
animation: slide-in 200ms ease-out forwards;
}

.slide-leave-active {
animation: slide-out 200ms ease-out forwards;
}

@keyframes slide-in {
from {
transform: translateY(-30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

@keyframes slide-out {
from {
transform: translateY(0px);
opacity: 1;
}
to {
transform: translateY(-30px);
opacity: 0;
}
}
</style>

위의 설정을 완료하면 이와 같은 Animation 효과를 확인하실 수 있습니다.


5. Firebase에 데이타 저장하고 불러오기

마지막으로 대망의 Firebase와 연결하기 입니다.
vue-resource 설정을 추가하기 이전에 save, load 기능을 사용할 dropdown 요소를 열었다 닫았다 할 수 있도록 간단히 세팅해줍니다.

src/components/Header.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<!--...생략...-->
<li class="dropdown"
:class="{open: isDropDownOpen}"
@click="isDropDownOpen = !isDropDownOpen">
<!--...생략...-->
</template>

<script>
//...생략...
data() {
return {
isDropDownOpen: false
}
},
//...생략...
</script>

1. Firebase 초기 세팅하기

먼저 Firebase 계정을 생성하고 프로젝트 초기 세팅을 하는 부분은 firebase 초기 세팅 이곳을 참고해주시기 바랍니다.

2. main.jsvue-resourceFirebase 추가하기

  • 가장 먼저 터미널에서 npm install --save vue-resource 를 실행하여 vue-resource 모듈을 설치합니다.
  • 그리고 아래 코드와 같이 main.jsvue-resource 모듈을 추가하고 DB의 root 주소를 등록해주면 기본 설정은 모두 완료된것입니다. 참 간단하죠~^^
src/main.js
1
2
3
4
5
6
7
//...생략...
import VueResource from 'vue-resource';
//...생략...
Vue.use(VueResource);

Vue.http.options.root = 'https://본인 Firebase DB 주소';
//...생략...

3. 데이타 저장하기

  • saveData 메소드에서 DB에 현재 저장할 데이터들을 추가해줍니다.
    • vue-resource의 API는 this.$http.put 와 같이 this.$http 뒤에 필요한 메소드를 붙혀서 사용할 수 있습니다.
    • put메소드를 사용할 때는 data.json 테이블이름.데이타타입 과 같이 데이타를 저장할 위치와 형태를 결정해주고 추가할 데이타를 변수로 넘겨주면 됩니다.
  • dropdown-menu 에서 Save Data 메뉴에 saveData 메소드를 추가해줍니다.
src/components/Header.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<!--...생략...-->
<ul class="dropdown-menu">
<li><a href="#" @click="saveData">Save Data</a></li>
<li><a href="#" @click="">Load Data</a></li>
</ul>
<!--...생략...-->
</template>

<script>
//...생략...
methods: {
//...생략...
saveData() {
const data = {
funds: this.$store.getters.funds,
stockPortfolio: this.$store.getters.stockPortfolio,
stocks: this.$store.getters.stocks
};
this.$http.put('data.json', data);
},
//...생략...
</script>

4. 데이타 불러오기

  • 초기에 SET_STOCKS mutation으로 고정된 초기값으로 데이타를 불러오는 부분은 있었지만 새로 생성된 portfolio 데이타를 불러와서 초기 설정 해주는 부분이 없었습니다. 그래서 portfolio.js 모듈 안에 portfolio 데이타를 설정해주는 SET_PORTFOLIO mutation을 추가해줍니다.
src/store/modules/portfolio.js
1
2
3
4
5
6
// ...생략...
'SET_PORTFOLIO' (state, portfolio) {
state.funds = portfolio.funds;
state.stocks = portfolio.stockPortfolio ? portfolio.stockPortfolio : [];
}
// ...생략...
  • 이 프로젝트의 DB에서 데이타를 불러오는 것은 Stocks, Portfolio 와 관련된 모든 데이타를 불러오는 것이기 때문에 어디서나 공통으로 사용하는 loadData action을 따로 생성해줍니다.
  • 이렇게 공통적으로 사용해야 하는 기능은 action만 따로 모아서 등록을 해놓을 수도 있습니다.
  • 컴포넌트 환경과는 다르게 JS에서 vue-resource를 사용할 때는 Vue인스턴스를 활용해서 Vue.http 뒤에 메소드를 호출하는 방식으로 사용합니다.
  • loadData action 에서는 저장하기에서 데이타를 저장했던 위치와 형태 그대로 get 메소드로 데이타를 호출하고 데이타를 각 컴포넌트에서 사용하는 형태로 변환하여 전달해줍니다.
src/store/actions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Vue from 'vue';

export const loadData = ({commit}) => {
Vue.http.get('data.json')
.then( response => response.json() )
.then( data => {
if(data) {
const stocks = data.stocks;
const funds = data.funds;
const stockPortfolio = data.stockPortfolio;

const portfolio = {
stockPortfolio,
funds
};

commit('SET_STOCKS', stocks);
commit('SET_PORTFOLIO', portfolio);
}
});
};
  • 그리고 생상한 actions.jsstore.js에 추가해줍니다.
src/store/store.js
1
2
3
4
5
6
7
8
9
10
11
12
// ...생략...
import * as actions from './actions'; // actions.js 추가를 위한분

Vue.use(Vuex);

export default new Vuex.Store({
actions, // actions.js 추가를 위한분부분
modules: {
stocks,
portfolio
}
});
  • 마지막으로 등록된 loadData action을 Header 컴포넌트에 추가해줍니다.
  • 아래 코드에서 기존 코드와 조금 바뀐점은 mapActions 헬퍼함수 안에 action의 key 값을 설정해주는 부분입니다.
    • 기존 코드에서는 ...mapActions([]) 배열 안에 필요한 action을 등록해주었었는데 그래서 key 값을 따로 설정해줄 수는 없었습니다.
    • 프로젝트에 기능이 점점 추가되고 action 들이 많아지기 시작하면 이름들이 중복될 수도 있고 헷갈릴수도 있기 때문에 ...mapActions({}) 처럼 객체를 사용하면 등록한 action의 key값을 별도로 정의할수도 있습니다. 그리고 해당 컴포넌트 내에서는 그 key로 해당 action을 사용하면 됩니다.
src/components/Header.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<!--...생략...-->
<ul class="dropdown-menu">
<li><a href="#" @click="saveData">Save Data</a></li>
<li><a href="#" @click="loadData">Load Data</a></li>
</ul>
<!--...생략...-->
</template>

<script>
// ...생략...
methods: {
...mapActions({
randomizeStocks: 'randomizeStocks',
fetchData: 'loadData'
}),
endDay() {
this.randomizeStocks();
},
saveData() {
const data = {
funds: this.$store.getters.funds,
stockPortfolio: this.$store.getters.stockPortfolio,
stocks: this.$store.getters.stocks
};
this.$http.put('data.json', data);
},
loadData() {
this.fetchData();
}
}
}
</script>

5. 페이지 접속시 데이타 불러오기

  • Load Data 버튼이 있기는 하지만 앱에 접속 했을 때 바로 이전 데이타를 불러오는 것이 자연스럽기 때문에 앱 접속시 이전 데이타를 바로 불러올 수 있도록 App.vueloadData action을 추가해줍니다.
  • VueJS는 컴포넌트의 script 영역에서 created 라이프사이클 훅을 사용해서 요소들이 렌더링이 되기 전에 미리 데이타를 로딩 해놓을 수 있습니다. created 외에도 다양한 라이프사이클을 활용 할 수 있습니다.
src/App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--...생략...-->
<script>
import {mapActions} from 'vuex';
import Header from './components/Header.vue';

export default {
methods: {
...mapActions({
fetchData: 'loadData'
})
},
components: {
'appHeader': Header
},
created() {
this.fetchData();
}
}
</script>
<!--...생략...-->

VueJS 라이프사이클


기본적인 구조를 잡고 컴포넌트를 만드는 것 부터 vue-router, vuex, vue-resource 까지 VueJS 를 활용하여 SPA를 만드는 과정을 소개해보았습니다. 깊이 있는 내용은 많이 다루지 못했지만 전체적인 흐름으르 한번 체험해보면 VueJS를 학습하는데 조금 도움이 되지 않을까 싶어서 작성하기 시작한 포스팅이었습니다. 한분에게라도 도움이 되는 글이 되었다면 감사할것 같습니다. 좀 더 필요하신 부분이나 보완할 점이 있다면 댓글로 의견 남겨주시기 바랍니다. 감사합니다.

git clone https://github.com/hanwong/vue-stock-trader.git 으로 프로젝트를 다운로드하고 git checkout step07 로 프로젝트의 최종 결과물을 확인 하실 수 있습니다.


- 끝 -

Share Comments