๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ‘“Vue

[Vue]Dynamic components, ๋‚ด๊ฐ€ ์›ํ•  ๋•Œ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

by ๋นˆ์„ฑ_ 2021. 12. 26.
๋ฐ˜์‘ํ˜•

Intro

์•ˆ๋…•ํ•˜์„ธ์š”. ๊ฐœ๋ฐœ์ž ์œ„์„ฑ๋นˆ์ž…๋‹ˆ๋‹ค. ์ตœ๊ทผ์— Vue.js ๊ณต๋ถ€๋ฅผ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กญ๊ฒŒ ๋ฐฐ์šด ๋†ˆ์ด๋‹ˆ ๋ฐ”๋กœ ์จ๋จน๊ณ  ์‹ถ์–ด ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ–ˆ์ฃ . ํ”„๋กœ์ ํŠธ ์ œ๋ชฉ์€ 'everything', '๋Œ€์‹œ๋ณด๋“œ ์œ„์— ์ƒ๊ฐ๋‚˜๋Š” ๋ชจ๋“  ๊ฒƒ์„ ์˜ฌ๋ ค๋ณด์ž'๋ผ๋Š” ์•„์ด๋””์–ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ฐจ๊ทผ์ฐจ๊ทผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค ๋งŒ๋‚œ 'Dynamic components', ๋™์  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ๊ฒช์—ˆ๋˜ ๊ฒƒ๋“ค์„ ์˜ค๋Š˜ ์ ์–ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

Result

<template>
  <div class="hello">
        <!-- ... -->
    <component :is="componentLoader"></component>
  </div>
</template>

 

export default{
  // ...
    data() {
        return {
            // ...
            , currentTab: 'VueRouter'
        }
    },
  computed: {
    componentLoader() {
            const tab = this.currentTab
            return () =>import(`@/components/${tab}`)
    }
  }
}

currentTab ๋ณ€์ˆ˜์— ๋“ค์–ด๊ฐ€๋Š” String ๊ฐ’์— ๋”ฐ๋ผ์„œ componentLoader๊ฐ€ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

componentLoader๋Š” currentTab์— ๋งž๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋™์ ์œผ๋กœ import.

๊ฒฐ๊ณผ์ ์œผ๋กœ <component>  ํƒœ๊ทธ ์ž๋ฆฌ์— ์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

 

์ „์ฒด ์†Œ์Šค ์ฝ”๋“œ๋Š” ์ด๊ณณ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•˜์‹ญ๋‹ˆ๋‹ค.

์ฃผ์ €๋ฆฌ์ฃผ์ €๋ฆฌ

'everything' ์ค‘ Memo ๊ธฐ๋Šฅ

'everything'์— ๊ตฌํ˜„๋  ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ธ 'Memo' ๊ธฐ๋Šฅ์˜ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค. 'Memo' ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋ฉ”๋ชจ์žฅ์ด ๋Œ€์‹œ๋ณด๋“œ ์œ„์— ์ถ”๊ฐ€๋˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ์€ 'Memo' ๋ฐ–์— ์—†์ง€๋งŒ '๋‹ฌ๋ ฅ'์ด๋‚˜ '๋‚ ์”จ' ๋“ฑ์„ ์ถ”๊ฐ€ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ถ”๊ฐ€๋˜๋Š” ๊ธฐ๋Šฅ๋“ค์€ ๋ชจ๋‘ ํ™”๋ฉด ์œ„๋ฅผ ์ž์œ ๋กญ๊ฒŒ ๋– ๋‹ค๋‹ˆ๋Š” Floating ๊ธฐ๋Šฅ์„ ๊ณตํ†ต์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ €๋Š” ์ด๊ฑธ Widget์ด๋ผ๊ณ  ๋ถ€๋ฅด๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

Widget ์ปดํฌ๋„ŒํŠธ๋Š” Floating ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๊ฒƒ ๋ง๊ณ ๋Š” ์•„๋ฌด๊ฒƒ๋„ ์—†์Šต๋‹ˆ๋‹ค. ๋– ๋‹ค๋‹ˆ๋Š” ์† ๋นˆ ๊ฐ•์ •์ด๋ž„๊นŒ์š”? ํ•˜์ง€๋งŒ ๊ทธ ๋นˆ ๊ณต๊ฐ„์— ๋™์  ์ปดํฌ๋„ŒํŠธ(Dynamic Components)๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด ๋ญ๋“  ๋– ๋‹ค๋‹ˆ๋Š” ์œ„์ ฏ์ด ๋งŒ๋“ค์–ด์ง€๊ฒ ์ฃ .

๊ตฌํ˜„

new Vue({
  // ...
  components: {
    "my-component": () => import("./my-async-component")
  }
});

Vue ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์—์„œ ์•ˆ๋‚ดํ•˜๋Š” ๋™์ ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด๋‘๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ๋™์ ์œผ๋กœ ๋กœ๋“œ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ƒ์˜ ์ด์ ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์•Œ๋ ค์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฑด ์ œ๊ฐ€ ์›ํ•˜๋˜ ๋ฐฉ์‹์€ ์•„๋‹ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด

export default{
  // ...
  components: {
    VueRouter: () => import('@/components/VueRouter'),
    Vuex: () => import('@/components/Vuex'),
    VueDevtools: () => import('@/components/VueDevtools'),
  }
}

