Frontend/Vue.js

[Vue CLI] 컴포넌트 기초 - 정의 및 사용, Props, 이벤트 전달, slot, 동적 컴포넌트

gamzaggang7 2024. 5. 25. 20:09
728x90

컴포넌트란 조합하여 화면을 구성할 수 있는 블록을 의미한다.

컴포넌트를 등록하는 방법은 전역과 지역이 있다. 지역 컴포넌트는 특정 인스턴스에서만 유효한 범위를 갖고, 전역 컴포넌트는 여러 인스턴스에서 공통으로 사용할 수 있다.

 

컴포넌트 정의하기

<template>
  <button @click="count++">당신은 {{ count }} 번 클릭했습니다.</button>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

위 코드는 Vue 3dml <script setup>을 사용한 단일 파일 컴포넌트(SFC)이다. Vue 3에서는 Composition API를 사용하여 상태 및 로직을 구성할 수 있는 새로운 기능인 <script setup>을 제공한다.

 

<script setup>을 사용하면 템플릿과 상태 및 로직이 한 파일에 모두 포함되어 코드가 간결해지고 가독성이 향상된다.

위에서는 상태 관리를 위해 ref를 import하고, 클릭 수를 저장하기 위한 count 상태 변수를 정의하고 있다. 이후 template에서 버튼 클릭 이벤트를 처리하여 count값을 증가시키고 현재 클릭 수를 표시한다.

 

script setup을 사용하지 않고 동일한 기능을 구현하려면 setup 메서드를 사용하여 상태를 정의하고 반환해야 한다.

<template>
  <button @click="count++">당신은 {{ count }} 번 클릭했습니다.</button>
</template>

<script>
import { ref } from "vue";

export default {
  setup() {
    const count = ref(0);
    return { count };
  },
};
</script>
728x90

 

자식 컴포넌트를 부모 컴포넌트로 가져오기 (import)

지역 또는 전역 컴포넌트를 등록하면 등록된 컴포넌트는 하위 컴포넌트(자식 컴포넌트)가 되고, 하위 컴포넌트를 등록한 인스턴스는 상위 컴포넌트(부모 컴포넌트)가 된다. 상위에서는 하위로 props를 전달하고 하위에서는 상위로 이벤트를 전달한다.

 

buttonCounter.vue 파일을 생성해 위 코드를 입력한다. buttonCounter.vue를 사용하기 위해 App.vue 파일에서 import한다.

<template>
  <h1>아래에 자식 컴포넌트가 있습니다.</h1>
  <ButtonCounter />
</template>

<script setup>
import ButtonCounter from './components/buttonCounter.vue'
</script>

<style>
</style>

 

컴포넌트는 원하는 만큼 재사용할 수 있다.

<h1>아래에 자식 컴포넌트가 있습니다.</h1>
  <ButtonCounter />
  <ButtonCounter />
  <ButtonCounter />

각 버튼은 독립적인 count를 유지한다. 컴포넌트를 사용할 때마다 해당 컴포넌트의 새 인스턴스가 생성되기 때문이다.

 

Props 전달하기

props는 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때 사용하는 속성이다. props 속성을 사용하려면 먼저 하위 컴포넌트의 속성에 정의해야 한다.

 

블로그 게시물 제목을 컴포넌트에 전달하려면 defineProps 메크로를 사용해야 한다.

<template>
  <h4>{{ title }}</h4>
</template>

<script setup>
defineProps(['title'])
</script>

 

defineProps는 컴포넌트에 전달된 모든 props를 객체로 반환하므로 필요한 경우 js에서 접근할 수 있다.

<script setup>
const props = defineProps(['title'])
console.log(props.title)
</script>

 

scsript setup을 사용하지 않는 경우 props 옵션을 선언해서 사용해야 하며 props 객체는 setup()에 첫 번째 인자로 전달된다.

<template>
  <h4>{{ title }}</h4>
</template>

<script>
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title);
  }
}
</script>

 

컴포넌트는 원하는 만큼 props를 가질 수 있으며 모든 값을 모두 props에 전달할 수 있다.

props가 등록되면 다음과 같이 데이터를 사용자 정의 속성으로 전달할 수 있다.

App.vue
<template>
  <BlogPost title="Vue와 함께한 나의 여행" />
  <BlogPost title="Vue로 블로깅하기" />
  <BlogPost title="Vue가 재밌는 이유" />
</template>

<script setup>
import BlogPost from './components/BlogPost.vue'
</script>
BlogPost.vue
<template>
  <h4>{{ title }}</h4>
</template>

<script>
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}
</script>

<style>
h4 {
  color: blue;
}
</style>

이는 다음과 같이 게시물 배열을 이용할 수도 있다.

App.vue
<script setup>
import { ref } from 'vue'
import BlogPost from './components/BlogPost.vue'

const posts = ref([
  {id:1, title: 'Vue와 함께한 나의 여행'},
  {id:2, title: 'Vue로 블로깅하기'},
  {id:3, title: 'Vue가 재밌는 이유'}
])
</script>

그런 다음 v-for를 사용하여 각각을 컴포넌트로 렌더링한다.

<template>
  <BlogPost
    v-for = "post in posts"
    :key = "post.id"
    :title = "post.title" />
</template>

 

이벤트 발생과 수신

하위 컴포넌트에서 이벤트를 발생시켜(event emit) 상위 컴포넌트에 신호를 보내 통신한다. 상위 컴포넌트에서 하위 컴포넌트의 특정 이벤트가 발생하기를 기다리고 있다가 하위 컴포넌트에서 특정 이벤트가 발생하면 상위 컴포넌트에서 해당 이벤트를 수신하여 상위 컴포넌트의 메서드를 호출하는 것이다.

 

