λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ“  archive

[Vue.js] ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ 이λͺ¨μ €λͺ¨

by HandHand 2022. 2. 20.

Vue ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•΄μ„œ

πŸ“Œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλž€?

ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ λŠ” μƒνƒœμ™€ μΈμŠ€ν„΄μŠ€κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§ν•©λ‹ˆλ‹€.

μ»΄ν¬λ„ŒνŠΈκ°€ μƒνƒœκ°€ μ—†κ³  μΈμŠ€ν„΄μŠ€ν™” λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— this μ»¨ν…μŠ€νŠΈ κ°€ μ—†μŠ΅λ‹ˆλ‹€.

μƒνƒœκ°€ μ—†κΈ° λ•Œλ¬Έμ— 초기 λ Œλ”λ§κ³Ό μ—…λ°μ΄νŠΈ μ‹œ μ„±λŠ₯ 이점을 λ³Ό 수 μžˆλ‹€λŠ” νŠΉμ§•μ΄ μžˆμŠ΅λ‹ˆλ‹€.

 

πŸ“Œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©ν•˜κΈ°

이후에 μ‚¬μš©λ˜λŠ” μ˜ˆμ œλŠ” Vue 2.x λ₯Ό κΈ°μ€€μœΌλ‘œ μž‘μ„±λ˜μ—ˆμŒμ— 유의 λ°”λžλ‹ˆλ‹€.

Vue 3 λΆ€ν„°λŠ” functional: true μ†μ„±λŒ€μ‹  일반 ν•¨μˆ˜λ‘œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ₯Ό κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

template 에 functional 속성 μ§€μ •ν•˜κΈ°

SFC(single file component) λ₯Ό μ‚¬μš©ν•˜λŠ” 경우 λ‹€μŒκ³Ό 같이 template μ˜΅μ…˜μ„ μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

<template functional>
  <div>ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ</div>
</template>

<script lang="ts">
import { Component, Vue, } from 'vue-property-decorator'

@Component({
  name: 'Functional'
})
export default class Functional extends Vue {}
</script>

@Component λ°μ½”λ ˆμ΄ν„°μ— functional: true μ˜΅μ…˜μ„ 지정할 수 μ—†λ‚˜ μ°Ύμ•„λ΄€λŠ”λ°..

vue-class-component λ ˆν¬μ— ν•΄λ‹Ή μ΄μŠˆκ°€ λ“±λ‘λ˜μ–΄μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

How to create functional component in @Component? · Issue #120 · vuejs/vue-class-component

개발자 μ˜κ²¬μ— λ”°λ₯΄λ©΄ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλŠ” κ²°κ΅­ μΈμŠ€ν„΄μŠ€ν™”λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— 클래슀둜 κ΅¬ν˜„ν•  ν•„μš”κ°€ μ—†κ³ ,

λ•Œλ¬Έμ— @Component λ°μ½”λ ˆμ΄ν„°λ₯Ό 톡해 클래슀둜 μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“œλŠ” 것은 쒋은 방법이 μ•„λ‹ˆλΌκ³  ν•©λ‹ˆλ‹€.

λŒ€μ‹  λ‹€μŒκ³Ό 같이 Vue.extend λ₯Ό μ‚¬μš©ν•˜λŠ” 방법을 κΆŒκ³ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

Vue.extend 와 render ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄μ„œ κ΅¬ν˜„ν•˜κΈ°

render ν•¨μˆ˜λŠ” context λΌλŠ” 인자λ₯Ό λ°›λŠ”λ°, 이λ₯Ό 톡해 μ»΄ν¬λ„ŒνŠΈμ˜ μ—¬λŸ¬ 속성에 μ ‘κ·Ό κ°€λŠ₯ν•©λ‹ˆλ‹€.

<script lang="ts">
import { Vue, } from 'vue-property-decorator'

export default Vue.extend({
  functional: true,
  render: function(createElement, context) {
    return createElement('div', 'ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ')
  }
})
</script>

