Development/Vue.js

[Vue.js] Document 따라하기 - Components Basics

도롱뇽도롱 2022. 7. 11. 18:00
반응형

Component를 사용하면

UI를 독립적이고 재사용 가능한 일부분으로 분할하고 각 부분을 개별적으로 다룰 수 있다. 그렇기에 앱이 중첩된 component의 트리로 구성되는 것은 일반적이다.

https://vuejs.org/guide/essentials/component-basics.html

이것은 기본 HTML element를 중첩하는 방법과 매우 유사하지만 Vue는 각 component에 사용자 정의 콘텐츠와 논리를 캡슐화할 수 있는 자체 component 모델을 구현한다. Vue는 기본 웹 component와도 잘 작동합니다. Vue component와 기본 웹 component 간의 관계가 궁금하다면 여기에서 읽어볼 수 있다.

 

Defining a Component

빌드 방식을 사용할 때 일반적으로 싱글 파일 component(줄여서 SFC)라고 하는 .vue 확장자를 사용하여 전용 파일에 각 Vue component를 정의한다.

<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

빌드 방식을 사용하지 않을 때 Vue component는 Vue 관련 옵션을 포함하는 일반 JavaScript 객체로 정의할 수 있다.

export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
}

JavaScript 문자열로 정의한 템플릿은 Vue가 즉석에서 컴파일한다. Element(보통 기본 <template> element)를 가리키는 ID selector를 사용할 수도 있다. Vue는 해당 콘텐츠를 템플릿 소스로 사용한다.

 

위의 예는 싱글 component를 정의하고 이를 .js 파일의 기본 내보내기로 내보낸다. 그러나 명명된 내보내기를 사용하여 동일한 파일에서 여러 component를 내보낼 수 있다.

 

Using a Component

tip. 이 가이드의 나머지 부분에서는 SFC 구문을 사용할 것이다. Component에 대한 개념은 빌드 방식의 사용 여부에 관계없이 동일하다. 예제 섹션에서는 두 시나리오의 component 사용을 보여준다.

 

자식 component를 사용하려면 부모 component에서 가져와야 한다. ButtonCounter.vue라는 파일 안에 카운터 component를 배치했다고 가정하면 component는 파일의 기본 내보내기가 노출된다.

<script>
import ButtonCounter from './ButtonCounter.vue'

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

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

가져온 component를 템플릿에 노출하려면 component 옵션으로 등록해야 한다. 그러면 component는 등록된 키를 사용하여 태그로 사용할 수 있다.

 

또한 component를 전역적으로 등록하여 가져올 필요 없이 지정된 앱의 모든 component에서 사용할 수 있도록 할 수도 있다. 전역 등록과 로컬 등록의 장단점은 component 등록 섹션에서 설명한다.

Using a Component
Using a Component
Using a Component

Component는 원하는 만큼 재사용할 수 있다.

<h1>Here are many child components!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

Using Components
Using Components

버튼을 클릭할 때 각 버튼은 독립적인 count를 유지한다. Component를 사용할 때마다 해당 component의 새 인스턴스가 생성되기 때문이다.

 

SFC에서는 기본 HTML element와 구별하기 위해 자식 component에 PascalCase 태그 이름을 사용하는 것이 좋다. 기본 HTML 태그 이름은 대소문자를 구분하지 않지만 Vue SFC는 컴파일된 형식이므로 대소문자를 구분하는 태그 이름을 사용할 수 있다. 또한 />를 사용하여 태그를 닫을 수 있다.

 

템플릿을 DOM에서 직접 작성하는 경우(예: 기본 <template> element의 콘텐츠로) 템플릿은 브라우저의 기본 HTML 구문 분석 동작을 따른다. 이러한 경우 component에 대해 kebab-case 및 명시적 닫는 태그를 사용해야 한다.

<!-- 템플릿이 DOM에서 작성된 경우 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

자세한 내용은 DOM 템플릿 구문 분석 주의 사항을 참조하자.

 

Passing Props

블로그를 구축하는 경우 블로그 게시물을 나타내는 component가 필요할 수 있다. 우리는 모든 블로그 게시물이 동일한 시각적 레이아웃을 공유하기를 원하지만 콘텐츠는 다르다. 이때 사용할 component는 표시하려는 특정 게시물의 제목 및 콘텐츠와 같은 데이터를 전달할 수 없으면 유용하지 않다. props가 필요한 것은 바로 이때다.

 

