7 Reusability & Composition

source: categories/study/vue-project/vue-project_7.md

7 Reusability & Composition

  • 이번 챕터에서는 Vue 3.x 버전에 추가된 핵심 기능인 컴포지션(Composition) API에 대해서 자세히 알아봅니다.
  • 또한 플러그인(Plugins), 믹스인(Mixins), Custom Directives 기능을 이용해서 프로젝트의 공통 모듈을 구현하고 코드를 재사용하는 방법에 대해서 익히게 됩니다.

7.1 Composition API

7.1.1 Composition API란?

  • 컴포지션 API는 컴포넌트 내에서 사용하는 특정 기능을 갖는 코드를 유연하게 구성하여 사용할 수 있도록 Vue 3 버전에 추가된 함수 기반의 API입니다.

  • 그동안 Vue는 ‘프로젝트 규모가 커질수록 관리하기 힘들다'는 단점이 있었습니다.
  • data, computed, watch, methods 등 프로젝트 규모가 커질수록, 컴포넌트의 계층 구조가 복잡할수록 코드에 대한 추적 및 관리가 어려웠습니다.
  • 하지만 컴포지션 API를 이용하면 Setup이라는 메소드 안에서 한 덩어리로 코드를 구현할 수 있어서 코드에 대한 관리가 훨씬 쉬워지게 됩니다.
  • 즉, 컴포지션 API는 그동안 Vue가 가지고 있던 단점을 보완하기 위해서 추가된 Vue3 버전의 핵심 기능입니다.

  • 일반적으로 우리가 지금까지 사용했던 API는 API를 호출함으로써 API에 구현된 기능을 그대로 사용할 수 있었습니다.
  • 즉, API는 특정 기능을 가지고 있고, 재사용을 위해 만들어진 것입니다.

  • 컴포지션 API 역시 API라는 이름이 붙어 있는 것처럼, 특정 기능을 갖는 함수를 정의하고 API처럼 사용할 수 있게 해주는 것입니다.
  • 결국 궁극적인 목적인 코드에 대한 재활용성을 높이고, 코드의 가독성을 높이기 위해 추가된 기능입니다.
  • Vue 2에서는 믹스인(Mixins)을 통해 코드를 재사용하였지만, 믹스인을 사용했을 때 오버라이딩 문제나, 다중 믹스인을 사용하는 경우 코드에 대한 관리가 어려웠습니다.

  • 컴포지션 API는 프로젝트에서 코드에 대한 재사용을 효율적으로 할 수 있도록 해줍니다.

  • [9-1] 그림은 좀 복잡한 컴포넌트를 기존 개발 방식과 컴포지션 API로 개발했을 때의 연관성 있는 로직 단위를 색상으로 표시한 것입니다.

  • 컴포지션 API로 개발하면 기존 개발 방식으로 개발했을 때보다 연관성 있는 로직을 같이 구현할 수 있어서 훨씬 코드가 간결해지고 코드 추적 및 유지 관리가 쉬워집니다.
  • 동일한 프로그램 로직과 관련된 코드를 함께 배치할 수 있다면 훨씬 더 좋을 것입니다.
  • 이것이 바로 컴포지션 API가 추가된 이유입니다.

7.1.2 Setup

  • Setup은 컴포지션 API를 구현하는 곳입니다.

  • 컴포지션 API가 어떻게 구현되는지 기존 개발 방법과 비교를 함으로써 컴포지션 API를 이해하도록 하겠습니다.
  • 사용자로부터 숫자 2개를 입력받고, 입력받은 숫자를 더한 값을 출력하는 코드를 작성해 보겠습니다.

  • 우리가 지금까지 배운 방법에 의하면 다음과 같은 코드로 작성될 것입니다.

<template>
    <div>
        <h2>Calculator</h2>
        <div>
            <input type="text" v-model="num1" @keyup="plusNumbers" />
            <span> + </span>
            <input type="text" v-model="num2" @keyup="plusNumbers" />
            <span> = </span>
            <span>{{ result }}</span>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'calculator',
        data() {
            return {
                num1: 0,
                num2: 0,
                result: 0,
            }
        },
        methods: {
            plusNumbers() {
                this.result = parseInt(this.num1) + parseInt(this.num2);
            }
        }
    }