context λ₯Ό 톡해 μ ‘κ·Ό κ°€λŠ₯ν•œ μ»΄ν¬λ„ŒνŠΈ 속성듀은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • props: 전달받은 props 객체
  • children: μžμ‹ VNode λ°°μ—΄
  • slots: 슬둯 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜
  • scopedSlots: λ²”μœ„κ°€ μ§€μ •λœ μŠ¬λ‘―μ„ λ Œλ”λ§ν•˜λŠ” ν•¨μˆ˜λ₯Ό 가진 κ°μ²΄μž…λ‹ˆλ‹€.
  • data: μ»΄ν¬λ„ŒνŠΈμ— μ „λ‹¬λœ 데이터 객체
  • parent: λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•œ μ°Έμ‘°
  • listeners: λΆ€λͺ¨μ—κ²Œ λ“±λ‘λœ 이벀트 λ¦¬μŠ€λ„ˆλ₯Ό 가진 객체
  • injections: inject μ˜΅μ…˜μ„ 가진 경우 μ£Όμž…λœ 데이터λ₯Ό 가지고 있음

 

ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμ— 이벀트 λ¦¬μŠ€λ„ˆ ν• λ‹Ήν•˜κΈ°

// λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈ

<template>
  <functional-component @click="onClick" />
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

import FunctionalComponent from '../components/Functional.vue'

@Component({
  name: 'Index',
  components: { FunctionalComponent }
})
export default class Index extends Vue {
  public onClick() {
    console.log('클릭됨')
  }
}
</script>
// μžμ‹ μ»΄ν¬λ„ŒνŠΈ

<script lang="ts">
import { Vue, } from 'vue-property-decorator'

export default Vue.extend({
  functional: true,
  render: function(createElement, context) {
    return createElement('div', {
      on: {
        click: context.listeners.click
      }
    }, 'ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ')
  }
})
</script>

 

πŸ“Œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν–ˆμ„ λ•Œ μž₯점은?

λ Œλ”λ§ 속도가 λΉ λ₯΄λ‹€.

ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλŠ” μƒνƒœκ°€ μ—†κΈ° λ•Œλ¬Έμ— Vue 의 λ°˜μ‘ν˜• μ‹œμŠ€ν…œμ„ μœ„ν•œ μ΄ˆκΈ°ν™” μž‘μ—…μ΄ ν•„μš”μ—†μŠ΅λ‹ˆλ‹€.

λ•Œλ¬Έμ— λ Œλ”λ§ μ†λ„μ—μ„œ 차이가 λ‚˜λŠ”λ°, 이λ₯Ό λ²€μΉ˜λ§ˆν‚Ήν•œ κ²°κ³ΌλŠ” ν•΄μ™Έ ν•œ λΈ”λ‘œκ±°κ°€ μ •λ¦¬ν•΄λ†¨μŠ΅λ‹ˆλ‹€.

https://codesandbox.io/s/vue-template-yterr?fontsize=14

κ²°κ³Όλ₯Ό μ‚΄νŽ΄λ³΄λ©΄ 1000개의 리슀트 λͺ©λ‘μ„ λ Œλ”λ§ ν•  λ•Œ, ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ‘œ κ΅¬ν˜„ν•  경우 40ms κ°€ μ†Œμš”λ˜λ©°

μƒνƒœκ°€ μ‘΄μž¬ν•˜λŠ” 일반적인 μ»΄ν¬λ„ŒνŠΈλ‘œ κ΅¬ν˜„ν•˜λ©΄ 140ms κ°€ μ†Œμš”λœλ‹€κ³  ν•©λ‹ˆλ‹€.

λ•Œλ¬Έμ— μƒνƒœκ°€ ν•„μš”ν•˜μ§€ μ•Šμ€ presentational component λ₯Ό λ Œλ”λ§ ν•  λ•Œ μ‚¬μš©ν•˜λ©΄ μ’‹μŠ΅λ‹ˆλ‹€.

 

