프로그래밍/JavaScript

VueJS 학습 - Component

seungdols 2022. 10. 13. 23:20

component

nested component

<template>
    <h2>Page Title</h2>
</template>
<template>
    <div>
        <PageTitle />
    </div>
</template>
<script>
    import PageTitle from '../components/PageTitle'
    export default {
        components: [PageTitle]
    }
</script>

props

<template>
    <h2>{{title}}</h2>
</template>
<script>
    export default {
        props: {
            title: {
                type: String,
                default: "페이지 제목입니다."
            }
        }
    }
</script>

<template>
    <div>
        <PageTitle title="Nested Component" />
    </div>
</template>
<script>
import PageTitle from '../components/PageTitle'
export default {
    components: { PageTitle }
}
</script>

dynamic props

v-bind를 이용하여 props 전달이 가능하다.

<page-title :title="title"

단방향 데이터 흐름

모든 props는 자식 속성과 부모 속성 사이에 아래로 단방향 바인딩(one-way-down binding)을 형성합니다. 부모 속성이 업데이트되면 자식으로 흐르지만 반대 방향은 아닙니다. 이렇게하면 하위 컴포넌트가 실수로 앱의 데이터 흐름을 이해하기 힘들게 만드는 상위 컴포넌트 상태 변경을 방지할 수 있습니다.

또한, 부모 컴포넌트가 업데이트될 때마다 자식 컴포넌트의 모든 prop들이 최신 값으로 새로고침됩니다. 즉, 하위 컴포넌트에서 prop를 변경하려고 시도해서는 안됩니다. 그렇게하면 Vue는 콘솔에서 경고합니다.

일반적으로 prop를 변경하려는 2가지 경우가 있습니다

  1. prop는 초기 값을 전달하는데 사용됩니다. 하위 컴포넌트는 나중에 prop값을 로컬 data속성으로 사용하려고 합니다. 이 경우 prop를 초기 값으로 사용하는 로컬 data 속성을 정의하는 것이 가장 좋습니다.
    props: ['initialCounter'],
    data() {
    return {
    counter: this.initialCounter
    }
    }
  2. prop는 변환해야 하는 원시 값으로 전달됩니다. 이 경우 prop의 값을 사용하여 computed 속성을 정의하는 것이 가장 좋습니다.
    props: ['size'],
    computed: {
    normalizedSize: function () {
     return this.size.trim().toLowerCase()
    }
    }

Prop 대소문자 구분 (camelCase vs kebab-case)

HTML 속성명은 대소문자를 구분하지 않으므로, 브라우저는 모든 대문자를 소문자로 해석합니다. 즉, DOM내 템플릿을 사용할 때 camelCase된 prop명은 kebab-case(하이픈으로 구분)된 해당 항목을 사용해야 합니다.

const app = Vue.createApp({})

app.component('blog-post', {
  // JavaScript에서의 camelCase
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- HTML에서의 kebab-case -->
<blog-post post-title="hello!"></blog-post>

부모 컴포넌트에서 자식 컴포넌트 이벤트 직접 발생시키기

this.$refs로 ref로 설정 된 id를 통해서 해당 컴포넌트에 접근이 가능하다.

<template>
    <button type="button" @click="childFunc" ref="btn">Click</button>
</template>
<script>
    export default {
        methods: {
            childFunc() {
                console.log("부모 컴포넌트에서 직접 발생시킨 이벤트")
            }
        }
    }
</script>
<template>
   <child-component @send-message="sendmessage" ref="child_component" />
</template>

<script>
    import ChildComponent from './ChildComponent'
    export default {
        components: { ChildComponent },
        mounted() {
            this.$refs.child_component.$refs.btn.click()
        }
    }
</script>

자식 컴포넌트에서 부모 컴포넌트로 이벤트/데이터 전달

<template>
    <button type="button" @click="childFunc" ref="btn">Click</button>
</template>
<script>
    export default {
        data() {
            return {
                message: 'Send Parent'
            }
        },
        methods: {
            childFunc() {
                console.log("부모 컴포넌트에서 직접 발생시킨 이벤트")
            },
            callFromParent() {
                console.log("부모 컴포넌트에서 직접 호출")
            }
        },
        mounted() {
            this.$emit('send-message', this.message)
        }
    }
</script>
<template>
   <child-component @send-message="sendMessage" ref="child_component" />
</template>

<script>
    import ChildComponent from './ChildComponent'
    export default {
        components: { ChildComponent },
        mounted() {
            this.$refs.child_component.$refs.btn.click()
            this.$refs.child_component.callFromParent()
        },
        methods: {
            sendMessage(data) {
                console.log(data)
            }
        }
    }
</script>

자식 컴포넌트에서 부모 컴포넌트로 이벤트 전달하기 위해서 $emit을 사용하면 된다.

slot

유사한 모습의 컴포넌트를 재활용할 수 있는 방법으로 HTML의 형식을 고정해두고, 해당 Slot을 여러 컴포넌트에서 재사용할 수 있다.

<template>
    <h2><slot></slot></h2>
</template>
<script>
    export default {
        props: {
            title: {
                type: String,
                default: "페이지 제목입니다."
            }
        }
    }
</script>
<template>
    <div>
        <PageTitle>{{title}}</PageTitle>
    </div>
</template>
<script>
import PageTitle from '../components/PageTitle'
export default {
    data() {
        return {
            title: "Nested Component!!"
        }
    },
    components: { PageTitle }
}
</script>

named slot

<div class="container">
  <header>
    <slot name="header">
  </header>
  <main>
    <slot>
  </main>
  <footer>
    <slot name="footer">
  </footer>
</div>
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

name 을 갖지 않는 <slot>은 묵시적으로 "default"라는 name을 갖습니다.

이름을 가진 slot에 컨텐츠를 제공하기 위해서는 <template> 엘리먼트에 v-slot 지시자를 사용합니다.
v-slot의 매개변수로는 각 slot의 이름을 전달합니다.

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

v-slot 대신에 축약으로 #도 사용 가능하다.

Provide/Inject

부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달 해야 할때, depth가 깊어지는 경우 props를 불필요하게 전달 해야 하는데, 이 때 Provde / Inject를 통해 원하는 자식 컴포넌트에게 데이터를 전달 할 수 있다.

<template>
  <ProvideInjectChild />
</template>
<script>
    import ProvideInjectChild from './ProvideInjectChild'
    export default {
        components: {
            ProvideInjectChild
        },
        data() {
            return {
                items: [
                    'A',
                    'B'
                ]
            }
        },
        provide() {
            return {
                item: this.items
            }
        }
    }
</script>
<script>
export default {
    inject: ['item'],
    mounted() {
        console.log(this.item)
        console.log(this.item[0])
    }
}
</script>
반응형