</script>

  • 사용자로부터 숫자가 입력되는 이벤트(keyup)가 발생할 때마다 plusNumbers 함수를 호출해서 사용자가 입력한 값을 더하기 해서 result로 반환하도록 코드가 작성되었습니다.

  • 컴포지션 API 기능을 이용해서 동일한 기능을 갖는 코드를 작성해 보겠습니다.

<template>
    <div>
        <h2>Calculator</h2>
        <div>
            <input type="text" v-model="state.num1" @keyup="plusNumbers" />
            <span> + </span>
            <input type="text" v-model="state.num2" @keyup="plusNumbers" />
            <span> = </span>
            <span>{{ state.result }}</span>
        </div>
    </div>
</template>

<script>
    import { reactive } from 'vue'; // reactive 추가
    export default {
        name: 'calculator',
        setup() {
            let state = reactive({ // reactive 를 이용해서 num1, num2, result 를 실시간 변경사항에 대한 반응형 적용
                num1: 0,
                num2: 0,
                result,
            })
            
            function plusNumbers() {
                state.result = parseInt(state.num1) + parseInt(state.num2);
            }
            
            return { // reactive 로 선언된 state와 plusNumbers 함수를 반환함으로써 기존 data, methods 옵션처럼 사용이 가능해짐
                state,
                plusNumbers,
            }
        }
    }
</script>

  • 컴포지션 API의 reactive를 이용해서 코드를 작성했습니다.
  • 지금 작성된 코드는 컴포지션 API를 이용하지 않은 코드와 크게 다를 바가 없어 보입니다.

  • 여기서 코드를 좀 더 수정해 보겠습니다.

<template>
    <div>
        <h2>Calculator</h2>
        <div>
            <input type="text" v-model="state.num1" />
            <span> + </span>
            <input type="text" v-model="state.num2" />
            <span> = </span>
            <span>{{ state.result }}</span>
        </div>
    </div>
</template>

<script>
    import { reactive, computed } from 'vue'; // computed 추가
    export default {
        name: 'calculator',
        setup() {
            let state = reactive({
                num1: 0,
                num2: 0,
                result: computed() => parseInt(state.num1) + parseInt(state.num2), // computed를 이용해서 num1, num2가 변경이 일어나면 즉시 result로 더한 값을 반환
            })
            
            return {
                state,
            }
        }
    }
</script>

  • reactivecomputed를 이용하니까 input type=text에 바인딩했던 keyup 이벤트를 없앨 수 있고, 코드가 훨씬 간결해졌습니다.
  • 지금 작성한 코드는 현재 컴포넌트 내에서만 사용 가능합니다.

  • 현재 컴포넌트 내에서만 사용하는 코드를 작성하는 경우도 있지만, 계산기에서 덧셈 연산을 여러 번 반복해서 사용할 수 있는 것처럼 재사용 가능한 코드를 작성하는 경우가 있습니다.
  • 이러한 경우, 작성한 코드를 여러 컴포넌트에서 재사용할 수 있도록 함수를 분리해야 합니다.

  • 일단 Setup에 작성된 코드를 분리해서 별도의 function으로 작성하겠습니다.
<template>
    <div>
        <h2>Calculator</h2>
        <div>
            <input type="text" v-model="num1" />
            <span> + </span>
            <input type="text" v-model="num2" />
            <span> = </span>
            <span></span>
        </div>
    </div>
</template>

<script>
    import { reactive, computed, toRefs } from 'vue'; // toRefs 추가
    function plusCalculator() {
        let state = reactive({
            num1: 0,
            num2: 0,
            result: computed(() => parseInt(state.num1) + parseInt(state.num2))
        })
        
        return toRefs(state); // 반응형으로 선언된 num1, num2, result가 외부 function에서 정상적으로 동작하기 위해서는 toRefs를 사용해야 함
    }
    export default {
        name: 'calculator',
        setup() {
            let { num1, num2, result } = plusCalculator(); // 외부 function
            return {
                num1,
                num2,
                result,
            }
        }
    }