πŸ“Œ Vue 의 ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλŠ” μ™„μ „ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

속성 병합이 μ œλŒ€λ‘œ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

Vue μ—μ„œλŠ” λ‹€μŒκ³Ό 같이 μƒμœ„ μ»΄ν¬λ„ŒνŠΈμ—μ„œ μžμ‹ μ»΄ν¬λ„ŒνŠΈμ—κ²Œ 속성을 전달할 수 μžˆμŠ΅λ‹ˆλ‹€.

λŒ€λΆ€λΆ„μ˜ μ†μ„±μ˜ 경우 μžμ‹ μ»΄ν¬λ„ŒνŠΈμ—κ²Œ μ „λ‹¬λœ μ†μ„±κ°’μœΌλ‘œ λŒ€μ²΄λ˜λŠ”λ° class 와 style 의 경우

병합이 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

λ”°λΌμ„œ λ§Œμ•½ λ‹€μŒκ³Ό 같이 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ •μ˜ν•œλ‹€λ©΄ μžμ‹ μ»΄ν¬λ„ŒνŠΈλŠ” fancy awesome λͺ¨λ‘λ₯Ό κ°€μ Έμ•Ό ν•©λ‹ˆλ‹€.

// λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈμ—μ„œ μžμ‹ μ»΄ν¬λ„ŒνŠΈμ˜ class μ†μ„±μœΌλ‘œ fancy 지정

<template>
  <div>
    <functional-component class="fancy" @click="onClick" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

import FunctionalComponent from '../components/Functional.vue'

@Component({
  name: 'Index',
  components: { FunctionalComponent }
})
export default class Index extends Vue {
  public onClick() {
    console.log('클릭됨')
  }
}
</script>

<style lang="scss">
.fancy {
  color: red;
}
</style>
// μžμ‹ μ»΄ν¬λ„ŒνŠΈ(ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ) μ—μ„œ class μ†μ„±μœΌλ‘œ awesome 지정

<script lang="ts">
import { Vue, } from 'vue-property-decorator'

export default Vue.extend({
  functional: true,
  render: function(createElement, context) {
    return createElement('div', {
      on: {
        click: context.listeners.click
      },
      class: {
        awesome: true,
      },
    }, 'ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ')
  }
})
</script>

<style lang="scss">
.awesome {
  font-size: 5rem;
}
</style>

그런데 싀행해보면 μžμ‹ μ»΄ν¬λ„ŒνŠΈλŠ” awesome ν•˜λ‚˜λ§Œ 가지고 μžˆλŠ” 것을 확인할 수 μžˆλŠ”λ°,

μ΄λŠ” λ‹€μŒκ³Ό 같이 staticClass λ₯Ό 직접 바인딩을 ν•΄μ€ŒμœΌλ‘œμ¨ ν•΄κ²° κ°€λŠ₯ν•©λ‹ˆλ‹€.

<script lang="ts">
import { Vue, } from 'vue-property-decorator'

export default Vue.extend({
  functional: true,
  render: function(createElement, context) {
    return createElement('div', {
      on: {
        click: context.listeners.click
      },
      class: {
        awesome: true,
        ...(context.data.staticClass && { [context.data.staticClass]: true })
      },
      attrs: {
        ...context.data.attrs
      }
    }, 'ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ')
  }
})
</script>

How to apply classes to Vue.js Functional Component from parent component?

 

ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ₯Ό μ€‘μ²©λœ μ»΄ν¬λ„ŒνŠΈλ‘œ μ‚¬μš©μ‹œ λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€.

μœ„ λ¬Έμ œμ— μ΄μ–΄μ„œ λ§Œμ•½ μœ„ μ½”λ“œμ—μ„œ λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈμ˜ μŠ€νƒ€μΌμ΄ scoped 라면 λΆ€λͺ¨μ—μ„œ μ§€μ •ν•œ