์ด๋ ‡๊ฒŒ n๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ, ์ฆ‰ ์œ„์ ฏ๋“ค์ด ๋งŒ๋“ค์–ด์ง„๋‹ค๋ฉด n๋ฒˆ ๋ฐ˜๋ณตํ•˜์—ฌ components์— ๋“ฑ๋กํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ํ˜„์‹ค์ ์œผ๋กœ ์ƒ๊ฐํ•˜๋ฉด 'evrerything'์€ ์ž‘์€ ํ”„๋กœ์ ํŠธ์ด๋‹ˆ ์ด๊ฒƒ๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํ•  ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ... ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ n๋ฒˆ ๋ฐ˜๋ณตํ•˜๋ผ๋‡จ. ์‹ซ์Šต๋‹ˆ๋‹ค!

๊ทธ๋ž˜์„œ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค! ์งœ์ž”~

<template>
  <!-- ... -->
  <li
    ...
    @click="currentTab = tab">
    {{ tab }}
  </li>
  <!-- ... -->
  <component:is="componentLoader"></component>
</template>

 

export default{
  // ...
  data() {
    return {
      // ...
      , currentTab: 'VueRouter'
    }
  },
  computed: {
    componentLoader() {
      return () => import(`@/components/${this.currentTab}`)
    }
  }
}

computed๋ฅผ ํ™œ์šฉํ•œ ๋™์  ์ปดํฌ๋„ŒํŠธ ๋ฐฉ์‹. ์•ž์„œ ๋ณด์—ฌ๋“œ๋ฆฐ ๋ฐฉ์‹๊ณผ ๋น„์Šทํ•ด ๋ณด์ด์ง€๋งŒ n๊ฐœ์˜ ์œ„์ ฏ์ด ์ƒ๊ธด๋‹ค๊ณ  ํ•˜๋”๋ผ๊ณ  props๋‚˜ data๋ฅผ ํ™œ์šฉํ•ด ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋˜์—ˆ์ฃ . ์ด์ œ ์ œ๊ฐ€ ์ƒ๊ฐํ–ˆ๋˜ '๋™์  ์ปดํฌ๋„ŒํŠธ'๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ!! ๋„๋Œ€์ฒด ์™œ! ํ•œ ๋ฒˆ์— ๋˜๋Š” ๊ฒŒ ์—†์„๊นŒ์š”.

currentTab์˜ ๊ฐ’์€ ์ œ๋Œ€๋กœ ๋ณ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ ์ด์ƒํ•˜๊ฒŒ computed๋ฅผ ํ™œ์šฉํ•œ ๋ฐฉ์‹์€ ๋™์ž‘ํ•˜์งˆ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ์ด์œ ๊ฐ€ ๋ญ˜๊นŒ ํ•˜๋ฉฐ ์ด๊ฒƒ์ €๊ฒƒ ํ•ด๋ณด๋‹ค๊ฐ€ ์ฐพ์€๊ฑด computed์˜ ํŠน๋ณ„ํ•จ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

export default{
  // ...
  data() {
    return {
      // ...
      , currentTab: 'VueRouter'
    }
  },
  computed: {
    componentLoader() {
      console.log(this.currentTab)
      return () =>import(`@/components/${this.currentTab}`)
    }
  }
}

๋งŒ์•ฝ ์ด๋ ‡๊ฒŒ componentLoader ํ•จ์ˆ˜์— ์ „ํ˜€ ์˜๋ฏธ ์—†๋Š” console.log๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์–ด๋–จ๊นŒ์š”?

๋†€๋ž๊ฒŒ๋„ ์ด๋ ‡๊ฒŒ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

vue์˜ computed๋Š” ์บ์‹ฑ ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์‹ ์ด ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” data์˜ ๊ฐ’์ด ๋ณ€ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์บ์‹ฑ๋œ ๊ฐ’์„ ๊บผ๋‚ด์„œ returnํ•˜๋Š” ๊ธฐ๋Šฅ์ด์ฃ . ์ €ํฌ ๋ˆˆ์œผ๋กœ ๋ณด๊ธฐ์—” componentLoader๊ฐ€ currentTab์„ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋ก  ๊ทธ๋ ‡์ง€ ๋ชปํ•ด ๋™์ž‘์„ ํ•˜์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ด์ฃ .

computedWatchers

์ด ๋ถ€๋ถ„์„ ์ด์•ผ๊ธฐํ•˜๊ธฐ ์œ„ํ•ด์„  computedWatchers ์†์„ฑ์— ๋Œ€ํ•ด ์•Œ์•„๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฑด Vue์˜ Reactivity ์‹œ์Šคํ…œ๊ณผ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. Vue์˜ Reactivity ์‹œ์Šคํ…œ์„ ์ด์•ผ๊ธฐ๋ฅผ ์—ฌ๊ธฐ์— ํ’€์–ด๋‚ด๊ธฐ์—” ์• ๋‹น์ดˆ ์ด ๊ธ€์˜ ์ฃผ์ œ์™€ ์กฐ๊ธˆ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒŒ ์•„๋‹Œ๊ฐ€ ์‹ถ์–ด ๋‹ค๋ฅธ ๊ธ€์— ์ ์–ด๋‘๊ฒ ์Šต๋‹ˆ๋‹ค. ํ˜น์‹œ ๊ด€์‹ฌ์ด ์žˆ์œผ์‹œ๋‹ค๋ฉด ๊ผญ ๋ด์ฃผ์„ธ์š”.

๊ทธ๋ ‡๋‹ค๋ฉด ์ผ๋‹จ!!

console.log ๋Œ€์‹  ์•„๋ž˜์ฒ˜๋Ÿผ ๋ฐ”๊พธ๋ฉด

export default{
  // ...
  data() {
    return {
      // ...
      , currentTab: 'VueRouter'
    }
  },
  computed: {
    componentLoader() {
      const tab = this.currentTab
      return () =>import(`@/components/${tab}`)
    }
  }
}

Dynamic component ๋!!!

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€