본문 바로가기
Vue.js

[Vue][업무][베트남🇻🇳]webpack의 모듈 시스템을 사용한 alert와 confirm 개발하기

by devebucks 2021. 7. 12.
728x90

이번 글은 Vue에서 alert와 confirm을 더 좋게 개발하는 방법에 대해서 알아보려고 합니다.

프로토타입 결과물은 아래와 같습니다.

 

 

🧑🏻‍💻 현재 제가 다니는 회사 웹 솔루션 제품 구성은 아래와 같습니다.

  • Vue-cli의 기본 스펙으로 개발되었음.
  • Javascript의 기본 alert를 사용하지 않음.
  • alert가 Vanilla Javascript로 작성되었음.
  • mixin에 script 파일을 등록하고 alert를 사용할 컴포넌트에 mixin 파일을 import해서 alert를 화면에 표시하고 있음.

 

시니어 개발자가 개발해 놓은 기능 중에는 alert와 confirm이 있었고, 어떻게 개발이 된 건지 분석을 해봤습니다. 시니어 개발자의 코드가 더 좋은 방법이고 구조적으로 코드가 작성되어 있다는 느낌을 받았습니다.

제가 작성해 놨던, 지금 사용하는 alert나 confirm코드는 말도 안 되는 코드라는 것을 정말 실감했습니다.

 

 

기존 코드의  문제점

Vue framework로 개발된 프로젝트에서 mixin으로 등록한 Vanilla Javascript를 화면 요소(alert, confirm)로 사용하고 있는 것이 문제였습니다.

alert나 confirm을 사용하는 모든 컴포넌트마다, mixin을 import를 해야한는 것도 귀찮은 일이었습니다.

 

📂 src/mixin/global.js 👈 나쁜 코드

기존에 사용하는  vanilla javascript alert 코드

⚠️ 이 코드의 문제점은 다음과 같습니다.

1. 유지보수의 어려움 - 반복되는 동일한 코드와 앞으로의 코드 의존성 관리가 문제가 될 것이라고 생각했습니다.

🤔 거의 모든 컴포넌트에 mixin을 import해서 사용해야 했습니다. 만약, 앞으로 계속 mixin으로 등록한 Vanilla Javascript alert를 사용하다간, 계속, 동일한 mixin import만 늘어날 것이 뻔히 보였습니다. 

 

2. DOM을 건드리는 Vanilla Javascript가 Vue 코드 베이스에 들어가 있다는 것이 마음에 걸렸습니다.

🤔 Vue는 가능한 DOM을 직접 건들지 않고 웹을 개발할 수 있게 하려는 의도를 가진 프레임워크입니다. Vanilla Javascript의 사용으로 이 의도를 해치고 싶지 않았습니다. 그리고 코드 베이스의 일관성을 유지할 수도 없게 됩니다.

 

 

 

베트남 개발자 방법이 좋은 방법인지 따져보기

베트남 개발자가 한 방법

  1. 📁  src/global/index.js 파일 생성하고, webpack의 모듈 시스템 Vue 전역 컴포넌트 등록을 사용해서 global 하위에 등록된 vue파일들을 자동으로 전역 컴포넌트로 등록시킴.
  2. 📁 global 폴더 하위에 alert와 confirm feature를 단일 파일 컴포넌트로 작성.
  3.  app.vue의 template에 alert와 confirm 컴포넌트 등록.
  4. 직접 개발한 Javascript 플러그인을 Vue 인스턴스의 property로 설정하고
  5. plugin이 호출되면, plugin의 객체값이 alert나 confirm 컴포넌트에 대입되서 true면 v-if로 alert나 confirm이 화면에 보이도록 함.

 

이렇게 했을 때의 장점을 생각해봤습니다.

  • Vue의 단일 파일 컴포넌트로 alert와 confirm 화면 요소를 작성할 수 있습니다.
  • Javascript로 Vue instance에 plugin형태로 전역으로 등록해서 컴포넌트에 import하지 않고 어느 script에서든 plugin으로 호출할 수 있습니다.
  • confirm의 callback 함수를 사용하지 않고 'ok'동작을 실행할 수 있습니다.  -> this 사용 가능

  • confirm 내부의 실행 로직의 depth를 감소시킬 수 있습니다.
  • confirm return ; 처리를 하지 않아도 됩니다.