</script>
  • 외부 function에서 반응형 변수를 사용하기 위해서 toRefs가 추가되었습니다.

  • 컴포넌트 안에서는 v-model 디렉티브를 통해 바인딩된 변수가 사용자의 입력값이 바뀔 때마다 반응형으로 처리 되었지만,
  • 함수를 컴포넌트 밖으로 빼면 사용자가 입력한 값에 대한 반응형 처리가 불가능해집니다.
  • 그래서 toRefs를 사용하여 컴포넌트 밖에서도 반응형 처리가 가능하도록 할 수 있습니다.

  • 컴포넌트 내에서 정의된 코드를 다른 컴포넌트에서도 사용할 수 있도록 컴포넌트 밖으로 분리하겠습니다.
  • common.js 파일을 생성하고 앞서 구현한 plusCaculator 코드를 다음과 같이 작성합니다.
// common.js
import { reactive, computed, toRefs } from 'vue';
const plusCalculator = () => {
    let state = reactive({
        num1: 0,
        num2: 0,
        result: computed(() => parseInt(state.num1) + parseInt(state.num2))
    })
    return toRefs(state);
}
export {
    plusCalculator,
}
  • vue 컴포넌트에서는 다음과 같이 common.js로 import해서 사용하면 됩니다.
<template>
    <div>
        <h2>Calculator</h2>
        <div>
            <input type="text" v-model="num1" />
            <span> + </span>
            <input type="text" v-model="num2" />
            <span> = </span>
            <span></span>
        </div>
    </div>
</template>

<script>
    import { plusCalculator } from '../common.js';
    export default {
        name: 'calculator',
        setup() {
            let { num1, num2, result } = plusCalculator();
            return {
                num1,
                num2,
                result,
            }
        }
    }
</script>
  • 이렇게 특정 기능을 갖는 함수를 컴포지션 API를 이용하고 개발해서 공통 스크립트로 제공하면 뷰 컴포넌트 내에서 반응형(reactivity)으로 처리를 할 수 있어서 매우 활용도가 높아지게 됩니다.

7.1.3 Lifecycle Hooks

  • 컴포지션 API 내에서 사용할 수 있는 컴포넌트 라이프사이클 훅은 다음 표와 같습니다.
Options API Hook inside setup()
beforeCreate  
created  
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
  • 컴포지션 API에서 setup()은 컴포넌트 라이프사이클의 beforeCreate 와 created 훅 사이에서 실행되기 때문에,
  • onBeforeCreate, onCreated 훅은 필요가 없고,
  • setup() 안에서 코드를 작성하면 됩니다.

  • 다음은 setup()에서 onMounted 훅을 적용한 코드입니다.
export default {
    setup() {
        // mounted
        onMounted(() => {
            console.log('Component is mounted!');
        })
    }
}

7.1.4 Provide / Inject

  • 컴포지션 API에서 Provide / Inject 사용하려면 provide 와 inject 를 별도로 import 해야 사용할 수 있습니다.
  • 부모 컴포넌트에서는 provide 함수를 통해서 전달할 값에 대한 키(key), 값(value)을 설정합니다.
<!-- CompositionAPIProvide.vue -->
<template>
    <CompositionAPIInject />
</template>

<script>
    import { provide } from 'vue'; // provide 추가
    import CompositionAPIInject from './CompositionAPIInject';
    export default {
        components: {
            CompositionAPIInject,
        },
        setup() {
            provide('title', 'Vue.js 프로젝트');
            // provide 함수를 통해서 전달할 키(key), 값(value) 설정
        }
    }
</script>
  • 자식 컴포넌트에서는 inject를 이용해서 부모 컴포넌트에서 정의한 provide 키로 데이터를 가져올 수 있습니다.