props는 component에 등록할 수 있는 사용자 정의 속성이다. 블로그 게시물의 제목을 component에 전달하려면 props 옵션을 사용해야 한다.

<!-- BlogPost.vue -->
<script>
export default {
  props: ['title']
}
</script>

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

값이 prop 속성에 전달되면 해당 component 인스턴스의 속성이 된다. 해당 속성의 값은 다른 component 속성과 마찬가지로 템플릿 내에서 그리고 component의 this 컨텍스트에서 액세스 할 수 있다.

 

Component는 원하는 만큼 props를 가질 수 있으며 기본적으로 모든 값을 모든 prop에 전달할 수 있다.

 

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

<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

Using Props
Using Props
Using Props

그러나 일반적인 앱에서는 상위 component에 아래와 같은 posts 배열이 있을 수 있다.

export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogging with Vue' },
        { id: 3, title: 'Why Vue is so fun' }
      ]
    }
  }
}

그런 다음 v-for를 사용하여 각각에 대한 component를 렌더링하려고 한다.

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

Using Props with array
Using Props with array

동적 props 값을 전달하기 위해 v-bind가 어떻게 사용되는지 주목해보자. 이것은 미리 렌더링할 정확한 콘텐츠를 모를 때 특히 유용하다.

 

지금은 이것이 props에 대해 알아야 할 전부이다. 하지만 이 페이지를 다 읽고 내용에 익숙해지면 나중에 다시 돌아와 Props에 대한 전체 가이드를 읽는 것이 좋다.

 

Listening to Events

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

 

부모 component에서 postFontSize 데이터 속성을 추가하여 이 기능을 지원할 수 있다.

data() {
  return {
    posts: [
      /* ... */
    ],
    postFontSize: 1
  }
}

템플릿에서 모든 블로그 게시물의 글꼴 크기를 제어하는 ​​데 사용할 수 있다.

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

Listening to Events
Listening to Events

이제 <BlogPost> component의 템플릿에 버튼을 추가해 보자.

<!-- BlogPost.vue, <script> 생략 -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

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

<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

그런 다음 자식 component는 빌트인 $emit 메서드를 호출하고 이벤트 이름을 전달하여 자체적으로 이벤트를 발생시킬 수 있다.

<!-- BlogPost.vue, <script> 생략 -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

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

 

emits 옵션을 사용하여 원하는 이벤트를 선택적으로 선언할 수 있다.

<!-- BlogPost.vue -->
<script>
export default {
  props: ['title'],
  emits: ['enlarge-text']
}
</script>

Listening to Events
Listening to Events
Listening to Events

이것은 component가 내보내는 모든 이벤트를 문서화하고 선택적으로 유효성을 검사한다. 또한 Vue에서 자식 component의 루트 element에 대한 네이티브 리스너가 암시적으로 적용되는 것을 방지할 수 있다.

 

지금은 이것이 사용자 정의 component 이벤트에 대해 알아야 할 전부이다. 그러나 이 페이지를 읽고 내용에 익숙해지면 나중에 다시 돌아와 사용자 정의 이벤트에 대한 전체 가이드를 읽는 것이 좋다.

 

Content Distribution with Slots

HTML element와 마찬가지로 다음과 같이 component에 콘텐츠를 전달할 수 있으면 종종 유용하다.

<AlertBox>
  Something bad happened.
</AlertBox>

다음과 같이 렌더링할 수 있다.

Content Distribution with Slots

이것은 Vue의 사용자 정의 <slot> element를 사용하여 달성할 수 있다.

<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

위에서 볼 수 있듯이 <slot>을 콘텐츠를 이동하려는 자리 표시자(placeholder)로 사용한다.

Content Distribution with Slots
Content Distribution with Slots

지금은 이것이 슬롯에 대해 알아야 할 전부다. 그러나 이 페이지를 다 읽고 내용에 익숙해지면 나중에 다시 돌아와 Slot에 대한 전체 가이드를 읽는 것이 좋다.

 

Dynamic Components