장점밖에는 보이지 않는 이 코드를 바로 현재 제품에 이식했습니다.

 

Javascript 로 plugin을 개발해서 사용하면 좋은 점.

  • Vue.use(플러그인)으로 Vue인스턴스에 plugin을 등록해서 import 없이 어느 컴포넌트에서든 script를 사용할 수 있습니다.
    Vue 인스턴스에 script를 등록해버리기 때문에 import 없이 어느 컴포넌트에서든 플러그인을 사용할 수 있습니다. 컴포넌트에 스크립트 호출코드를 작성하지 않아도 되기 때문에 컴포넌트가 의존성이 높아지지 않게 되어서 유연하게 코드를 유지관리 할 수 있게 되는 점에서 좋다고 생각했습니다.
  • plugin에서 Vue.compoent() 메서드를 사용해서 plugin에 컴포넌트를 등록할 수 있습니다.
    plugin으로 Vue 컴포넌트를 등록해서 호출할 수 있습니다.

 

 

 

그럼, alert와 confirm 개발 방법을 알아보겠습니다.

alert 개발 방법

1.  자동으로 컴포넌트를  Vue 인스턴스에 넣어 줄 webpack의 모듈 시스템을 사용합니다.

  • 이 기능은 webpack의 기능입니다.
  • src/global 디렉토리 하위에 index.js 파일을 만들어 줍니다. 
  • 다음과 같은 코드를 작성해서, global 하위에 있는 vue 파일들을 Vue 인스턴스에 전역 컴포넌트로 등록합니다.

📁 src/global/index.js