<template>
    <h1>{{ title }}</h1>
</template>

<script>
    import { inject } from 'vue'; // inject 추가
    export default {
        setup() {
            const title = inject('title');
            // inject를 사용해서 provide에서 정의한 키(key)로 데이터를 전달받음
            return {
                title,
            }
        }
    }
</script>

7.2 믹스인(Mixins)

  • 일반적인 프로그래밍 언어를 이용해서 애플리케이션을 개발할 때 우리는 공통 모듈이라고 부르는 파일을 만들게 됩니다.
  • 이 파일에는 자주 사용되는 기능을 메소드로 만들어서 등록해 놓고, 개발자들은 각 화면 개발 시 공통모듈 파일을 import하고 그 기능을 사용합니다.

  • Vue에서도 이렇게 공통 모듈에 해당하는 파일을 만들어서 사용할 수 있는데, 그중 하나의 방법이 믹스인입니다.
  • 믹스인은 이름에서도 알 수 있듯이 믹스(mix)-인(in), 믹스인 파일을 컴포넌트 안에(in) 삽입해서, 합쳐서(mix) 사용하는 것입니다.
  • 일반적인 언어의 공통모듈처럼 메소드를 정의해서 사용할 수도 있고, 이외에도 Vue의 라이프사이클 훅까지 사용할 수 있습니다.
  • 이벤트 훅까지 사용할 수 있다는 것은 굉장히 큰 장점으로 작용합니다.

  • 믹스인(mixin)은 기능을 따로 구현하고, 필요할 때마다 믹스인 파일을 컴포넌트에 결합해서 사용하는 방법을 말합니다.
  • 예를 들어 애플리케이션 내의 모든 컴포넌트에서는 사용자가 컴포넌트에 접근할 때마다 사용자가 해당 컴포넌트에 대한 접근 권한이 있는지를 체크한다고 가정해 봅시다.

  • 각각의 모든 컴포넌트에서 컴포넌트가 생성되는 시점에(beforeCreate) 사용자의 권한을 체크하는 로직을 다 넣는다고 생각하면 모든 컴포넌트에 중복된 코드가 양산되게 됩니다.
  • 이런 경우 믹스인을 이용해서 사용자 권한을 체크하는 로직을 구현하고, 각각의 컴포넌트에서는 해당 믹스인 파일을 추가만 하면 됩니다.

  • 믹스인은 이처럼 여러 컴포넌트에 동일한 로직을 사용할 필요가 있을 때 매우 유용합니다.

  • 이러한 구조에 믹스인을 적용하면,

  • 특정 기능을 캡슐화하여 단순히 코드의 수가 줄어들고 재사용성이 늘어나는 것뿐만 아니라 애플리케이션 운영 시에도 큰 이점을 가지게 됩니다.
  • 사용자 권한 체크로직이 변경이 일어났을 때 믹스인 파일만 수정하면 참조하고 있는 모든 컴포넌트에 반영되기 때문입니다.

  • Mock 서버의 api를 호출하는 메소드를 믹스인 파일로 만들어서 적용하겠습니다.
  • 리스트 랜더링을 배울 때 Mock 서버의 api를 호출하기 위해서 axios를 통한 호출 메소드(callAPI)를 작성했습니다.
  • 이렇게 서버와 통신하기 위한 코드는 대다수의 컴포넌트에서 필요한 기능입니다.
  • 이러한 기능을 믹스인 파일로 만들고 서버 통신이 필요한 컴포넌트에서는 해당 믹스인 파일을 사용하면 됩니다.

  • axios를 이용해서 서버 데이터를 호출했던 메소드를 믹스인으로 만들어 보겠습니다

7.2.1 믹스인(mixins) 파일 생성

  • src 폴더에 api.js 파일을 생성합니다.
  • 다음과 같이 axios 패키디를 이용해서 서버와의 데이터 통신을 위한 공통 함수를 작성했습니다.
