프론트엔드 프레임워크 뷰(Vue)의 기본에 충실하면 프레임워크 자체의 디자인패턴이랑과 별개로 우리가 작성하는 코드(from1)는 모델-뷰-컨트롤러(‣ Model-View-Controller (MVC)) 패턴에 맞게 분리된다(sup1:분리해서 보는 것이 맞긴 한지, MVC가 엄밀하게 지켜지곤 하는건지).
예를 들어 뷰(View)에 버튼과 버튼이 몇 번 클릭되었는지를 표시하는 상황을 떠올려 보자. <template> 태그 안에 버튼이 정의되어 있을 것이다. 버튼이 클릭되면 이벤트 바인드를 통해 컨트롤러(Controller) 메서드를 호출한다. 호출된 컨트롤러는 버튼이 몇 번 클릭되었는지에 대한 상태(State, 혹은 Model)를 1만큼 늘린다. 뷰(View)는 상태(State, 혹은 Model)의 변화를 감지하고 UI에 표시하는 버튼 클릭 횟수를 업데이트한다.’
// View Area ----------------------------
<template>
...
<button @click="onClickController"> {{ state }} </button>
...
</template>
<script>
export default {
// Model Area ---------------------------
data() {
state: 0
}
// Controller Area ----------------------
method {
onClickController() {
return this.state += 1
}
}
};
</script>
JavaScript
복사
이렇게 뷰-모델-컨트롤러를 잘 분리하라는 원칙은 모범 사례에도 나타나 있다. 템플릿(template) 속성에서 복잡한 연산을 하거나 메소드(method) 속성을 더럽히지 말고 컴퓨티드(comuted) 속성으로 분리하라는 말이 있다(from2). 메소드는 모델이나 뷰 영역이 아니라 컨트롤러 영역이기 때문이다(sup1).
// View Area ----------------------------
<template>
...
<button @click="onClickController"> {{ cntString }} </button>
...
</template>
<script>
export default {
computed: {
cntString: function() {
return this.state.toString() + "회 클릭됨"
}
}
// Model Area ---------------------------
data() {
state: 0
}
// Controller Area ----------------------
method {
onClickController() {
return this.state += 1
}
}
};
</script>
JavaScript
복사
MVC 패턴을 이해했다면 조금 더 복잡해진 아래 코드에 computed 프로퍼티를 이용해 컨트롤과 모델을 분리해 보자. 아래 코드는 리팩터링 전 코드이다.
<template>
<div class="compare-box-outer">
<h1 id="compare-box-title">Which one is better?</h1>
<b-card-group v-show="showCardGroup">
<b-card :title="getPercentage('A')" class="rounded-2" :class="{ selected: selectedCard == 'A' }" :img-src="imgSrcA" img-alt="ImageA" img-top>
<b-card-text> </b-card-text>
<template #footer>
<small class="text-muted"></small>
</template>
<div>
<b-button v-if="!isButtonLocked" variant="outline-primary" @click="lockAllButton('A')">❤️ 이게 더 좋아요!</b-button>
<b-button v-else disabled :variant="getLockButton('A')">❤️ 이게 더 좋아요!</b-button>
</div>
</b-card>
<div class="interval"></div>
<b-card :title="getPercentage('B')" class="rounded-2" :class="{ selected: selectedCard == 'B' }" :img-src="imgSrcB" img-alt="ImageB" img-top>
<b-card-text> </b-card-text>
<template #footer>
<small class="text-muted"></small>
</template>
<div>
<b-button v-if="!isButtonLocked" variant="outline-primary" @click="lockAllButton('B')">❤️ 이게 더 좋아요!</b-button>
<b-button v-else disabled :variant="getLockButton('B')">❤️ 이게 더 좋아요!</b-button>
</div>
</b-card>
</b-card-group>
</div>
</template>
<script>
import { EventBus } from '@/main.js'
export default {
data() {
return {
imgSrcA: "https://placekitten.com/g/1080/1350",
imgSrcB: "https://placekitten.com/g/1080/1350",
percentageLikeA: 68,
percentageLikeB: 32,
isButtonLocked: false,
selectedCard: "",
showCardGroup: true,
};
},
watch: {
isButtonLocked() {
if (this.isButtonLocked){
window.setTimeout(() => {
this.showCardGroup = false;
}, 10);
}
}
},
methods: {
lockAllButton(AorB) {
this.isButtonLocked = true;
this.selectedCard = AorB;
},
getLockButton(AorB){
if (AorB == this.selectedCard){
return 'primary';
} else {
return 'outline-primary';
}
},
getPercentage(AorB){
if (this.isButtonLocked) {
if (AorB == 'A') {
return this.percentageLikeA.toString() + "%"
} else {
return this.percentageLikeB.toString() + "%"
}
} else {
return AorB
}
},
onAfterLeave(){
EventBus.$emit('singleCompareEnd', true)
}
},
};
</script>
JavaScript
복사
위 코드를 리팩터링하면 아래와 같이 재구성된다.
<template>
<div class="compare-box-outer">
<h1 id="compare-box-title">Which one is better?</h1>
<b-card-group v-show="showCardGroup">
<b-card :title="cardApercentageString" class="rounded-2" :class="{ selected: selectedCard == 'A' }" :img-src="imgSrcA" img-alt="ImageA" img-top>
<b-card-text> </b-card-text>
<template #footer>
<small class="text-muted"></small>
</template>
<div>
<b-button v-if="!isButtonLocked" variant="outline-primary" @click="lockAllButton('A')">❤️ 이게 더 좋아요!</b-button>
<b-button v-else disabled :variant="conditionalLockedButtonA">❤️ 이게 좋아요!</b-button>
</div>
</b-card>
<div class="interval"></div>
<b-card :title="cardBpercentageString" class="rounded-2" :class="{ selected: selectedCard == 'B' }" :img-src="imgSrcB" img-alt="ImageB" img-top>
<b-card-text> </b-card-text>
<template #footer>
<small class="text-muted"></small>
</template>
<div>
<b-button v-if="!isButtonLocked" variant="outline-primary" @click="lockAllButton('B')">❤️ 이게 더 좋아요!</b-button>
<b-button v-else disabled :variant="conditionalLockedButtonB">❤️ 이게 좋아요!</b-button>
</div>
</b-card>
</b-card-group>
</div>
</template>
<script>
import { EventBus } from '@/main.js'
export default {
computed: {
cardApercentageString: function() {
if (this.isButtonLocked) {
return this.percentageLikeA.toString() + "%"
} else { return 'A' }
},
cardBpercentageString: function() {
if (this.isButtonLocked) {
return this.percentageLikeA.toString() + "%"
} else { return 'B' }
},
conditionalLockedButtonA: function() {
if (this.selectedCard == 'A') {
return 'primary'
} else { return 'outline-primary' }
},
conditionalLockedButtonB: function() {
if (this.selectedCard == 'B') {
return 'primary'
} else { return 'outline-primary' }
}
},
data() {
return {
imgSrcA: "https://placekitten.com/g/1080/1350",
imgSrcB: "https://placekitten.com/g/1080/1350",
percentageLikeA: 68,
percentageLikeB: 32,
isButtonLocked: false,
selectedCard: "",
showCardGroup: true,
};
},
watch: {
isButtonLocked() {
if (this.isButtonLocked){
window.setTimeout(() => {
this.showCardGroup = false;
}, 10);
}
}
},
methods: {
lockAllButton(AorB) {
this.isButtonLocked = true;
this.selectedCard = AorB;
},
onAfterLeave(){
EventBus.$emit('singleCompareEnd', true)
}
},
};
</script>
JavaScript
복사
parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료들.
1.
None
Quiz
from : 과거의 어떤 생각이 이 생각을 만들었는가?
2.
supplementary : 어떤 새로운 생각이 이 문서에 작성된 생각을 뒷받침하는가?
opposite : 어떤 새로운 생각이 이 문서에 작성된 생각과 대조되는가?
1.
None
to : 이 문서에 작성된 생각이 어떤 생각으로 발전되고 이어지는가?
참고 : 레퍼런스
1.
None