📌 목차
- 서론
- Builder-Direrctor Pattern 이란?
- Builder-Director Pattern Vue에 적용해보기(FormBuilder)
- Implementing Vuex
- Implementing Vee-Validate
- Conclusion
- Reference
📌 서론
Component로 vue를 관리 할때 우리는 이러한 문제들을 마주 하게 됩니다.
Components
|_SignInForm.vue
|_CreditCardFrom.vue
|_RegisterFrom.vue
|_EditForm.vue
|_SignUpForm.vue
|_TeamAddForm.vue
|_...
만약 이 수많은 Form들을
|_FormBuilder.vue
|_FormBuilder.js
|_FormDirector.js
로 해결할 수 있다면 어떨까요?
해당 코드는 https://github.com/eddie0329/design-pattern/tree/master/vue/builder-pattern서 볼수 있습니다.
📌Builder-Direrctor Pattern 이란?
Builder-Director 패턴은 oop design 패턴중 하나로써 다음과 같은 논리를 가집니다.
Builder는 제품에 필요한 요소를 가지고 있습니다. Director는 Builder에게 어떠한 것을 만들라고 명령을 내립니다. 그럼 Builder는 명령에 따라 해당 제품을 만듭니다. 예를 들어 보겠습니다.
Builder는 자동차를 만들수 있는 기술이 있습니다. (자동차 바퀴생성, 자체 모양 생성, 헤드라이트 생성...기타.. 자동차에 들어가는 모든 부품을 만들 수 있다고 가정해보겠습니다.) Director는 빌더에게 스포츠카를 만들어줘! 트럭을 만들어줘! 라고 명령을 한다고 가정하면 Builder는 해당 기술들로 스포츠카를 만들고 트럭을 만듭니다. 이러한 논리로 FormBuilder를 만들어 보겠습니다.
📌Builder-Director Pattern Vue에 적용해보기(FormBuilder)
먼저 App.vue에 FormBuilder를 넣어보겠습니다.
<template>
<div id="app">
<FormBuilder form-method="makeUserSignIn" />
</div>
</template>
<script>
import FormBuilder from "./components/FormBuilder";
export default {
name: "App",
components: {
FormBuilder,
},
};
</script>
여기서 주목을 해야할 점은 FormBuilder에 넣어주는 form method입니다. 해당 부분으로 어떤 폼을 만들지를 정의를 해주어 props에 던져주게 됩니다.
<script>
// FormBuilder.Vue
import FormBuilder from "../builder/FormBuilder";
import FormDirector from "../director/FormDirector";
export default {
props: ["formMethod"],
data: () => ({
_inputUsername: "",
_inputPassword: "",
}),
render(h) {
const self = this;
const director = new FormDirector();
const builder = new FormBuilder(h, self);
director[this.formMethod](builder);
return builder.getForm();
},
};
</script>
FormBuilder.vue 여느 컴포넌트들과는 좀 다르게 생겼습니다. 여기서 코드는 평범하지만 director[this.formMethod](builder)부분에 먼저 주목을 해주세요.
export default class FormDirector {
makeUserSignIn(builder) {
builder
.setFields([
{ name: "Username" },
{ name: "Password", type: "password" },
])
.setSubmit("Sign in");
}
}
이렇게 director는 builder에게 명령을 할수 있습니다. 그럼 builder가 가지고 있는 기술 (input만들기, submit 버튼 만들기를 통해 makeUserSigInForm을 동적으로 만들 수 있습니다.
export default class FormBuilder {
constructor(h, self) {
this.h = h;
this.fields = [];
}
setFields(fields) {
this.fields.push(
fields.map((f) => {
return this.h('input', {
class: { input: true },
props: {
type: f.type || "text",
placeholder: f.name,
name: f.name,
},
on: {
input: (event) => {
this.self.$emit(input, event.target.value)
},
},
});
})
);
return this;
}
setSubmit(text) {
this.fields.push(
this.h("input", {
class: "button is-link",
domProps: { type: "submit", value: text },
})
);
return this;
}
getForm() {
return this.h(
"form",
{
on: {
submit: (event) => {
event.preventDefault();
},
},
},
this.fields
);
}
}
setField는 input을 만들어주는 함수입니다. setSubmit은 submit button을 만들어 주는 함수 입니다. 그리고 최종적으로 만들어진 elements들을 묶어 form을 리턴해주는 함수는 getForm입니다.
📌Implementing Vuex
Vuex를 적용하는건 매우 간단합니다.FormBuilder.vue를 한번 바꿔보겠습니다.(vuex 자세한 내용은 실제 코드 참고 부탁드립니다🤟🏻)
<script>
import { mapState, mapMutations, mapActions } from "vuex";
import {
SET_INPUT_USERNAME,
SET_INPUT_PASSWORD,
SIGN_IN_USER,
} from "../store/modules/formInputs";
import FormBuilder from "../builder/FormBuilder";
import FormDirector from "../director/FormDirector";
export default {
props: ["formMethod", "validationMethod"],
computed: {
...mapState("formInputs", ["inputUsername", "inputPassword"]),
getSubmitTypesFunc() {
const submitTypes = {
makeUserSignIn: () => this.signinUser,
};
return submitTypes[this.formMethod]();
},
_inputUsername: {
get() {
return this.inputUsername;
},
set(username) {
this.setInputUserName(username);
},
},
_inputPassword: {
get() {
return this.inputPassword;
},
set(password) {
this.setInputPassword(password);
},
},
},
methods: {
...mapMutations("formInputs", {
setInputUserName: SET_INPUT_USERNAME,
setInputPassword: SET_INPUT_PASSWORD,
}),
...mapActions("formInputs", {
signinUser: SIGN_IN_USER,
}),
},
render(h) {
const self = this;
const director = new FormDirector();
const builder = new FormBuilder(h, self);
director[this.formMethod](builder);
return builder.getForm(this.getSubmitTypesFunc);
},
};
</script>
여기서는 self를 넘겨주는것에 집중해주세요.
다음은 FormBuilder.js입니다.
getTypes(name) {
const inputTypes = {
Username: "_inputUsername",
Password: "_inputPassword",
};
return inputTypes[name];
}
setFields(fields) {
this.fields.push(
fields.map((f) => {
return this.h('input', {
class: { input: true },
props: {
type: f.type,
placeholder: f.name,
name: f.name,
inputValue: this.self[this.getTypes(f.name)],
},
on: {
input: (value) => {
this.self[this.getTypes(f.name)] = value;
},
},
});
})
);
return this;
}
setSubmit(text) {
this.fields.push(
this.h("input", {
class: "button is-link",
domProps: { type: "submit", value: text },
})
);
return this;
}
getForm(submitFunc) {
return this.h(
"form",
{
on: {
submit: (event) => {
event.preventDefault();
submitFunc();
},
},
},
this.fields
);
}
getTypes로 어떤것들이 들어오는지에 대해 정의를 해놓았습니다.(어떤 값들을 v-model에 적용시켜주어야 하는지에 대한 정의) -> Username이면 _inputUsername, Password는 _inputPassword를 사용합니다.
📌Implementing Vee-Validate
Vee-Validate는 두가지로 나눌 수 있습니다. ValidationProvider과 ValidationObeserver입니다.
먼저 ValidationProvider를 먼저 살펴보겠습니다.
해당 부분은 여러 element들이 들어가야 하기 때문에 TextInput.vue컴포넌트에 선언을 해놨습니다.(코드는 다음과 같습니다.)
<template>
<ValidationProvider :rules="rules" v-slot="{ errors }">
<input v-model="innerValue" :type="type" :name="name" :placeholder="placeholder" />
<span>{{ errors[0] }}</span>
</ValidationProvider>
</template>
<script>
export default {
data: () => ({
innerValue: "",
}),
props: {
inputValue: {
type: String,
default: "",
},
rules: {
type: [Object, String],
default: "",
},
placeholder: {
type: String,
default: "",
},
type: {
type: String,
default: "text",
},
name: {
type: String,
default: "",
},
},
watch: {
innerValue(value) {
this.$emit("input", value);
},
inputValue(val) {
if (val !== this.innerValue) {
this.innerValue = val;
}
},
},
};
</script>
다음은 ValidationObserver입니다. 해당 부분도 builder에 포함을 시켜줄 필요가 없기때문에 그냥 FormBuilder를 감싸주었습니다.
<template>
<div id="app">
<ValidationObserver v-slot="{ passes }">
<FormBuilder form-method="makeUserSignIn" :validation-method="passes" />
</ValidationObserver>
</div>
</template>
지금 보시면 validation-method를 passes만 넘겨주었지만 destructuring을 안하고 넘겨주어 Observer의 기능들 모두를 사용할 수 있습니다만 예제를 위해 passes만 넘겨주었습니다.
이제 받았던 validation method를 form검사할때 사용하기 위해 이렇게 넘겨줍니다.
return builder.getForm(this.getSubmitTypesFunc, this.validationMethod);
그렇게하여 이렇게 구현을 할 수 있습니다.
getForm(submitFunc, validationMethod) {
return this.h(
"form",
{
on: {
submit: (event) => {
event.preventDefault();
validationMethod(submitFunc); -> // passes(submitFunc);
},
},
},
this.fields
);
}
주석처리된 부분은 실제로 실행되는 코드입니다.
📌 Conclusion
Vue Conference중 Jacob이 builder-director패턴을 vue에 적용하는 강연 영상이 있었습니다. 하지만 대부분 댓글이 '현업에 오래 몸담고 있던 사람이 최신 기술을 쓰면 어떻게 되는지 극단적인 사례', 'vuex나 vee-validate'를 쓸줄 모르는 사람. 'vue'를 실제로 못써본 사람'이라는 비판적인 댓글들을 보았습니다. 하지만 저는 댓글을 보며 '응? 아닌거 같은데... 좋은 패턴인거 같은데?', '왜 못쓰지?'라는 생각에서 vue에 builder-director패턴을 적용해 실제로 회사에서 쓰는 기능들을 모두 접목해 만들어 보았습니다. (vuex + vee-validate)
실제로 이패턴을 사용했을때의 장점과 단점은 다음과 같았습니다.
장점:😎
- 매우 관리하기가 쉽다.(컴포넌트 관리)
- 빠르게 폼을 만들수 있다.
- 테스트를 하기에 적당하다.
단점: 😱
- 처음에 구현하기가 힘들다.
- builder instruction을 다른 사람들에게 공유하기가 힘들다.
- 다양한 디자인은 적용하기 힘들다.(재 사용성에 초점을 둬야한다.)
따라서 제품에 사용하는 디자인이 한정적이고 간단하다면 해당 패턴을 고려 해보는것이 매우 좋을거 같다는 결론을 내리며 이글을 마칩니다.
📌 Reference
'Design Pattern' 카테고리의 다른 글
Mediator Pattern (0) | 2020.10.23 |
---|---|
Observer Pattern (0) | 2020.10.19 |
Module Pattern (0) | 2020.10.07 |
Singleton Pattern (0) | 2020.10.05 |
Constructor Pattern (0) | 2020.09.24 |