// api.js
import axios from 'axios';
export default  {
    methods: {
        async $callAPI(url, method, data) {
            return (await axios({
                method,
                url,
                data,
            }).catch(e => {
                console.log(e);
            })).data;
        }
    }
}
  • 함수 이름은 $callAPI라고 작성이 되었습니다.
  • 함수 이름에 $라는 prefix를 사용하는 이유는 믹스인 파일을 사용하는 컴포넌트 내에 동일한 메소드명이 있어서 오버라이딩 되는 것을 방지하기 위해서입니다.

  • 일반적으로 컴포넌트에 정의되는 메소드명에는 $와 같은 prefix를 사용하지 않기 때문에 믹스인 파일의 메소드명을 이렇게 작성하면 컴포넌트의 메소드명과 구분할 수 있습니다.

7.2.2 컴포넌트에서 믹스인(mixins) 사용

  • 다음과 같이 mixins 프로퍼티에 사용할 믹스인 파일을 정의해서 사용하면 됩니다.
<!-- Mixins.vue -->
<script>
    import ApiMixin from '../api.js';
    export default {
        mixins: [ApiMixin], // 사용할 믹스인 파일을 배열로 등록
        data() {
            return {
                productList: [],
            }
        },
        async mounted() {
            this.productList = await this.$callAPI("https://ada1e~~~~.mock.pstmn.io/list", "get");
            console.log(this.productList);
        }
    }
</script>
  • 믹스인은 이렇게 메소드를 정의해서 컴포넌트에서 사용할 수 있게 해줍니다.
  • 이외에도 믹스인은 컴포넌트에서 일어나는 이벤트 훅을 그대로 이용할 수 있다는 큰 이점을 가지고 있습니다.

7.2.3 믹스인(mixin)에서 라이프사이클 훅 이용하기

  • 애플리케이션을 이용하는 사용자가 방문한 페이지 및 페이지에 머문 시간을 기록하는 코드를 작성한다고 가정합시다.
  • 믹스인에 사용자가 특정 페이지에 방문하고 빠져나갈 때 데이터베이스에 시간을 저장하는 메소드를 만들었습니다.

  • 각 컴포넌트에서는 mounted 훅이 발생할 때 믹스인의 방문 시작 메소드를 호출하고,
  • unmounted 훅이 발생할 때 믹스인의 방문 종료 메소드를 호출해서
  • 데이터베이스에 방문 시작 시간과 방문 종료 시간을 기록하여 페이지에 머문 시간을 계산할 수 있습니다.

  • mounted, unmounted마다 모든 컴포넌트에서 믹스인에 메소드를 호출하는 것은 간편하긴 하지만,
  • 어찌 보면 굉장히 반복적이고 불편한 작업이 될 것입니다.
  • 만약 개발자의 실수로 특정 컴포넌트에 해당 코드를 작성하지 않으면 코드가 작성되지 않은 컴포넌트의 페이지 방문 이력을 기록할 수 없게 됩니다.

  • 믹스인에서는 단순히 메소드만 정의해서 사용하는 것이 아니라, 컴포넌트의 라이프사이클 훅을 그대로 이용할 수 있습니다.
  • 즉, 믹스인 파일에 mounted, unmounted마다 데이터베이스에 방문 시작 시간과 방문 종료 시간을 기록하는 코드를 작성하면,
  • 해당 믹스인 파일을 사용하는 모든 컴포넌트에서는 자동으로 컴포넌트가 mounted, unmounted될 때 데이터베이스에 방문 기록을 저장할 수 있게됩니다.

  • 실제로 믹스인 파일의 mounted, unmounted 훅에 작성된 코드가 컴포넌트 안에서 어느 시점에 실행이 되는지 다음 코드를 통해 확인하도록 하겠습니다.


// mixins.js
export default {
    mounted() {
        console.log('믹스인 mounted');
    },
    unmounted() {
        console.log('믹스인 unmounted');
    }
}




