Vue.js Starter - Composition, Directives, Plugins
Vue.js νλ‘μ νΈ ν¬μ μΌμ£ΌμΌ μ - Part 5
17. Options API & Composition API π©βπ»
1. Composition API Examples
Composition API
λ TypeScript
μ λμ
κ³Ό ν¨κ» Vue 3
μ κ°μ₯ ν° νΉμ§ μ€ νλλ€.
κΈ°μ‘΄ Vue 2
μμλ Options API
λ₯Ό μ΄μ©ν΄ μ»΄ν¬λνΈμ lifecycle μ κ΄λ¦¬νκ³ , mixins
λ₯Ό ν΅ν΄μ μ½λλ₯Ό μ¬μ¬μ© ν μ
μμλ€. νμ§λ§ μ΄λ νλ‘μ νΈ κ·λͺ¨κ° 컀μ§λ©° μ¬μ¬μ©μ΄ μ΄λ €μ΄ Options API λ large tree μ»΄ν¬λνΈμμ data, computed,
watch, methods λ±μ κ΄λ¦¬νκ³ μ½λλ₯Ό μΆμ νλ κ²μ΄ μ΄λ €μμ§λ λ¬Έμ κ° λ°μνκ³ , μ½λ μ¬μ¬μ©μ μν΄ μ¬μ©ν mixins μ
μ€λ²λΌμ΄λ© λ¬Έμ κ° λ°μ
ν΄ λ€μ€ mixins μ¬μ©ν κ²½μ° μ½λ κ΄λ¦¬κ° μ΄λ €μμ§λ λ¬Έμ μ μ΄ λ°μνλ€.
μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ Composition API λ ν¨μ κΈ°λ°μ API
λ‘ μ½λλ₯Ό μ μ°νκ² κ΅¬μ±νμ¬ API λ₯Ό νΈμΆνλ― μ¬μ¬μ© κ°λ₯νκ² λ§λ€μ΄μ‘λ€.
μ΄κ²μ React 16.8
μμ μΆκ°λ Hooks
μ λΉμ·νκ² μ»΄ν¬λνΈμ λΉμ¦λμ€ λ‘μ§μ λΆλ¦¬ν΄ κ΄λ¦¬ν μ μκ² ν΄μ€λ€.
Vue 곡μ λ¬Έμμ κ°μ΄λ TypeScript with Composition API
μ λ°λ₯΄λ©΄ Vue 3 μμ κΈ°μ‘΄μ Options API λ₯Ό TypeScript μ ν¨κ» μ¬μ©νλ κ²μ κ°λ₯νλ
TypeScript with Options API
Composition API μ TypeScript λ ν¨κ» μ¬μ©νλ κ²μ κΆμ₯
νλ κ²μ μ μ μλ€.
- /src/views/CalculatorOptionsAPI.vue
<template>
<div>
<h2>Calculator</h2>
<div>
<input type="text" v-model.number="num1" @keyup="plusNumbers">
<span> + </span>
<input type="text" v-model.number="num2" @keyup="plusNumbers">
<span> = </span>
<span> {{ result }} </span>
</div>
</div>
</template>
<script>
export default {
name: 'CalculatorOptionsAPI',
data () {
return {
num1: 0,
num2: 0,
result: 0
}
},
methods: {
plusNumbers () {
this.result = this.num1 + this.num2
}
}
}
</script>
- /src/views/CalculatorCompositionAPI.vue
beforeCreate
, created
Hooks λμ setup
μ μ¬μ©νλ€.
<template>
<div>
<h2>Calculator</h2>
<div>
<input type="text" v-model.number="state.num1" @keyup="plusNumbers">
<span> + </span>
<input type="text" v-model.number="state.num2" @keyup="plusNumbers">
<span> = </span>
<span> {{ state.result }} </span>
</div>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'CalculatorCompositionAPI',
setup () {
// data()
const state = reactive({
num1: 0,
num2: 0,
result: 0
})
// methods
const plusNumbers = () => {
state.result = state.num1 + state.num2
}
// return Composition
return {
state,
plusNumbers
}
}
}
</script>
Options API
μμdata()
μ ν΄λΉνλTwo-way data binding
μ ν λ°μ΄ν°λ₯Όreactive
λ₯Ό μ΄μ©ν΄ κ°μ²΄λ‘ μμ±ν΄ μ¬μ©νλ€. λ°λΌμ λͺ¨λ λ³μ/μμλ μ΄ κ°μ²΄λ₯Ό ν΅ν΄ μ κ·Όνλ€. μ μμ μμλstate
λ₯Ό μ΄μ©ν΄ μ κ·Όνλ€.
2. Use External Functions with toRefs
μμ κ°μ΄ setup()
μ μμ±λ μ½λλ ν΄λΉ μ»΄ν¬λνΈμμλ§ μ¬μ© κ°λ₯νλ€. λ§μ½ λ€λ₯Έ μ»΄ν¬λνΈμμ μ¬μ¬μ© κ°λ₯νλλ‘ νλ €λ©΄ μ΄λ₯Ό λ³λμ
ν¨μλ‘ λΆλ¦¬νκ³ , setup()
μμ μ΄κ²μ κ°μ Έμ μ¬μ©νλλ‘ ν΄μΌνλ€. μ¦, μ½λλ₯Ό ν¨μνμΌλ‘ μμ±ν΄ λΆλ¦¬νλ κ²μ΄λ€.
μ Options API
μμ μ Computed Properties
κΉμ§ ν¬ν¨λ λ‘μ§μ νμΈν ν μ΄λ₯Ό Composition API
λ°©μμΌλ‘ λ°κΎΈκ³
ν¨μνμΌλ‘ μμ±ν΄ λΆλ¦¬ν΄λ³΄μ.
- /src/views/CalculatorOptionsAPI.vue
<template>
<div>
<h2>Calculator</h2>
<div>
<input type="text" v-model.number="num1" @keyup="plusNumbers">
<span> + </span>
<input type="text" v-model.number="num2" @keyup="plusNumbers">
<span> = </span>
<span> {{ sum }} </span>
</div>
<div>
<input type="text" v-model.number="num1" disabled>
<span> x </span>
<input type="text" v-model.number="num2" disabled>
<span> = </span>
<span> {{ product }} </span>
</div>
</div>
</template>
<script>
export default {
name: 'CalculatorOptionsAPI',
data () {
return {
num1: 0,
num2: 0,
sum: 0
}
},
methods: {
plusNumbers () {
this.sum = this.num1 + this.num2
}
},
computed: {
product () {
return this.num1 * this.num2
}
}
}
</script>
- /src/views/CalculatorCompositionAPI.vue
<template>
<div>
<h2>Calculator</h2>
<div>
<input type="text" v-model.number="num1" @keyup="plusNumbers">
<span> + </span>
<input type="text" v-model.number="num2" @keyup="plusNumbers">
<span> = </span>
<span> {{ sum }} </span>
</div>
<div>
<input type="text" v-model.number="num1" disabled>
<span> x </span>
<input type="text" v-model.number="num2" disabled>
<span> = </span>
<span> {{ product }} </span>
</div>
</div>
</template>
<script>
import { reactive, computed, toRefs } from 'vue'
const sumTwoNumbers = () => { return (a, b) => a + b }
const calculator = () => {
const state = reactive({
num1: 0,
num2: 0,
sum: 0,
product: computed(() => state.num1 * state.num2)
})
return toRefs(state)
}
export default {
name: 'CalculatorCompositionAPI',
setup () {
const { num1, num2, sum, product } = calculator()
const plusNumbers = () => {
sum.value = sumTwoNumbers()(num1.value, num2.value)
}
// return Composition
return { num1, num2, sum, product, plusNumbers }
}
}
</script>
- reactive : Options API μ
data()
μ ν΄λΉνλ€.- computed : Options API μ
computed
μ ν΄λΉνλ€.- toRefs :
reactive
λ‘ μμ±λ λ°μ΄ν°λ₯Ό μ μ μλνλλ‘ νκΈ° μν΄Object
λ₯Ό μ΄μ©νReference Types
λ‘ λ³ννλ€.
(νλμValue
λ§Reference Type
μΌλ‘ λ°κΏμ£Όλref()
,Object
λ΄ νλμField
λ§Reference Types
λ‘ λ°κΏμ£ΌλtoRef()
λ μλ€. λ°λΌμ μ€μ κ°μ μ κ·Όν λλ.value
λ₯Ό μ΄μ©νλ€.)
calculator μ μ°κ΄λ λΆλΆμ setup
μμ μμ ν λΆλ¦¬μν€κΈ° μν΄ plusNumbers λ₯Ό calculator μμ λ£μ΄λ³΄μ.
λ¨, μ΄λ product μ κ°μ΄ computed
λ₯Ό μ΄μ©ν΄ λ΄λΆμμ λ‘μ§μ μ μνλ κ²μ΄ μλκ³ , λ λ€μ μΈλΆμμ λͺ¨λμ κ°μ Έμ€λ
μΈλΆ λͺ¨λν κ°λ
μ κ°μ ν΄ sumTwoNumbers λ calculator μΈλΆμμ κ°μ Έμ μ΄μ©νλλ‘ μ½λλ₯Ό μμ±ν΄λ³΄μ.
<template>
<div>
<h2>Calculator</h2>
<div>
<input type="text" v-model.number="num1" @keyup="plusNumbers">
<span> + </span>
<input type="text" v-model.number="num2" @keyup="plusNumbers">
<span> = </span>
<span> {{ sum }} </span>
</div>
<div>
<input type="text" v-model.number="num1" disabled>
<span> x </span>
<input type="text" v-model.number="num2" disabled>
<span> = </span>
<span> {{ product }} </span>
</div>
</div>
</template>
<script>
import { reactive, computed, toRefs } from 'vue'
const sumTwoNumbers = () => { return (a, b) => a + b }
const calculator = () => {
const state = reactive({
num1: 0,
num2: 0,
sum: 0,
product: computed(() => state.num1 * state.num2),
plusNumbers: () => { state.sum = sumTwoNumbers()(state.num1, state.num2) }
})
return toRefs(state)
}
export default {
name: 'CalculatorCompositionAPI',
setup () {
const { num1, num2, sum, product, plusNumbers } = calculator()
// return Composition
return { num1, num2, sum, product, plusNumbers }
}
}
</script>
3. Modularize into Separate Files
μ΄μ calculator μ μ°κ΄λ λΉμ¦λμ€ λ‘μ§μ΄ μμ ν λΆλ¦¬λμλ€. κ·Έλ λ€λ©΄ μ΄ μ½λλ μΈλΆ νμΌλ‘ μμ ν λΆλ¦¬μμΌ λ€μκ³Ό κ°μ΄ λ³λμ νμΌλ‘ λͺ¨λν μν¬ μ μλ€.
- /src/utils/calculator.js
import { reactive, computed, toRefs } from 'vue'
const sumTwoNumbers = () => { return (a, b) => a + b }
const calculator = () => {
const state = reactive({
num1: 0,
num2: 0,
sum: 0,
product: computed(() => state.num1 * state.num2),
plusNumbers: () => { state.sum = sumTwoNumbers()(state.num1, state.num2) }
})
return toRefs(state)
}
export {
sumTwoNumbers,
calculator
}
- /src/views/CalculatorWithExternalFiles.vue
<template>
<div>
<h2>Calculator</h2>
<div>
<input type="text" v-model.number="num1" @keyup="plusNumbers">
<span> + </span>
<input type="text" v-model.number="num2" @keyup="plusNumbers">
<span> = </span>
<span> {{ sum }} </span>
</div>
<div>
<input type="text" v-model.number="num1" disabled>
<span> x </span>
<input type="text" v-model.number="num2" disabled>
<span> = </span>
<span> {{ product }} </span>
</div>
</div>
</template>
<script>
import { calculator } from '@/utils/calculator'
export default {
name: 'CalculatorWithExternalFiles',
setup () {
const { num1, num2, sum, product, plusNumbers } = calculator()
// return Composition
return { num1, num2, sum, product, plusNumbers }
}
}
</script>
18. Lifecycle Hooks π©βπ»
Composition API
λ΄μμ μ¬μ©ν μ μλ Component Lifecycle Hooks
λ λ€μκ³Ό κ°λ€.
Options API | Composition API |
---|---|
Β | setup |
beforeCreate | Β |
created | Β |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
beforeCreate, created μ μΌμΉνλ Hooks λ Composition API μλ μ‘΄μ¬νμ§ μλλ€. λμ setup
μ μ¬μ©νλ©°,
λλ¨Έμ§ Hooks λ λͺ¨λ setup μμμ Composition API μμ μ 곡νλ λ©μλλ₯Ό μ΄μ©ν΄ μ μ
νλ€.
Composition API: Lifecycle Hooks
Composition API μμ onMounted
λ₯Ό μ΄μ©ν΄ μ½μμ λ©μμ§λ₯Ό μΆλ ₯νλλ‘ ν΄λ³΄μ.
<template>
<div>
<h2>Calculator</h2>
<div>
<input type="text" v-model.number="num1" @keyup="plusNumbers">
<span> + </span>
<input type="text" v-model.number="num2" @keyup="plusNumbers">
<span> = </span>
<span> {{ sum }} </span>
</div>
<div>
<input type="text" v-model.number="num1" disabled>
<span> x </span>
<input type="text" v-model.number="num2" disabled>
<span> = </span>
<span> {{ product }} </span>
</div>
</div>
<p> {{ initialMessage }} </p>
<p> {{ state.anotherMessage }} </p>
</template>
<script>
import { calculator } from '@/utils/calculator'
import { onMounted, reactive, ref } from 'vue'
export default {
name: 'CalculatorWithExternalFiles',
setup () {
const { num1, num2, sum, product, plusNumbers } = calculator()
const initialMessage = ref('')
const state = reactive({
anotherMessage: ''
})
onMounted(() => {
initialMessage.value = 'CalculatorWithExternalFiles.vue is mounted'
state.anotherMessage = 'Hello Vue.js'
})
// return Composition
return { num1, num2, sum, product, plusNumbers, initialMessage, state }
}
}
</script>
19. Provide/Inject in Composition API π©βπ»
Composition API μμ Provide/Inject
λ₯Ό μ¬μ©νλ €λ©΄ Hooks μ λ§μ°¬κ°μ§λ‘ vue μμ import ν΄μΌνλ€.
1 ) Provide
- /src/views/RootView.vue
<template>
<button type="button" @click="changeValue">Change Root Value</button>
<hr>
<FirstChild/>
</template>
<script>
import FirstChild from '@/components/FirstChild.vue'
import { provide, ref } from 'vue'
export default {
name: 'RootView',
components: {
FirstChild
},
setup () {
const rootValue = ref("Hello~ I'm root.")
const changeValue = (() => {
let times = 0
return () => {
rootValue.value = `Hello~ I'm root. is changed ${++times} times`
}
})()
provide('rootValue', rootValue)
return { changeValue }
}
}
</script>
- /src/components/FirstChild.vue
<template>
<SecondChild/>
</template>
<script>
import SecondChild from '@/components/SecondChild.vue'
export default {
name: 'FirstChild',
components: {
SecondChild
}
}
</script>
- /src/components/SecondChild.vue
<template>
<ThirdChild/>
</template>
<script>
import ThirdChild from '@/components/ThirdChild.vue'
export default {
name: 'SecondChild',
components: {
ThirdChild
}
}
</script>
2 ) App-level Provide
- /src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App)
.use(store)
.use(router)
.provide('appLevelValue', 'Hello~ This is App')
.mount('#app')
3 ) Inject
- /src/components/ThirdChild.vue
<template>
<p>This message is come from root : {{ rootMessage }}</p>
<p>This message is come from app : {{ appMessage }}</p>
</template>
<script>
import { inject } from 'vue'
export default {
name: 'ThirdChild',
setup () {
const rootMessage = inject('rootValue')
const appMessage = inject('appLevelValue')
return {
rootMessage,
appMessage
}
}
}
</script>
20. Mixins π©βπ»
1. Why use mixins?
mixins
λ Vue 2
κΉμ§ μ»΄ν¬λνΈ μ μμμ AOP
νλ‘κ·Έλλ°μ κ°λ₯νλλ‘ λλ μ’μ λͺ¨λν λ°©λ²μ΄λ€. λ©μλλ₯Ό μ μνκ±°λ Lifecycle Hooks
κΉμ§λ μ¬μ©ν μ μμ΄ Axios Interceptors μ
μ μ ν μ¬μ©νλ©΄ λ§μ μ½λλ₯Ό 곡ν΅ν ν μ μλ€.
- Axios Interceptors :
HTTP
μμ²μ 보λ΄κΈ° μ ν ν° μ£Όμ κ³Ό κ°μHTTP
μμ² μμ±μ κ΄λ ¨λ κ΄λ ¨λ κ²λ€μ 곡ν΅ν ν μ μλ€. - Vue.js mixins : μ»΄ν¬λνΈλ³ 곡ν΅νλ₯Ό μ²λ¦¬ν μ μλ€. λ¨μ λ©μλ μ¬μ¬μ© λΏ μλλΌ
Lifecycle Hooks
μbeforeCreate
λλsetup
κ³Ό ν¨κ» μ¬μ©νλ©΄ μ¬μ©μλ³ μ»΄ν¬λνΈμ μ κ·Ό κΆν κ°μ κ²λ€μ 곡ν΅ν ν μ μλ€.
2. Prefer Composition API
mixins in Composition API λ₯Ό 보면 Vue 3
μμ μ¬μ ν mixins λ₯Ό μ§μνμ§λ§
μ»΄ν¬λνΈκ° μ½λ μ¬μ¬μ©μ μν΄μλ Composition API λ₯Ό μ νΈ
νλΌκ³ λμ΄μλ€.
μ΄λ mixins
κ° Options API μ Lifecycle Hooks λ₯Ό 곡ν΅ν ν μ μμ΄ κ° μ»΄ν¬λνΈλ§λ€ λμΌν Hooks λ₯Ό μμ±νμ§ μμλ
λλ€λ μ₯μ μ μμ§λ§ νλ‘μ νΈ κ·λͺ¨κ° 컀μ§λ©° Nested Components ꡬ쑰μμ Overriding λλ λ¬Έμ κ° λ°μ
νκΈ° λλ¬Έμ΄λ€.
λ°λΌμ mixins λ Vue 2 κΉμ§λ§ μ¬μ©νκ³ , Vue 3
μμλ κΈ°μ‘΄ Vue 2 νλ‘μ νΈμμ λ§μ΄κ·Έλ μ΄μ
λ mixins λ₯Ό μ¬μ©ν
λͺ¨λμ Composition API λ₯Ό μ΄μ©
νλλ‘ λ³κ²½λμ΄μΌ ν κ²μ΄λ€.
(Lifecycle Hooks μ κ³΅ν΅ λ‘μ§μ κ°μ μ£Όμ
μ TypeScript λ‘ λμ΄κ° Interfaces λ₯Ό μ¬μ©νλ©΄ ν΄κ²°λ κ²μΌλ‘ 보μΈλ€)
3. Axios Examples with Composition API
μ§λλ² Axios Examples λ₯Ό
Composition API
μ mounted
λ₯Ό μ΄μ©ν΄ λ€μ ꡬνν΄λ³΄μ.
- /src/utils/api.js
npm i axios -S
import axios from 'axios'
const $api = axios.create({
baseURL: 'https://0000.mock.pstmn.io'
})
const $get = async (url, data) =>
await $api.get(url, data).then(successHandler).catch(errorHandler);
const $post = async (url, data) =>
await $api.post(url, data).then(successHandler).catch(errorHandler);
const $put = async (url, data) =>
await $api.put(url, data).then(successHandler).catch(errorHandler);
const $patch = async (url, data) =>
await $api.patch(url, data).then(successHandler).catch(errorHandler);
const $delete = async (url, data) =>
await $api.delete(url, data).then(successHandler).catch(errorHandler);
const successHandler = (res) => {
if ((res.status / 200).toFixed() !== "1") {
throw new HTTPError(res.status, res.statusText);
} else {
return res.data;
}
};
const errorHandler = (error) => {
// Step 1. Send error to server for log.
// Step 2. Throw error to components
throw error;
};
class HTTPError extends Error {
constructor(status, statusText) {
super(`HTTP Error ${status}: ${statusText}`);
this.status = status;
this.statusText = statusText;
}
}
export { $api, $get, $post, $put, $patch, $delete };
λ©μλ μ΄λ¦ μμ
$
λ₯Ό λΆμ΄λ μ΄μ λ νΉμ ν κΈ°λ₯μ΄ μλ κ²μ μλκ³ λ€μκ³Ό κ°λ€.
- μ»΄ν¬λνΈμμ νΉμλΌλ λμΌν μ΄λ¦μ ν¨μκ° μ‘΄μ¬ν κ²½μ°
Overriding
λλ κ²μ λ°©μ§.- μ»΄ν¬λνΈκ° μλ 곡ν΅ν λͺ¨λμμ μ¨ λ©μλλΌλ κ²μ μκ°μ μΌλ‘ ννν΄ μ½λ λΆμμ μ©μ΄νκ² ν¨.
- /src/dto/Product.js
export default class Product {
productName
price
category
constructor (productName, price, category) {
this.productName = productName
this.price = price
this.category = category
}
}
- /src/views/AxiosWithCompositionAPI.vue
<template>
<div>
<table>
<thead>
<tr>
<th>μ νλͺ
</th>
<th>κ°κ²©</th>
<th>μΉ΄ν
κ³ λ¦¬</th>
</tr>
</thead>
<tbody>
<tr v-for="(product, i) in state.productList" :key="i">
<td>{{ product.productName }}</td>
<td>{{ product.price }}</td>
<td>{{ product.category }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import Product from '@/dto/Product'
import { onMounted, reactive } from 'vue'
import { $get } from '@/utils/api'
export default {
name: 'AxiosWithCompositionAPI',
setup () {
const state = reactive({
productList: Array[Product]
})
const getList = async () => {
state.productList = await $get('/test')
}
onMounted(() => {
getList()
})
return { state }
}
}
</script>
<style scoped>
table {
font-family: Arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #ddd;
text-align: left;
padding: 8px;
}
</style>
μμ κ°μ΄ reactive
λ₯Ό μ¬μ©νκ±°λ μλμ κ°μ΄ ref
λ₯Ό μ¬μ©ν΄ binding νλ€.
<template>
<div>
<table>
<thead>
<tr>
<th>μ νλͺ
</th>
<th>κ°κ²©</th>
<th>μΉ΄ν
κ³ λ¦¬</th>
</tr>
</thead>
<tbody>
<tr v-for="(product, i) in productList" :key="i">
<td>{{ product.productName }}</td>
<td>{{ product.price }}</td>
<td>{{ product.category }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import Product from '@/dto/Product'
import { onMounted, ref } from 'vue'
import { $get } from '@/utils/api'
export default {
name: 'AxiosWithCompositionAPI',
setup () {
const productList = ref(Array[Product])
const getList = async () => {
productList.value = await $get('/test')
}
onMounted(() => {
getList()
})
return { productList }
}
}
</script>
<style scoped>
table {
font-family: Arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #ddd;
text-align: left;
padding: 8px;
}
</style>
λ¨,
ref
,toRef
,toRefs
λ₯Ό μ¬μ©νλ©΄reactive
μ λ¬λ¦¬DTO
λ₯Ό μ¬μ©ν νμ μΆμ μ΄ λμ§ μμ μ½λ μλμμ±κ³Ό κ°μ κ²μ΄ μ μλνμ§ μλλ€. λ°λΌμ κ°κΈμ μ΄λ©΄DTO
μ κ°μ΄ νμ μ κΈ°μ΅ν νμκ° μλ κ³³μreactive
λ₯Ό μ¬μ©νλ κ²μ΄ μ’μ κ²μΌλ‘ 보μΈλ€.
21. Custom Directives π©βπ»
Vue λ v-model, v-show μ κ°μ 미리 μ μλ Built-in Directives
μΈμ Custom Directives
λ₯Ό μ¬μ©ν μ μλ€.
μλ₯Ό λ€μ΄ μ¬λ¬ νμ΄μ§λ‘ λλ νμκ°μ
νμ΄μ§μμ κ° νμ΄μ§λ§λ€ 첫 βinputβ μ μλμΌλ‘ focus μν€λ κ²½μ°λ₯Ό κ°μ ν΄λ³΄μ.
Vanilla JS
μμλ *window λλ document μ onload()
λλ addEventListener('load', () => {})
μ κ°μ
μ½λμ νΉμ HTML μ attribute λ₯Ό μ°Ύμ focus()
λ₯Ό μ€νμν€λλ‘ νλ μ½λλ₯Ό λ³λμ JavaScript νμΌλ‘ λ§λ€μ΄ λͺ¨λν νκ³ ,
κ° HTML νμ΄μ§μλ ν΄λΉ attribute λ₯Ό μΆκ°
ν΄μ€μΌλ‘μ¨ focus λλλ‘ κ³΅ν΅ν μ²λ¦¬λ₯Ό ν μ μλ€.
Vue λ μ΄ κ³Όμ μ Vue Instance λ₯Ό μμ±ν λ Custom Directives
λ₯Ό λ§λ€μ΄ μ§μ νκ³
κ° μ»΄ν¬λνΈμμλ ν΄λΉ Directives λ₯Ό μΆκ°
νκΈ°λ§ νλ©΄ focus λλλ‘ κ³΅ν΅ν μ²λ¦¬λ₯Ό ν μ μλ€.
1. Directive Hooks
Directive Hooks μμ μ¬μ©ν μ μλ Hooks μ μ’ λ₯λ λ€μκ³Ό κ°λ€.
const myDirective = {
// called before bound element's attributes
// or event listeners are applied
created(el, binding, vnode, prevVnode) {
// see below for details on arguments
},
// called right before the element is inserted into the DOM.
beforeMount(el, binding, vnode, prevVnode) {},
// called when the bound element's parent component
// and all its children are mounted.
mounted(el, binding, vnode, prevVnode) {},
// called before the parent component is updated
beforeUpdate(el, binding, vnode, prevVnode) {},
// called after the parent component and
// all of its children have updated
updated(el, binding, vnode, prevVnode) {},
// called before the parent component is unmounted
beforeUnmount(el, binding, vnode, prevVnode) {},
// called when the parent component is unmounted
unmounted(el, binding, vnode, prevVnode) {}
}
2. Hook Arguments
Directive Hooks
μ΄ κ°λ arguments
λ λ€μκ³Ό κ°λ€.
el
: the element the directive is bound to. This can be used to directly manipulate the DOM.
binding
: an object containing the following properties.
value
: The value passed to the directive. For example inv-my-directive="1 + 1"
, the value would be2
.
oldValue
: The previous value, only available inbeforeUpdate
andupdated
. It is available whether or not the value has changed.
arg
: The argument passed to the directive, if any. For example inv-my-directive:foo
, the arg would be"foo"
.
modifiers
: An object containing modifiers, if any. For example inv-my-directive.foo.bar
, the modifiers object would be{ foo: true, bar: true }
.
instance
: The instance of the component where the directive is used.
dir
: the directive definition object.
vnode
: the underlying VNode representing the bound element.
prevNode
: the VNode representing the bound element from the previous render. Only available in thebeforeUpdate
andupdated
hooks.
μλ₯Ό λ€μ΄ λ€μκ³Ό κ°μ Custom Directive κ° μλ€κ³ κ°μ ν΄λ³΄μ.
<template>
<div v-example:foo.bar="baz"></div>
</template>
μ΄λ binding
argument μ Object λ λ€μκ³Ό κ°μ κ²μ΄λ€.
{
arg: 'foo',
modifiers: { bar: true },
value: /* value of `baz` */,
oldValue: /* value of `baz` from previous update */
}
λν Custom Directives
μμ Built-in Directives μ λ§μ°¬κ°μ§λ‘ attribute μ value λ₯Ό μ΄μ©ν
Dynamic μ²λ¦¬κ° κ°λ₯
νλ€.
<template>
<div v-example:[arg]="value"></div>
</template>
el
μ μ μΈνarguments
μμ λμ΄μλ μ λλ€. λ°λμRead-only
λ‘ λ€λ£¨μ΄μ ΈμΌνλ€. λ§μ½ μλ‘ λ€λ₯ΈHooks
κ° λ°μ΄ν°λ₯Ό 곡μ ν νμκ° μλ€λ©΄ dataset λ₯Ό μ¬μ©νλλ‘ νλ€.
3. Create Global Custom Directives
main.js
μμ Vue
Instance λ₯Ό μμ±νλ μμ μ v-focus
directive λ₯Ό μΆκ°ν΄ λͺ¨λ μ»΄ν¬λνΈμμ μ¬μ©ν μ μλλ‘ λ±λ‘νμ.
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App)
.use(store)
.use(router)
.directive('focus', {
mounted (el) {
el.focus()
}
})
.provide('appLevelValue', 'Hello~ This is App')
.mount('#app')
v-focus
directive μ μv-
λ HTML μ attributes μ μ΄κ²μ Vue μ μν΄ κ΄λ¦¬λλ Directives λΌλ κ²μ μΈμνλλ‘ λΆμ¬μ£Όλprefix
λ‘, Custom Directives λ₯Ό λ§λ€λ κ·Έ μ΄λ¦μv-
λ μ μΈνκ³ λ§λ€μ΄μΌνλ€.
4. Function Shorthand
λλΆλΆμ Directives λ κΈ°λ³Έμ μΌλ‘ mounted μ updated μΌ λ λμΌνκ² μλ
νλ€. λ§μ½, mounted μ updated Hooks μ
μλμ΄ λμΌνλ©°, λ€λ₯Έ Hooks μ΄ νμ μμ κ²½μ° λ€λ₯Έ μ€μ κ°κ³Ό λ§μ°¬κ°μ§λ‘ APP μ mount()
λ©μλλ₯Ό λΆλ¦¬μν¨ ν
Custom Directives λ₯Ό λ€μκ³Ό κ°μ΄ ν¨μνμΌλ‘ μμ±
ν μ μλ€.
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const myApp = createApp(App)
.use(store)
.use(router)
.provide('appLevelValue', 'Hello~ This is App')
myApp.directive('focus', (el, binding) => {
el.focus()
})
myApp.mount('#app')
λ μλμ κ°λ€.
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App)
.use(store)
.use(router)
.directive('focus', {
mounted (el) {
el.focus()
},
updated (el) {
el.focus()
}
})
.provide('appLevelValue', 'Hello~ This is App')
.mount('#app')
5. Create Local Custom Directives
Custom Directives λ₯Ό λͺ¨λ Global λ‘ λ±λ‘ν κ²½μ° λ무 λ§μ Directives κ° μ겨λκ³ μ μ μ€μΌ
μ΄ λ°μνλ€. λ°λΌμ,
κ° μ»΄ν¬λνΈμμλ§ μ¬μ©ν Custom Directives κ° νμν κ²½μ° μ»΄ν¬λνΈ λ΄μμ λ±λ‘ν΄ μ¬μ©
ν μ μλ€. μ v-focus
directive
λ₯Ό μ»΄ν¬λνΈ λ΄μμ Local λ‘ λ±λ‘
ν΄λ³΄μ.
<template>
<p>Global Custom Directive 'focus' : <input type="text" v-focus></p>
<img v-pin="{ position: 'fixed', top: 20, left: 20 }" :src="blogIcon" alt="blog-icon" :style="iconSize">
</template>
<script>
import { ref } from 'vue'
export default {
name: 'CustomDirectives',
setup () {
const blogIcon = ref('/greendreamtree.png')
const iconSize = { height: '70px', weight: '70px;' }
return { blogIcon, iconSize }
},
directives: {
pin: {
mounted (el, binding) {
el.style.position = binding.value.position
el.style.top = `${binding.value.top}px`
el.style.left = `${binding.value.left}px`
}
}
}
}
</script>
6. Object Literals
Directives
μ μ¬λ¬ κ°μ κ°μ΄ νμν λ Object Literals
λ₯Ό μ¬μ©ν μ μλ€. μ μμ μμ
<template>
<img v-pin="{ position: 'fixed', top: 20, left: 20 }">
</template>
<script>
export default {
name: 'CustomDirectives',
directives: {
pin: {
mounted (el, binding) {
el.style.position = binding.value.position
el.style.top = `${binding.value.top}px`
el.style.left = `${binding.value.left}px`
}
}
}
}
</script>
μ΄ λΆλΆμ ν΄λΉνλ€.
7. Use Custom Directives
<template>
<p>Global Custom Directive 'focus' : <input type="text" v-focus></p>
<img v-pin="{ position: 'fixed', top: 20, left: 20 }" :src="blogIcon" alt="blog-icon" :style="iconSize">
</template>
Custom Directives λ‘ focus
μ pin
μ λ§λ€μλ€. μ¬μ©ν λλ *Vue κ° Directives λ‘ μΈμν μ μλλ‘ prefix(v-
)
λ₯Ό λΆμ¬ v-focus
, v-pin
μΌλ‘ μ¬μ©νλ€.
22. Plugins π©βπ»
1. Introduction
NPM
ν¨μ§μ§ 맀λμ λ₯Ό μ¬μ©ν΄ 곡κ°λ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νλ κ² μΈμ μ¬μ©μκ° μ§μ λ§λ€μ΄ μ¬μ©νλ κ² μμ κ°λ₯νλ€.
Plugins
μ΄λ건 μμμ λͺ¨λνλ₯Ό μν΄ λ§λ api.js λ calculator.js μ κ°μ Utilities
μ λ¬λ¦¬, App-level
μμ
μλνλ κΈ°λ₯μ΄λ€.
Pluginsλ install(app, options)
λ©μλλ₯Ό λ
ΈμΆμν€λ λ°©λ²μΌλ‘ μ μλλ€. install(app, options) λ©μλλ
κΈ°λ³Έμ μΌλ‘ App Instance
λ₯Ό μ λ¬λ°μΌλ©°, app.use('plugins', 'options')
λ₯Ό ν΅ν΄ options
λ₯Ό μ λ¬λ°λλ€.
- Plugins λ₯Ό μ μ
const myPlugin = {
install(app, options) {
// configure the app
}
}
- Plugins λ₯Ό μ¬μ©
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
/* optional options */
})
Plugins λ₯Ό λ§λλ κ²μ μ격ν μ μλ κ²μ΄ μ‘΄μ¬νμ§λ μλλ€. νμ§λ§ μΌλ°μ μΌλ‘ μ μ©ν νλ¬κ·ΈμΈμ μλ리μ€λ λ€μκ³Ό κ°λ€.
- νλ μ΄μμ
Global Components
λλCustom Directives
λ₯Όapp.component()
λλapp.directive()
λ₯Ό μ¬μ©ν΄ λ±λ‘νλ€.app.provide()
λ₯Ό νΈμΆν΄ μ± μ μμμ 리μμ€λ₯Όinjectable
νλλ‘ λ§λ λ€.- μΌλΆ
Global Instance Properties
λλMethods
λ₯Όapp.config.globalProperties
μ μΆκ°νλ€.μ΄λ¬ν 쑰건μ λ§μ‘±νλ μ’μ λΌμ΄λΈλ¬λ¦¬μ μ μ€ νλκ° vue-router λ€.
2. Writing a Plugin
i18n
μ μ²λ¦¬ν΄μ£Όλ νλ¬κ·ΈμΈμ λ§λ€κ³ , μ΄λ₯Ό μ μ©ν΄λ³΄λλ‘ νλ€.
Internationalization
μ μνλ²³μ 20κΈμμΈλ° μ΄κ±Έ λ€ μ°λ©΄ κΈ°λκΉ λ§¨ μi
μ 18μ리nternationalizatio
, κ·Έλ¦¬κ³ λ§μ§λ§n
μΌλ‘ λΆλ¦¬ν΄i18n
μ΄λΌ λΆλ₯Έλ€.
- /src/plugins/i18n.js
export default {
install: (app, options) => {
// inject a globally available $translate() method
app.config.globalProperties.$translate = (key) => {
// retrieve a nested property in `options`
// using `key` as the path
return key.split('.').reduce((o, i) => {
return o ? o[i] : null
}, options)
}
}
}
- /src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import i18n from '@/plugins/i18n'
const myApp = createApp(App)
.use(store)
.use(router)
.use(i18n, {
greetings: {
en: 'Hello!',
ko: 'μλ
νμΈμ!',
fr: 'Bonjour!',
de: 'Hallo!'
}
})
.provide('appLevelValue', 'Hello~ This is App')
myApp.directive('focus', (el, binding) => {
el.focus()
})
myApp.mount('#app')
- /src/views/CustomPlugins.vue
<template>
<h3>English</h3>
<p>{{ $translate('greetings.en') }}</p>
<h3>Korean</h3>
<p>{{ $translate('greetings.ko') }}</p>
<h3>French</h3>
<p>{{ $translate('greetings.fr') }}</p>
<h3>German</h3>
<p>{{ $translate('greetings.de') }}</p>
</template>
<script>
export default {
name: 'CustomPlugins'
}
</script>
app.config.globalProperties.$translate
μ 보면globalProperties
μ$translate
λΌλ μ΄λ¦μΌλ‘ ν¨μλ₯Ό λ₯Ό λ±λ‘ν κ²μ λ³Ό μ μλ€. λ°λΌμ μ± μ μμμ λ³λμ import μμ΄$translate
λ₯Ό μ¬μ©ν μ μλ€.
3. Provide/Inject with Plugins
Plugins λ App-level μμ μλ
νλ κ²μ μν΄ λ§λ λ€κ³ νλ€. λ°λΌμ μμμ app.use()
λ₯Ό μ΄μ©ν΄ μ± μ μμ
μ¬μ©ν μ μλλ‘ Custom Plugins λ₯Ό Global λ‘ λ±λ‘
νκΈ° λλ¬Έμ μ΄λ€ μ»΄ν¬λνΈμμλ globalProperties μ λ±λ‘λ
$translate
λΌλ ν¨μλ₯Ό λ³λμ import μμ΄ μ¬μ©ν μ μμλ€(e.g. $translate('greetings.en')
).
Create Local Custom Directives μμ λ³Έ κ²μ²λΌ
μ μ μ€μΌ
μ νΌνκΈ° μν΄ μΌλΆλ¬ Local λ‘ λ±λ‘νλ κ²½μ°κ° μ‘΄μ¬νλ€. Plugins μμ κ°λ₯νλ°, globalProperties
μ λ±λ‘νλ
λμ Provide/Inject
λ₯Ό μ¬μ©νλ κ²μ΄λ€.
- /src/plugins/i18n.js
export default {
install: (app, options) => {
// inject a globally available $translate() method
app.config.globalProperties.$translate = (key) => {
// retrieve a nested property in `options`
// using `key` as the path
return key.split('.').reduce((o, i) => {
return o ? o[i] : null
}, options)
}
app.provide('i18n', options)
}
}
μ΄μ Custom Plugins λ *Vue Instance λ₯Ό μμ±ν λ app.provide()
λ₯Ό μ΄μ©ν΄ μμ μ μ μμΌλ‘ λ±λ‘νκ³ , μ¬μ©ν μ»΄ν¬λνΈμμ
μ΄λ₯Ό Inject
ν΄ μ£Όμ
ν μ¬μ©νκ²λλ€.
- /src/views/CustomPlugins.vue
<template>
<h3>English</h3>
<p>{{ $translate('greetings.en') }}</p>
<h3>Korean</h3>
<p>{{ $translate('greetings.ko') }}</p>
<h3>French</h3>
<p>{{ this.i18n.greetings.fr }}</p>
<h3>German</h3>
<p>{{ this.i18n.greetings.de }}</p>
</template>
<script>
export default {
name: 'CustomPlugins',
inject: ['i18n']
}
</script>
μ€μ κ°λ°μμ μ΄λ° κ²λ€μ μ§μ κ°λ°νλ κ² λ³΄λ€λ λ€μκ° μ°Έμ¬νλ μ€νμμ€λ₯Ό μ¬μ©νλ κ²μ΄ λ μ’λ€.
Vue I18n for Vue 2 λλ Vue I18n for Vue 3 μ κ°μ κ²λ€μ΄ μμΌλ μ°Έκ³ νλλ‘ νλ€.
Reference
- κ³ μΉμ. Vue.js νλ‘μ νΈ ν¬μ μΌμ£ΌμΌ μ . λΉμ μ΄νΌλΈλ¦ Chapter 9, 2021.
- βReactivity API: Core.β Vue.js. accessed Jan. 05, 2022, Reactivity: Core.
- βComposition API: Lifecycle Hooks.β Vue.js. accessed Jan. 05, 2023, Composition API: Lifecycle Hooks.
- βOptions: Composition #mixins.β Vue.js. accessed Jan. 07, 2023, mixins in Composition API.
- βCustom Directives.β Vue.js. accessed Jan. 08, 2023, Reusability: Custom Directives.
- βHTMLElement.dataset.β MDN, Oct. 26, 2022, HTML Element dataset.
- βPlugins.β Vue.js. accessed Jan. 08, 2023, Reusability: Plugins.
- βvue-router.β GitHub. Jan. 05, 2023, Vue Router.
- βVue I18n.β GitHub. May. 03, 2022, Vue I18n for Vue 2.
- βVue I18n.β Vue-I18n, Aug. 01, 2022, Vue I18n for Vue 3.