scoped css κ°€ 적용이 μ•ˆλ˜λŠ” μ΄μŠˆκ°€ μžˆμŠ΅λ‹ˆλ‹€.

ν˜„μž¬ 이 μ΄μŠˆλŠ” 해결이 μ•ˆλœ κ²ƒμœΌλ‘œ λ³΄μ΄λŠ”λ°... Vue 3 μ—μ„œλŠ” 해결이 λ˜μ—ˆλŠ”μ§€ 확인이 ν•„μš”ν•΄λ³΄μž…λ‹ˆλ‹€.

μ‹€μ œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©μ„ κ³ λ €ν•  λ•Œ μŠ€νƒ€μΌμ΄ ν•„μš”ν•˜λ‹€λ©΄ 큰 이슈둜 μž‘μš©λ  것이라 μƒκ°λ©λ‹ˆλ‹€.

Scoped styles inconsistent between functional and stateful components · Issue #1136 · vuejs/vue-loader

Nested functional components break SFC CSS scoping · Issue #1259 · vuejs/vue-loader

 

ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ—μ„œ μžμ‹ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ Œλ”λ§ν•  λ•Œ λ¬Έμ œκ°€ μƒκΉλ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 template 속성을 μ‚¬μš©ν•΄μ„œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ₯Ό κ΅¬μ„±ν•œ λ’€

μžμ‹ μ»΄ν¬λ„ŒνŠΈλ₯Ό import ν•΄μ„œ λ Œλ”λ§ν•˜λ €κ³  ν•˜λ©΄ λ¬Έμ œκ°€ 생긴닀고 ν•©λ‹ˆλ‹€.

<template functional>
  <div>
    <some-children />
  </div>
</template>

<script>
import SomeChildren from "./SomeChildren"

export default {
  components: {
    SomeChildren
  }
}
</script>

이λ₯Ό μš°νšŒν•˜λŠ” λ°©λ²•μœΌλ‘œ λ‹€μŒκ³Ό 같이 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ£Όμž…ν•΄μ£ΌλŠ” 방법을 μ‚¬μš©ν•˜λΌκ³  μ œμ•ˆν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

<template functional>
  <div>
    <component :is="injections.components.SomeChildren"></component>
  </div>
</template>

<script>
import SomeChildren from "./SomeChildren.vue";
export default {
  inject: {
    components: {
      default: {
        SomeChildren
      }
    }
  }
};
</script>

그런데 직접 ν•΄λ³Έ κ²°κ³Ό, κ·Έλƒ₯ render ν•¨μˆ˜λ₯Ό 톡해 ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ₯Ό κ΅¬ν˜„ν•˜λ©΄

λ¬Έμ œμ—†μ΄ μžμ‹ μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§ λ˜λŠ” 것을 ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.

κ·Έλƒ₯ 맘 νŽΈν•˜κ²Œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλŠ” 일반 ν•¨μˆ˜λ₯Ό 톡해 κ΅¬ν˜„ν•˜λŠ” 것이 μ •μ‹  건강에 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€. πŸ˜…

<script lang="ts">
import { Vue, } from 'vue-property-decorator'
import Finance from './Finance.vue'

export default Vue.extend({
  functional: true,
  render: function(createElement, context) {
    return createElement('div', {
      on: {
        click: context.listeners.click
      },
      class: {
        awesome: true,
        ...(context.data.staticClass && { [context.data.staticClass]: true })
      },
      attrs: {
        ...context.data.attrs
      }
    },
    [createElement(Finance)]
    )
  }
})
</script>

Functional single file component with components option. · Issue #7492 · vuejs/vue

 

πŸ“Œ μ°Έκ³  자료

ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ(Functional Components) | Vue.js

Vue.js functional components: what, why, and when?

Render Functions & JSX - Vue.js

Vue.js functional components: What, Why, and When?

λ°˜μ‘ν˜•

πŸ’¬ λŒ“κΈ€