<script>
import mixin from './mixin'
export default {
    mixins: [mixin],
    mounted() {
        console.log('컴포넌트 mounted');
        // 믹스인 mounted
        // 컴포넌트 mounted
    },
    unmounted() {
        console.log('컴포넌트 unmounted');
        // 믹스인 unmounted
        // 컴포넌트 unmounted
    }
}
</script>


  • 이 코드를 실행하면 컴포넌트가 mounted되는 시점에 믹스인에 있는 mounted 코드가 먼저 실행되고,
  • 그 다음 컴포넌트의 mounted 코드가 실행됩니다.
  • 즉, 컴포넌트의 라이프사이클 훅 시점에 동일한 믹스인 라이프사이클 훅 호드가 먼저 실행됩니다.
  • 2개의 파일이 같은 프로퍼티, 같은 라이프사이클 훅끼리 코드가 합쳐지는데, 믹스인 코드가 먼저 실행이 됩니다.

7.2.4 믹스인 파일 전역으로 등록하기: main.js에 등록

  • api를 호출하는 기능은 애플리케이션 내의 거의 모든 컴포넌트에서 사용하는 기능이므로 전역으로 등록해서 각 컴포넌트에서 별도의 mixins 추가 없이 사용할 수 있게 하겠습니다.

  • 다음과 같이 mixins.js파일을 생성합니다.



import axios from 'axios';

export default {
    methods: {
        async $api(url, method, data) {
            return (await axios({
                method,
                url,
                data
            }).catch(e => {
                console.log(e);
            })).data;
        }
    }
}


  • mixins.js 파일을 전역으로 등록하기 위해서 main.js에 다음과 같이 추가합니다.


import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import mixins from './mixins';

const app = createApp(App);
app.use(router);
app.mixin(mixins);
app.mount('#app');


7.3 Custom Directives

  • Vue에서는 v-model, v-show 디렉티브 같은 기본 디렉티브 외에도 사용자가 직접 디렉티브를 정의해서 사용할 수 있습니다.
  • 웹사이트 방문 시 로그인 페이지에 접속하면 페이지가 열림과 동시에 사용자 ID를 입력하는 필드에 마우스 포커스가 위치해 있는 것을 빈번하게 보았을 것입니다.
  • 사용자가 컴포넌트에 접속했을 때 지정된 입력 필드로 포커스를 위치시킬 수 있는 커스텀 디렉티브를 만들어 보겠습니다.
  • 참고로 커스텀 디렉티브를 전역에서 사용할 수 있도록 등록이 가능하고, 특정 컴포넌트 안에서만 사용하도록 등록도 가능합니다.

  • main.js에 커스텀 디렉티브를 다음과 같이 추가합니다.


const app = createApp(App);
app.directive('focus', {
    mounted(el) {
        el.focus();
    }
})


  • 코드를 보면 컴포넌트가 mounted되면 v-focus 디렉티브를 적용한 HTML 객체로 포커스(el.focus())를 위치시키도록 작성되었습니다.
  • 컴포넌트에서는 다음과 같이 v-focus 디렉티브를 사용하면 v-focus 디렉티브가 정의된 HTML 객체에 마우스 포커스가 위치하게 됩니다.


<input type="text" v-focus />


  • 실제로 지금 사용한 커스텀 디렉티브는 Vue 애플리케이셔 개발 시 main.js에 전역으로 등록해서 많이 사용합니다.

  • 다음은 전역에 등록하는 방법이 아닌, 컴포넌트 내에 등록해서 사용하는 방법을 알아보겠습니다.
  • 다음과 같이 directives 옵션에 정의하면 됩니다.


<script>
export default {
    directives: {
        focus: {
            mounted(el) {
                el.focus();
            }
        }
    }
}
</script>


  • 커스텀 디렉티브 사용 시에도 데이터바인딩 처리가 가능합니다.
  • 다음 코드는 v-pin 디렉티브에 데이터 옵션의 position을 바인딩했습니다.
  • 컴포넌트가 mounted되면 v-pin 디렉티브가 지정된 HTML 객체의 positiontop: 50px, left: 100px로 고정시킵니다.