때로는 탭 인터페이스와 같이 component 간에 동적으로 전환하는 것이 유용할 수 있다.

Dynamic Components
Dynamic Components
Dynamic Components
Dynamic Components
Dynamic Components

위의 예제는 Vue의 <component> element에 특별한 is 속성이 있기에 가능하다:

<!-- currentTab이 변경되면 Component가 변경된다 -->
<component :is="currentTab"></component>

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

  • 등록된 component의 이름 문자열
  • 실제 가져온 component개체

is 속성을 사용하여 일반 HTML element를 만들 수도 있다.

 

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

 

DOM Template Parsing Caveats

Vue 템플릿을 DOM에서 직접 작성하는 경우 Vue는 DOM에서 템플릿 문자열을 검색해야 한다. 이는 브라우저의 기본 HTML 파싱 동작으로 인해 몇 가지 주의 사항으로 이어진다.

 

tip. 아래에 설명된 제한 사항은 템플릿을 DOM에서 직접 작성하는 경우에만 적용된다는 점에 유의해야 한다. 다음 소스의 문자열 템플릿을 사용하는 경우에는 적용되지 않는다.

  • 싱글 파일 component(SFC)
  • 인라인 템플릿 문자열(예: "template: '...'")
  • <script type="text/x-template">

 

Case Insensitivity

HTML 태그와 속성의 이름은 대소문자를 구분하지 않으므로 브라우저는 대문자를 소문자로 해석한다. 즉, DOM 내 템플릿을 사용할 때 PascalCase component 이름과 camelCased props의 이름 또는 v-on 이벤트 이름은 모두 kebab-cased(하이픈으로 구분)로 사용해야 한다.

// JavaScript에서 camelCase
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
<!-- HTML에서 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

 

Self Closing Tags

이전 코드 샘플에서 component에 자동 닫기 태그를 사용했다.

<MyComponent />

Vue의 템플릿 파서는 유형에 관계없이 모든 태그를 닫는 표시로 />를 허용하기 때문이다.

 

그러나 DOM 템플릿에서는 항상 명시적인 닫는 태그를 포함해야 한다.

<my-component></my-component>

이는 HTML 사양이 몇 가지 특정 element닫는 태그를 생략할 수 있도록  허용하기 때문이다. 가장 일반적인 것은 <input> 및 <img>이다. 다른 모든 element의 경우 닫는 태그를 생략하면 기본 HTML 파서는 사용자가 여는 태그를 종료하지 않은 것으로 간주한다.

예를 들어:

<my-component /> <!-- 우리는 여기서 태그를 닫으려 한 것이지만... -->
<span>hello</span>

다음과 같이 파싱된다.

<my-component>
  <span>hello</span>
</my-component> <!-- 브라우저는 여기에서 닫을 것이다. -->

 

Element Placement Restrictions

<ul>, <ol>, <table> 및 <select>와 같은 일부 HTML element에는 내부에 표시할 수 있는 element에 대한 제한이 있으며 <li>, <tr> 및 <option>과 같은 일부 element는 특정 다른 element 내부에만 사용할 수 있다.

 

이러한 제한이 있는 element가 있는 component를 사용할 때 문제가 발생한다.

예를 들어:

<table>
  <blog-post-row></blog-post-row>
</table>

사용자 정의 component <blog-post-row>는 잘못된 콘텐츠로 호이스트(hoisted)되어 최종적으로 렌더링 된 출력에서 ​​오류가 발생한다. 특별한 is 속성을 해결 방법으로 사용할 수 있다.

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

 

tip. 기본 HTML element에 사용될 때 is 값은 Vue component로 해석되기 위해서 vue: 접두사를 사용해야 한다. 이것은 맞춤형 내장 element와의 혼동을 피하기 위해 필요하다.

 

이것이 현재로서는 DOM 템플릿 파싱 경고에 대해 알아야 할 전부이며 공식 document의 Essentials 섹션이 끝났다.

 

방금 익힌 지식에 익숙해지면 다음 가이드로 이동하여 component에 대해 자세히 알아보자.

 

 

관련 Repo

https://github.com/galaxyuliana/VueExercises/tree/master/first-vue-project/src/components/Exercise/12

 

참고 자료

https://vuejs.org/guide/essentials/component-basics.html

반응형