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 πŸ‘©β€πŸ’»

Vue Lifecycle Hookds

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>

Composition API Hooks


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>

Composition API Provide/Inject 1

Composition API Provide/Inject 2


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>

Axios with Composition API

단, 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 in v-my-directive="1 + 1", the value would be 2.
oldValue: The previous value, only available in beforeUpdate and updated. It is available whether or not the value has changed.
arg: The argument passed to the directive, if any. For example in v-my-directive:foo, the arg would be "foo".
modifiers: An object containing modifiers, if any. For example in v-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 the beforeUpdate and updated 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 On

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 λ₯Ό λ§Œλ“œλŠ” 것에 μ—„κ²©νžˆ μ •μ˜λœ 것이 μ‘΄μž¬ν•˜μ§€λŠ” μ•ŠλŠ”λ‹€. ν•˜μ§€λ§Œ 일반적으둜 μœ μš©ν•œ ν”ŒλŸ¬κ·ΈμΈμ˜ μ‹œλ‚˜λ¦¬μ˜€λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  1. ν•˜λ‚˜ μ΄μƒμ˜ Global Components λ˜λŠ” Custom Directivesλ₯Ό app.component() λ˜λŠ” app.directive() λ₯Ό μ‚¬μš©ν•΄ λ“±λ‘ν•œλ‹€.
  2. app.provide()λ₯Ό ν˜ΈμΆœν•΄ μ•± μ „μ—­μ—μ„œ λ¦¬μ†ŒμŠ€λ₯Ό injectable ν•˜λ„λ‘ λ§Œλ“ λ‹€.
  3. 일뢀 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λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

Custom Plugins i18n

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>

Custom Plugins i18n

μ‹€μ œ κ°œλ°œμ—μ„œ 이런 것듀은 직접 κ°œλ°œν•˜λŠ” 것 λ³΄λ‹€λŠ” λ‹€μˆ˜κ°€ μ°Έμ—¬ν•˜λŠ” μ˜€ν”ˆμ†ŒμŠ€λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 더 μ’‹λ‹€.
Vue I18n for Vue 2 λ˜λŠ” Vue I18n for Vue 3 와 같은 것듀이 μžˆμœΌλ‹ˆ μ°Έκ³ ν•˜λ„λ‘ ν•œλ‹€.




Reference

  1. κ³ μŠΉμ›. Vue.js ν”„λ‘œμ νŠΈ νˆ¬μž… 일주일 μ „. λΉ„μ œμ΄νΌλΈ”λ¦­ Chapter 9, 2021.
  2. β€œReactivity API: Core.” Vue.js. accessed Jan. 05, 2022, Reactivity: Core.
  3. β€œComposition API: Lifecycle Hooks.” Vue.js. accessed Jan. 05, 2023, Composition API: Lifecycle Hooks.
  4. β€œOptions: Composition #mixins.” Vue.js. accessed Jan. 07, 2023, mixins in Composition API.
  5. β€œCustom Directives.” Vue.js. accessed Jan. 08, 2023, Reusability: Custom Directives.
  6. β€œHTMLElement.dataset.” MDN, Oct. 26, 2022, HTML Element dataset.
  7. β€œPlugins.” Vue.js. accessed Jan. 08, 2023, Reusability: Plugins.
  8. β€œvue-router.” GitHub. Jan. 05, 2023, Vue Router.
  9. β€œVue I18n.” GitHub. May. 03, 2022, Vue I18n for Vue 2.
  10. β€œVue I18n.” Vue-I18n, Aug. 01, 2022, Vue I18n for Vue 3.