import Vue from 'vue';
const requireComponent = require.context('.', false, /[\w-]+\.vue$/);
// 👆webpack에서 컴파일 때, 정규식에 매칭되는 파일들을 webpack 빌드 디펜던시에 추가함.
const global = {
  import() {
    requireComponent.keys().forEach(fileName => {
      const componentConfig = requireComponent(fileName)
      const componentName = fileName.replace(/^\.\//,'').replace(/\.\w+$/,''); //👈 확장자 명을 빼는 코드
      Vue.component(componentName, componentConfig.default || componentConfig);  // 👈 전역 컴포넌트 등록
    })
  }
}


export default global  // 👈 런타임에 실행하기 위해서 객체를 내보냄.
webpack이 컴파일을 하면, 의존트리를 그리기 위해서 require, require.context, import, import()의 구문을 해석합니다.
webpack에서의 context는 "모듈의 경로를 해석하기 위한 기준이 되는 디렉토리"를 의미합니다.

require.context를 사용하면, webpack이 모듈을 찾을 경로를 지정해 줄 수 있습니다. 'webpack에게 컴파일할 때 이것도 빠뜨리지 말고 해~' 라고 명령을 하는겁니다.

require.context('webpack이 모듈을 찾을 경로',
지정 경로의 하위 디렉토리들의 하위 파일들도 수집할 지 여부
수집 파일 이름 정규식
);

위에 코드의 작동방식은 다음과 같습니다.
1. webpack이 컴파일을 할 때 require.context를 인지해서 지정 경로에 파일들을 webpack  build dependency에 추가해 둡니다.
2. 빌드 후, 코드가 실행되는 런타임 시점에서 내보냈던 global객체를 app.vue에서 import하고, global.import()를 실행합니다.
3. build dependency에 추가되어 있는 vue 컴포넌트 파일들을 하나씩 호출해서 Vue 인스턴스의 전역 컴포넌트로 등록합니다. 

---
이 과정을 거치면, 앞으로 global 디렉토리에 작성되는 vue 컴포넌트 파일들을 별도의 호출 선언이나 설정 없이 모든 컴포넌트의 <template>에서 사용할 수 있게 됩니다.👍
이 방법은 vue뿐만 아니라, 다른 js파일도 모두 정규식으로 작성해서 사용할 수 있습니다.

 

2. 전역으로 사용할 alert 컴포넌트를 Vue 파일로 작성합니다

  • 플러그인의 객체를 컴포넌트의 data property로 저장시킨다.
  • data property의 isDisplay 값이 true이면, 컴포넌트가 화면에 표시된다.
  • 배경을 클릭하면, 프러그인 $alert의 cancel 메서드를 실행해서 alert가 닫히도록 한다.

📂 src/global/alert.vue 단일 파일 컴포넌트

<template lang="">
    <div class="alert" v-if="alert.isDisplay === true" @click="this.$alert.cancel()">
        <div class="alert" @click.stop="">
            <header>{{ alert.color }}</header>
            <content>{{ alert.message }}</content>
            <footer>
                <v-btn>OK</v-btn>
                <v-btn @click="alert.isDisplay = false">CANCEL</v-btn>
            </footer>
        </div>
    </div>
</template>

<script>
export default {
    name: 'alert',
    data() {
        return {
            alert: this.$alert.alertData,  // 👈 플러그인 객체
        };
    },
};
</script>
<style lang="scss" scoped>
//...
</style>

 

3. plugin으로 javascript 함수를 만들고, Vue 인스턴스에 전역으로 plugin을 등록합니다.

📂 src/plugin/alert.js

import BaseAlert from '@/global/BaseAlert.vue';

const alert = {
    alertData: {
        isDisplay: false,
        message: '',
        color: 'error',
    },
    close() {
        Object.assign(this.alertData, { isDisplay: false, message: '' });
    },
    show(color, message, timeout) {
        Object.assign(this.alertData, { isDisplay: true, message, color, timeout });
    },
    error(msg) {
        this.show('error', msg);
    },
    success(msg) {
        this.show('success', msg);
    },
    warnig(msg) {
        this.show('warning', msg);
    },
    loading(msg, timeout) {
        this.show('white', msg, timeout);
    },
};

export default {
    install(Vue, pluginName = '$alert') {
        Vue.prototype[pluginName] = alert;         // 👈 $alert 라는 이름으로 프로퍼티를 Vue 인스턴스에 등록
        Vue.component('plugin-alert', BaseAlert);  // 👈 전역 컴포넌트를 Vue 인스턴스에 등록
    },
};

export { alert };
prototype를 사용해서 Vue 인스턴스에 프로퍼티를 추가 등록합니다.
Vue.prototype[$alert] = alert
alert 객체를 Vue 인스턴스에 '$alert 프로퍼티'에 대입한다.

$
는 모든 인스턴스에서 사용 가능한 프로퍼티라고 알려주는 Vue에서 사용하는 언어입니다. 이것은 이미 정의된 데이터나, computed 요소, 메소드와의 충돌을 예방합니다.

이걸 사용하는 이유는 많은 컴포넌트에서 동일하게 사용되는 데이터(형식)이나 기능(유틸)가 모든 Vue 인스턴스(컴포넌트)에 선반영 되서 영향을 주지않고, 개발자가 원하는 Vue 인스턴스(컴포넌트)에서만 사용하기 위해서입니다.
그니까, 모든 컴포넌트에서 프로퍼티를 호출하면 사용할 수 있게 되는 겁니다. 코드에는  this.$alert... 이런 식으로 선언해서 사용하게 됩니다.

Vue 문서 - 인스턴스 프로퍼티 추가하기

 

전역으로 컴포넌트를 등록합니다.
Vue.component('캐밥-방식으로-등록할-컴포넌트-이름-작성', { /* ... */})

전역으로 컴포넌트를 등록하는 방법입니다. 어떤 루트의 컴포넌트에서도 사용할 수 있는 컴포넌트가 됩니다. 
만약, 전역으로 등록할 기본 컴포넌트가 많다면, require.context()를 사용해서 특정 디렉토리 하위에 있는 컴포넌트들을 전역으로 등록할 수 도 있습니다.

자세한 내용은 아래 문서를 참고해 주세요.

Vue 문서 컴포넌트 등록 참고

 

4. Vue인스턴스에 플러그인을 등록합니다.

📂 src/main.js

import Vue from 'vue';
import Vuex from 'vuex';
import router from './router';
import global from '@/global'; // 👈 export default로 내보낸 global 객체를 import합니다.
import Alert from '@/plugins/alert'; // 👈 개발한 alert plugin

global.import();  // 👈 global객체의 import 함수를 실행합니다.

Vue.use(Vuex);
Vue.use(Alert); // 👈 plugin을 Vue 인스턴스에 등록

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

 

5. app.vue <template>에 전역 alert 컴포넌트를 작성합니다.

  • alert.js의 플러그인의 객체 값이 isDisplay로 화면 요소로 보여지고, 안 보여지고 합니다.
  • import 없이 컴포넌트를 등록하고, 별도의 plugin 호출 함수도 필요가 없어서 app.vue를 최대한 간단하게 작성할 수 있는 장점이 있습니다.

📂src/App.vue

<template>
    <div id="App" @click="($event) => handleClickGlobal($event)">
        <!-- ... -->
        <BaseAlert></BaseAlert> <!-- 전역 alert 컴포넌트 -->
    </div>
</template>

<script>

export default {
    name: 'App',
    // ...
};
</script>

 

 

6. 사용.

📂src/components/Main.vue

 async handleEdgeCardToMap(aClickedTagObject) {
            // map이 1개도 등록되어 있지 않은 경우.
            if (this.gMapList.length === 0) {
                this.$alert.warning("upload 'map' first."); // 👈 plugin 호출

                return;
            }

        },

 

 

 

 

confirm은 alert와 차이점

  • 플러그인의 매개변수가 객체가 들어감.
  • 객체 요소에 ok버튼이 눌렸을 때의 동작 함수를 정의해줘야 함.

confirm 개발 방법

1. alert의 1번과 동일. webpack의 모듈 시스템을 사용해서, 전역컴포넌트 등록.

2. 전역 컴포넌트 등록

📂src/global/confirm.vue

<template>
    <div class="alert" v-if="confirm.state === true" @click="cancel()">
        <div class="alert" @click.stop="">
            <header>{{ confirm.title }}</header>
            <content>{{ confirm.question }}</content>
            <footer>
                <v-btn @click="done()">{{ confirm.okText }}</v-btn>
                <v-btn @click="cancel()">{{ confirm.cancelText }}</v-btn>
            </footer>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            confirm: this.$confirm.sConfirmData
        };
    },
    methods: {
        done() {
            this.confirm.done();
            this.confirm.state = false;
        },
        cancel() {
            this.$confirm.cancel();
        }
    }
};
</script>