<template>
    <div style="height: 1000px">
        <p v-pin="position">페이지 고정 영역 (position: fixed; top: 50px; left: 100px;)</p>
    </div>
</template>

<script>
export default {
    directives: {
        pin: {
            mounted(el, binding) {
                el.style.position = 'fixed';
                el.style.top = binding.value.top + 'px';
                el.style.left = binding.value.left + 'px';
            }
        }
    },
    data() {
        return {
            position: {top: 50, left: 100}
        }
    }
}
</script>


  • 애플리케이션에서 필요한 커스텀 디렉티브를 잘 정의해서 사용한다면 애플리케이션 개발 생산성을 향상시킬 수 있습니다.

7.4 Plugins

  • 우리는 이미 플러그인이 무엇인지 잘 알고 있습니다.
  • 플러그인은 특정 기능을 제공하는 코드이고, 여러분은 Vue 프로젝트를 진행할 때 유용한 플러그인들을 설치하고 사용하고 있습니다.
  • NPM을 통해 설치되는 패키지 역시 플러그인입니다.

  • 플러그인은 때로는 모듈로, 때로는 패키지로 사용될 수 있습니다.
  • 플러그인은 특정 기능을 제공하고 쉽게 설치해서 사용할 수 있습니다.
  • 아마 여러분이 프로젝트를 진행하면서 필요한 대부분의 플러그인은 이미 전 세계 개발자들 중 누군가가 개발해서 NPM에 등록했을 것이고,
  • 여러분은 NPM을 통해 쉽게 설치해서 사용할 수 있습니다.
  • 하지만 대규모 프로젝트를 진행하다보면 해당 프로젝트에 맞게 특화된 플러그인을 제작해야 하는 상황이 생길 수 있습니다.
  • Vue에서는 직접 플러그인을 제작해서 전역으로 사용할 수 있게 해줍니다.

  • 다국어(i18n)를 처리해주는 플러그인을 제작해 보겠습니다.
  • src 폴더 및에 plugins 폴더를 만들고 다음과 같이 i18n.js 파일을 생성합니다.


// src/plugins/i18n.js
export default {
    install: (app, options) => {
        app.config.globalProperties.$translate = key => {
            return key.split('.').reduce((o, i) => {
                if (o) return o[i]
            }, options)
        }
        app.provide('i18n', options); // i18n 키로 다국어 데이터 전달
    }
}


  • 플러그인은 install 옵션에서 정의해서 사용할 수 있습니다.
  • app.config.globalProperties를 선언하여 컴포넌트에서 $translate로 바로 접근해서 사용할 수 있습니다.

  • 또한 provide로 다국어 데이터를 전달해서 컴포넌트에서는 inject를 이용해서도 사용이 가능합니다.
  • 다국어 플로그인은 전역에서 사용해야 하므로 main.js 파일을 열어서 다국어 플러그인을 사용할 수 있도록 추가해야 합니다.


import i18nPlugin from './plugins/i18n'; // i18n 플러그인 추가
const i18nStrings = {
    en: {
        hi: 'Hello!',
    },
    ko: {
        hi: '안녕하세요!',
    }
}
const app = createApp(App);
app.use(i18nPlugin, i18nStrings); // i18n 플러그인에 다국어 번역 데이터를 파라미터로 전달
app.mount('#app');


  • i18nStrings 변수를 선언해서 다국어 번역이 필요한 내용을 정의한 후 i18nPlugin으로 전달합니다.

  • 이제 모든 컴포넌트에서 다국어 플러그인을 사용할 수 있습니다.
  • 컴포넌트에서 사용하는 방법은 다음과 같습니다.


<template>
    <div>
        <h2>{{ $translate("ko.hi") }}</h2> <!-- $translate로 사용 -->
        <h2>{{ i18n.ko.hi }}</h2>
    </div>
</template>

<script>
export default {
    inject: ['i18n'], // provide로 전달된 i18n을 inject로 사용할 수 있음
    mounted() {
        console.log(this.i18n);
    }
}
</script>