지난 글에서는 목적과 구현을 분리했다. 그렇다면, 자연스럽게 함수가 꽤 많아졌을 것이다. 데이터에 대한 가공을 함수에 전부 위임했기 때문이다. 그렇지 않다면 유사한 로직들을 함수에 위임할 수 있도록 잘게 쪼개는 연습이 필요하다. 이는 리팩토링의 기초 단계이기도 하다. 코드는 리팩터링 책의 예제를 참고로 만들었다.
<template>
<div>
<div>청구 내역 (고객명: {{ invoice.customer }} )</div>
<div>{{ result }}</div>
<div>총액: {{ totalAmount / 100 }}</div>
<div>적립 포인트: {{ volumeCredits }} 점</div>
</div>
</template>
<script>
export default {
data() {
return {
plays: {},
invoice: {},
totalAmount: 0,
volumeCredits: 0,
result: ''
}
},
created () {
this.init()
},
methods: {
init() {
this.plays = plays; // axios 통신 생략
this.invoice = invoice; // axios 통신 생략
for (let perf of this.invoice.performances) {
const play = this.plays[perf.playID];
let thisAmount = 0;
switch (play.type) {
case "tragedy": //비극
thisAmount = 40000;
if (perf.audience > 30) {
thisAmount += 1000 * (perf.audience - 30);
}
break;
case "comedy": //희극
thisAmount = 30000;
if (perf.audience > 20) {
thisAmount += 1000 + 500 * (perf.audience - 20);
}
thisAmount += 300 * perf.audience;
break;
default:
throw new Error(`알 수 없는 장르: ${play.type}`);
}
// 포인트를 적립한다.
this.volumeCredits += Math.max(perf.audience - 30, 0);
// 희극 관객 5명마다 추가 포인트를 제공한다.
if ("comedy" === play.type) {
this.volumeCredits += Math.floor(perf.audience / 5);
}
// 청구 내역을 출력한다.
this.result += `${play.name}: ${thisAmount / 100} (${
perf.audience
}석)\n`;
this.totalAmount += thisAmount;
}
}
}
}
</script>
위의 예제를 쪼개보면 아래와 같이 바꿀 수 있을 것이다. 리팩터링 교제의 코드와는 약간의 차이가 있을 수 있다. 코드의 어떤 부분이 어떤 방식으로 바뀌었는지를 참고하면 될듯하다. 함수 쪼개기에 대한 자세한 내용은 생략하겠다.
<script>
export default {
methods: {
init() {
this.plays = plays; // axios 통신 생략
this.invoice = invoice; // axios 통신 생략
let volumeCredits = 0;
let total = 0;
for (let perf of this.invoice.performances) {
const play = this.getPlay(perf);
// 청구 내역을 출력한다.
this.result += `${play.name}: ${this.getAmount(perf) / 100} (${
perf.audience
}석)\n`;
total += this.getAmount(perf);
volumeCredits += this.setVolume(perf);
}
this.totalVolumeCredits = volumeCredits;
this.totalAmount = total;
},
setVolume(perf) {
let result = 0
// 포인트를 적립한다.
result += Math.max(perf.audience - 30, 0);
// 희극 관객 5명마다 추가 포인트를 제공한다.
if ("comedy" === this.getPlay(perf).type) {
result += Math.floor(perf.audience / 5);
}
return result;
},
getPlay(perf) {
return this.plays[perf.playID]
},
getAmount(pert) {
const { audience } = pert;
const { type } = this.getPlay(pert);
let result = 0;
switch (type) {
case "tragedy": //비극
result = 40000;
if (audience > 30) result += 1000 * (audience - 30);
break;
case "comedy": //희극
result = 30000;
if (audience > 20) result += 1000 + 500 * (audience - 20);
result += 300 * audience;
break;
default:
throw new Error(`알 수 없는 장르: ${type}`);
}
return result;
}
}
}
</script>
이렇게, 함수를 쪼개다보면 메소드가 많아질 수 있다. 이럴 때 메소드의 용도를 잘 구분하면, 코드의 복잡도를 줄일 수 있다. methods 외에 computed, filter 등이 있는데, filter는 사용해보지 않는 분들도 있을 것 같아서 소개를 해보려 한다.
filter는 주로 표시되는 포맷이 변경될 때, 사용이 가능하다. 주로 이런 경우에는 util을 만들어서 사용을 하는 경우가 많지만, 특정 페이지에서 사용이 많은 경우에는 methods로 만들기도 한다. 예제 코드에는 totalAmount / 100
으로 나눠주는 형식으로 지정되어 있지만, 예를 들어 totalAmount 값이 0이 들어올 경우 해당 코드는 잘못된 값을 노출한다. 이럴 때 filter를 사용한다.
<template>
<div>총액: {{ totalAmount | divide }}</div>
</template>
<script>
export default {
filters: {
divide: function (value) {
if (!value) return ''
return value / 100 // return이 반드시 있어야 한다.
}
}
}
</script>
이렇게 사용하면 데이터의 단순 표시를 위한 가공 메소드를 줄일 수 있고, 데이터 표시할 때의 예외 처리도 쉬워진다.
vue3에는 지원하지 않으니, util을 만들거나 computed등을 활용하길 바란다.
공식 문서에는 computed와 watch가 묵여서 나오지만, 사실 methods와 비교해 봐야 한다. computed를 data에 대한 가공이 필요할 때 사용한다. computed는 사실 많이 사용할 테지만, 두 가지만 참고하면 될 것 같다.
- parameter가 없어야 한다. parameter가 있을 때는 methods 사용 권장
- 값이 바뀌지 않으면 실행되지 않는다. (성능상 이점)
<template>
<div>청구 내역 (고객명: {{ customer }} )</div>
</template>
<script>
export default {
computed: {
customer() {
return this.invoice.customer; // return이 반드시 있어야 한다.
}
}
}
</script>
사실 filter에서 예시든 경우에도 computed로 처리가 가능하다. 둘의 차이는 filter는 값을 넘긴다는 점, computed는 data를 직접 가능한다는 점이다. 잘 참고해서, 너무 많은 메소드로 인해 혼동되는 사례가 없으면 좋겠다.
글을 꾸준히 쓴다는 게 참 쉽지 않다. 일주일에 한 편씩 쓰려고 하는데, 글 쓰는데 투자하는 시간에 비해 검증해야 할 것들이 많아지고 있다. ㅠ_ㅠ.. 그래도 누군가에게는 도움을 줄 수 있겠지..
다음 글의 주제는 "디렉토리 구조"에 관한 글이다.
잘봤습니다!