3. 플러그인 등록

📂src/plugins/confirm.js

import BaseConfirm from '@/global/BaseConfirm';
const _initConfirmData = {
    title: '',
    text: '',
    okText: 'Ok',
    persistent: false,
    confirmColor: '',
    cancelText: 'Cancel',
    hideCancel: false,
    hideOk: false,
    done() {},
    cancel() {}
};
const confirm = {
    sConfirmData: { state: false },
    show(dialogData = {}) {
        this.sConfirmData.state = true;
        Object.assign(this.sConfirmData, {
            ..._initConfirmData,
            ...dialogData
        });
    },
    cancel() {
        this.sConfirmData.state = false;
        this.sConfirmData.cancel();
        setTimeout(() => {
            Object.assign(this.sConfirmData, _initConfirmData);
        }, 500);
    }
};

export default {
    install(Vue, pluginName = '$confirm') {
        Vue.prototype[pluginName] = confirm;
        Vue.component('plugin-confirm', BaseConfirm);
    }
};

export { confirm };

4. 컴포넌트 app.vue에서 사용

📂src/app.vue

<template>
    <v-app>
        <BaseHeader />
        <router-view />
        <BaseAlert />
        <BaseConfirm />  //👈
    </v-app>
</template>

<script>
export default {
    name: 'App'
};
</script>

5. 플러그인 Vue 인스턴스에 등록

📂src/main.js

import Vue from 'vue';
import App from '@/App.vue';
import vuetify from '@/plugins/vuetify';
// ...
import Confirm from '@/plugins/confirm.js';

Vue.use(Confirm);

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

6. 사용

📂src/

export default {
    methods: {
        handleRemoveAvatarSubmit() {
            this.$confirm.show({
                question: 'Sure that Remove an Avatar?',
                okText: 'OK',
                done: () => {
                    this.sAvatarImgFile = false;
                }
            });
        },
       }
}

 

 

728x90

댓글