BlogPost 컴포넌트를 개발할 때 일부 기능은 상위 항목과 다시 통신해야 할 수도 있다. 예를 들어 페이지의 나머지 부분은 기본 크기를 유지하면서 블로그 게시물의 텍스트를 확대하는 접근성 기능을 구현할 수 있다.

부모 컴포넌트에서 postFontSize ref를 추가하여 모든 블로그 게시물의 글꼴 크기를 제어할 수 있다.

App.vue
<template>
  <div :style="{ fontSize: postFontSize + 'em' }">
    <BlogPost v-for="post in posts" :key="post.id" :title="post.title" />
  </div>
</template>

<script setup>
import { ref } from "vue";
import BlogPost from "./components/BlogPost.vue";

const posts = ref([
  { id: 1, title: "Vue와 함께한 나의 여행" },
  { id: 2, title: "Vue로 블로깅하기" },
  { id: 3, title: "Vue가 재밌는 이유" },
]);

const postFontSize = ref(1);
</script>

 

이제 BlogPost 컴포넌트의 템플릿에 버튼을 추가한다.

BlogPost.vue
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>텍스트 확대</button>
  </div>
</template>

이 버튼은 아직 아무 동작도 하지 않는다. 버튼을 클릭하여 모든 게시물의 텍스트를 확대한다고 부모에게 알려야 한다. 이 문제를 해결하기 위해 컴포넌트는 커스텀 이벤트 시스템을 제공한다. 부모 컴포넌트는 네이티브 DOM 이벤트와 마찬가지로 v-on 또는 @를 사용하여 자식 컴포넌트 인스턴스의 모든 이벤트를 수신하도록 선택할 수 있다.

<BlogPost
      v-for="post in posts"
      :key="post.id"
      :title="post.title"
      @enlarge-text="postFontSize += 0.1"
    />

그런 다음 자식 컴포넌트는 빌트인 $emit 메서드를 호출하고 이벤트 이름을 전달하여 자체적으로 이벤트를 생성할 수 있다.

<button @click="$emit('enlarge-text')">텍스트 확대</button>

@enlarge-text 리스너 덕분에 부모 컴포넌트는 이벤트를 수신하고 postFontSize 값을 업데이트한다.

 

slot이 있는 컨텐츠

<slot>은 컨텐츠를 이동하려는 자리 표시자로 사용한다.

App.vue
<template>
  <AlertBox>나쁜 일이 일어났습니다.</AlertBox>
</template>

<script setup>
import AlertBox from "./components/AlertBox.vue";
</script>
AlertBox.vue
<template>
  <div class="alert-box">
    <strong>이것은 데모용 에러입니다.</strong>
    <br>
    <slot />
  </div>
</template>

<script>

</script>

<style scoped>
.alert-box {
  width: 200px;
  color: #666;
  border: 1px solid red;
  border-radius: 4px;
  padding: 20px;
  background-color: #f8f8f8;
}
 
strong {
  color: red;    
}
</style>

 

동적 컴포넌트

다음은 탭 인터페이스와 같이 컴포넌트 간에 동적으로 전환하는 예제이다.

App.vue
<template>
  <div class="demo">
    <button
      v-for="(_, tab) in tabs"
      :key="tab"
      :class="['tab-button', { active: currentTab === tab }]"
      @click="currentTab = tab"
    >
      {{ tab }}
    </button>
    <component :is="tabs[currentTab]" class="tab"></component>
  </div>
</template>

<script setup>
import Home from "./components/HomeComponent.vue";
import Posts from "./components/PostsComponent.vue";
import Archive from "./components/ArchiveComponent.vue";
import { ref } from "vue";

const currentTab = ref("Home");

const tabs = {
  Home,
  Posts,
  Archive,
};
</script>

<style>
.demo {
  font-family: sans-serif;
  border: 1px solid #eee;
  border-radius: 2px;
  padding: 20px 30px;
  margin-top: 1em;
  margin-bottom: 40px;
  user-select: none;
  overflow-x: auto;
}

.tab-button {
  padding: 6px 10px;
  border-top-left-radius: 3px;
  border-top-right-radius: 3px;
  border: 1px solid #ccc;
  cursor: pointer;
  background: #f0f0f0;
  margin-bottom: -1px;
  margin-right: -1px;
}
.tab-button:hover {
  background: #e0e0e0;
}
.tab-button.active {
  background: #e0e0e0;
}
.tab {
  border: 1px solid #ccc;
  padding: 10px;
}
</style>
HomeComponent.vue
<template>
  <div class="tab">
    Home component
  </div>
</template>
PostComponent.vue
<template>
  <div class="tab">
    Posts component
  </div>
</template>
ArchiveComponent.vue
<template>
  <div class="tab">
    Archive component
  </div>
</template>

위의 예에서 :is에 전달된 값은 다음 중 하나를 포함할 수 있다.

  • 등록된 컴포넌트의 이름 문자열
  • 실제 가져온 컴포넌트 객체

<component :is="...">를 사용하여 여러 컴포넌트 간에 전환할 때 다른 컴포넌트로 전환되면 컴포넌트가 마운트 해제된다. 내장된 <keepAlive> 컴포넌트를 사용하여 비활성 컴포넌트를 활성 상태로 유지하도록 강제할 수 있다.

 

 

공부 사이트 => https://ko.vuejs.org/guide/essentials/component-basics.html

 

Vue.js

Vue.js - The Progressive JavaScript Framework

vuejs.org

 

728x90