Skip to content

Components Basics

Các thành phần cho phép chúng ta chia giao diện người dùng thành các phần độc lập và có thể tái sử dụng, và nghĩ về mỗi phần một cách độc lập. Thông thường, ứng dụng được tổ chức thành một cây các thành phần lồng nhau:

Cây Thành Phần

Điều này rất giống với cách chúng ta lồng các phần tử HTML tự nhiên, nhưng Vue thực hiện mô hình thành phần riêng của mình, cho phép chúng ta đóng gói nội dung và logic tùy chỉnh trong từng thành phần. Vue cũng tương thích tốt với Web Components tự nhiên. Nếu bạn tò mò về mối quan hệ giữa Thành phần Vue và Web Components tự nhiên, đọc thêm ở đây.

Định Nghĩa Một Thành Phần

Khi sử dụng bước xây dựng, chúng ta thường định nghĩa mỗi thành phần Vue trong một tệp riêng biệt với phần mở rộng .vue - được biết đến là Thành Phần Một Tệp (viết tắt là SFC):

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

<template>
  <button @click="count++">Bạn đã click {{ count }} lần.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">Bạn đã click {{ count }} lần.</button>
</template>

Khi không sử dụng bước xây dựng, một thành phần Vue có thể được định nghĩa như một đối tượng JavaScript thuần chứa các tùy chọn cụ thể của Vue:

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Bạn đã click {{ count }} lần.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      Bạn đã click {{ count }} lần.
    </button>`
  // Cũng có thể nhắm đến một mẫu trong DOM:
  // template: '#my-template-element'
}

Mẫu được nhúng như một chuỗi JavaScript ở đây, mà Vue sẽ biên dịch ngay khi chạy. Bạn cũng có thể sử dụng một ID selector chỉ đến một phần tử (thông thường là các phần tử <template> tự nhiên) - Vue sẽ sử dụng nó làm nguồn mẫu.

Ví dụ trên định nghĩa một thành phần duy nhất và xuất nó như là xuất mặc định của một tệp .js, nhưng bạn cũng có thể sử dụng xuất tên để xuất nhiều thành phần từ cùng một tệp.

Sử Dụng Một Thành Phần

TIP

Chúng ta sẽ sử dụng cú pháp SFC cho phần còn lại của hướng dẫn này - các khái niệm xung quanh thành phần là giống nhau bất kỳ bạn có sử dụng bước xây dựng hay không. Phần Ví Dụ thể hiện cách sử dụng thành phần trong cả hai tình huống.

Để sử dụng một thành phần con, chúng ta cần nhập nó trong thành phần cha. Giả sử chúng ta đã đặt thành phần đếm của mình trong một tệp có tên là ButtonCounter.vue, thành phần sẽ được hiển thị như là xuất mặc định của tệp:

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

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

<template>
  <h1>Đây là một thành phần con!</h1>
  <ButtonCounter />
</template>

Để hiển thị thành phần nhập vào trong mẫu của chúng ta, chúng ta cần đăng ký nó với tùy chọn components. Thành phần sau đó sẽ được sử dụng như một thẻ với khóa nó đã được đăng ký.

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

<template>
  <h1>Đây là một thành phần con!</h1>
  <ButtonCounter />
</template>

Với <script setup>, các thành phần được nhập tự động làm cho chúng sẵn có trong mẫu.

Cũng có thể đăng ký một thành phần toàn cục, khiến nó sẵn có cho tất cả các thành phần trong ứng dụng cụ thể mà không cần phải nhập nó. Ưu và nhược điểm của việc đăng ký toàn cục so với đăng ký cục bộ được thảo luận trong phần Đăng Ký Thành Phần ch dedicated Component Registration section.

Các thành phần có thể được sử dụng lại nhiều lần:

template
<h1>Đây là nhiều thành phần con!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

Lưu ý rằng khi nhấp vào các nút, mỗi nút giữ riêng biệt count. Điều đó là bởi vì mỗi khi bạn sử dụng một thành phần, một thể hiện mới của nó được tạo.

Trong SFCs, nên sử dụng tên thẻ PascalCase cho các thành phần con để phân biệt chúng với các phần tử HTML tự nhiên. Mặc dù tên thẻ của phần tử HTML tự nhiên không phân biệt chữ hoa và chữ thường, nhưng SFC của Vue là một định dạng được biên dịch nên chúng ta có thể sử dụng tên thẻ phân biệt chữ hoa chữ thường trong nó. Chúng ta cũng có thể sử dụng /> để đóng m

ột thẻ.

Nếu bạn đang viết các mẫu trực tiếp trong DOM (ví dụ: nội dung của một phần tử <template> tự nhiên), mẫu sẽ phụ thuộc vào hành vi phân tích HTML tự nhiên của trình duyệt. Trong trường hợp này, bạn cần sử dụng kebab-case và thẻ đóng rõ ràng cho các thành phần:

template
<!-- nếu mẫu này được viết trong DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

Xem Cảnh Báo Phân Tích Mẫu trong DOM để biết thêm chi tiết.

Truyền Props

Nếu chúng ta đang xây dựng một blog, chúng ta có thể cần một thành phần đại diện cho một bài đăng blog. Chúng ta muốn tất cả các bài đăng blog chia sẻ cùng một bố cục trực quan, nhưng với nội dung khác nhau. Một thành phần như vậy sẽ không hữu ích cho đến khi bạn có thể truyền dữ liệu cho nó, chẳng hạn như tiêu đề và nội dung của bài đăng cụ thể chúng ta muốn hiển thị. Đó là nơi props đến.

Props là các thuộc tính tùy chỉnh bạn có thể đăng ký trên một thành phần. Để truyền tiêu đề cho thành phần bài đăng blog của chúng ta, chúng ta phải khai báo nó trong danh sách các props mà thành phần này chấp nhận, sử dụng tùy chọn propsmacro defineProps:

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

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

Khi giá trị được truyền vào một thuộc tính prop, nó trở thành một thuộc tính trên thể hiện của thành phần đó. Giá trị của thuộc tính đó có thể truy cập trong mẫu và trong ngữ cảnh this của thành phần, giống như bất kỳ thuộc tính thành phần nào khác.

vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

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

defineProps là một macro biên dịch chỉ tồn tại trong <script setup> và không cần được nhập một cách rõ ràng. Props được khai báo tự động được hiển thị trong mẫu. defineProps cũng trả về một đối tượng chứa tất cả các props được truyền vào thành phần, để chúng ta có thể truy cập chúng trong JavaScript nếu cần:

js
const props = defineProps(['title'])
console.log(props.title)

Xem thêm: Định Dạng Props Thành Phần

Nếu bạn không sử dụng <script setup>, props nên được khai báo bằng cách sử dụng tùy chọn props, và đối tượng props sẽ được chuyển đến setup() như là đối số đầu tiên:

js
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

Một thành phần có thể có bao nhiêu props tùy bạn và, theo mặc định, bất kỳ giá trị nào cũng có thể được truyền cho bất kỳ prop nào.

Một khi một prop được đăng ký, bạn có thể truyền dữ liệu cho nó như một thuộc tính tùy chỉnh, như sau:

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

Tuy nhiên, trong ứng dụng điển hình, bạn có thể có một mảng các bài viết trong thành phần cha của bạn:

js
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' }
      ]
    }
  }
}
js
const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

Sau đó, muốn hiển thị một thành phần cho mỗi bài viết, sử dụng v-for:

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

Chú ý cách v-bind được sử dụng để truyền giá trị prop động. Điều này đặc biệt hữu ích khi bạn không biết nội dung chính xác mà bạn sẽ hiển thị từ trước.

Đó là tất cả những gì bạn cần biết về props trong lúc này, nhưng sau khi bạn đã đọc xong trang này và cảm thấy thoải mái với nội dung của nó, chúng tôi khuyến khích quay lại sau để đọc toàn bộ hướng dẫn về Props.

Lắng Nghe Sự Kiện

Khi chúng ta phát triển thành phần <BlogPost>, một số tính năng có thể yêu cầu giao tiếp lên đến thành phần cha. Ví dụ, chúng ta có thể quyết định bao gồm một tính năng truy cập để làm to văn bản của các bài đăng blog, trong khi giữ nguyên kích thước mặc định của trang.

Trong thành phần cha, chúng ta có thể hỗ trợ tính năng này bằng cách thêm một thuộc tính dữ liệu dataref postFontSize:

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

const postFontSize = ref(1)

Nó có thể được sử dụng trong mẫu để kiểm soát kích thước font chữ của tất cả các bài đăng blog:

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

Bây giờ hãy thêm một nút vào mẫu của thành phần <BlogPost>:

vue
<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Phóng to văn bản</button>
  </div>
</template>

Hiện tại, nút không thực hiện bất kỳ hành động nào - chúng ta muốn khi nhấp vào nút, nó sẽ thông báo cho thành phần cha rằng nó nên làm to văn bản của tất cả các bài đăng. Để giải quyết vấn đề này, các thành phần cung cấp một hệ thống sự kiện tùy chỉnh. Cha có thể chọn nghe bất kỳ sự kiện nào trên thể hiện của thành phần con với v-on hoặc @, giống như chúng ta làm với một sự kiện DOM cơ bản:

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

Sau đó, thành phần con có thể phát ra một sự kiện trên chính nó bằng cách gọi phương thức $emit tích hợp, truyền tên của sự kiện:

vue
<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Phóng to văn bản</button>
  </div>
</template>

Nhờ vào trình nghe @enlarge-text="postFontSize += 0.1", cha sẽ nhận được sự kiện và cập nhật giá trị của postFontSize.

[Try it in the Playground](https://play.vuejs.org/#eNqNUsFOg0AQ/ZUJMaGNbbHqidCmmujNxMRED9IDhYWuhV0CQy0S/t1ZYIEmaiRkw8y8N/vmMZVxl6aLY8EM23ByP+Mprl3Bk1RmCPexjJ5ljhBmMgFzYemEIpiuAHAFOzXQgIVeESNUKutL4gsmMLfbBPStVFTP1Bl46E2mup4xLDKhI4CUsMR+1zFABTywYTkD5BgzG8ynEj4kkVgJnxz38Eqaut5jxvXAUC

IiLqI/8TcD/m1fKhTwHHIJYSEIr+HbnqikPkqBL/yLSMs23eDooNexel8pQJaksYeMIgAn4EewcyxjtnKNCsK+zbgpXILJEnW30bCIN7ZTPcd5KDNqoWjARWufa+iyfWBlV13wYJRvJtWVJhiKGyZiL4vYHNkJO8wgaQVXi6UGr51+Ndq5LBqMvhyrH9eYGePtOVu3n3YozWSqFsBsVJmt3SzhzVaYY2nm9l82+7GX5zTGjlTM1SyNmy5SeX+7rqr2r0NdOxbFXWVXIEoBGz/m/oHIF0rB5Pz6KTV6aBOgEo7Vsn51ov4GgAAf2A==)

Chúng ta có thể tùy chọn khai báo các sự kiện được phát ra bằng cách sử dụng tùy chọn emitsmacro defineEmits:

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

Điều này tài liệu hóa tất cả các sự kiện mà một thành phần phát ra và tùy chọn kiểm tra chúng. Nó cũng cho phép Vue tránh áp dụng chúng một cách ngầm định như người nghe cơ sở vào phần tử gốc của thành phần con.

Tương tự như defineProps, defineEmits chỉ có thể sử dụng trong <script setup> và không cần phải được nhập. Nó trả về một hàm emit tương đương với phương thức $emit. Nó có thể được sử dụng để phát ra sự kiện trong phần <script setup> của một thành phần, nơi $emit không trực tiếp truy cập được:

vue
<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>

Xem thêm: Đánh Dấu Loại Sự Kiện Của Thành Phần

Nếu bạn không sử dụng <script setup>, bạn có thể khai báo các sự kiện được phát ra bằng cách sử dụng tùy chọn emits. Bạn có thể truy cập hàm emit như một thuộc tính của ngữ cảnh thiết lập (được truyền vào setup() như là đối số thứ hai):

js
export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

Đó là tất cả những gì bạn cần biết về sự kiện tùy chỉnh của thành phần cho đến lúc này. Tuy nhiên, sau khi bạn đã đọc xong trang này và cảm thấy thoải mái với nội dung của nó, chúng tôi khuyến nghị quay lại sau để đọc toàn bộ hướng dẫn về Sự Kiện Tùy Chỉnh.

Phân phối Nội dung với Slots

Tương tự như các phần tử HTML, có lẽ sẽ hữu ích khi có khả năng truyền nội dung vào một thành phần, như sau:

template
<AlertBox>
  Đã xảy ra điều gì đó tồi tệ.
</AlertBox>

Có thể sẽ xuất hiện một cái gì đó như sau:

Đây là một Lỗi để Minh họa

Đã xảy ra điều gì đó tồi tệ.

Điều này có thể được thực hiện bằng cách sử dụng phần tử <slot> tùy chỉnh của Vue:

vue
<template>
  <div class="alert-box">
    <strong>Đây là một Lỗi để Minh họa</strong>
    <slot />
  </div>
</template>

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

Như bạn thấy ở trên, chúng tôi sử dụng <slot> như một nơi giữ chỗ nơi chúng ta muốn nội dung đi vào - và đó là tất cả. Chúng ta đã hoàn thành!

Đó là tất cả những gì bạn cần biết về slots cho đến lúc này, nhưng sau khi bạn đã đọc xong trang này và cảm thấy thoải mái với nội dung của nó, chúng tôi khuyến nghị quay lại sau để đọc toàn bộ hướng dẫn về Slots.

Các Thành phần Linh hoạt

Đôi khi, việc chuyển động động giữa các thành phần là hữu ích, như trong một giao diện theo tab:

Việc trên được thực hiện thông qua phần tử <component> của Vue với thuộc tính is đặc biệt:

template
<!-- Thay đ

ổi Component khi currentTab thay đổi -->
<component :is="currentTab"></component>
template
<!-- Thay đổi Component khi currentTab thay đổi -->
<component :is="tabs[currentTab]"></component>

Trong ví dụ trên, giá trị được truyền vào :is có thể chứa:

  • Chuỗi tên của một thành phần đã được đăng ký, HOẶC
  • Đối tượng thành phần đã được nhập thực tế

Bạn cũng có thể sử dụng thuộc tính is để tạo ra các phần tử HTML thông thường.

Khi chuyển đổi giữa nhiều thành phần với <component :is="...">, một thành phần sẽ bị hủy khi nó được chuyển đi. Chúng ta có thể buộc các thành phần không hoạt động được giữ lại "sống" với thành phần tích hợp sẵn <KeepAlive>.

Nhược điểm phân tích Mẫu trực tiếp trong DOM

Nếu bạn viết mẫu Vue trực tiếp trong DOM, Vue sẽ phải lấy chuỗi mẫu từ DOM. Điều này dẫn đến một số lưu ý do hành vi phân tích HTML tự nhiên của trình duyệt.

TIP

Lưu ý rằng các hạn chế được thảo luận dưới đây chỉ áp dụng nếu bạn viết mẫu của bạn trực tiếp trong DOM. Chúng KHÔNG áp dụng nếu bạn sử dụng các mẫu chuỗi từ các nguồn sau:

  • Các thành phần Single-File
  • Chuỗi mẫu được nhúng (ví dụ: template: '...')
  • <script type="text/x-template">

Không phân biệt chữ hoa

Các thẻ HTML và tên thuộc tính không phân biệt chữ hoa, vì vậy trình duyệt sẽ diễn giải bất kỳ ký tự viết hoa nào như chữ thường. Điều này có nghĩa là khi bạn sử dụng các mẫu trong DOM, tên thành phần PascalCase và tên prop hoặc tên sự kiện v-on được viết theo kiểu camelCase đều cần sử dụng các đối tượng tương đương được ngăn cách bằng gạch nối (hyphen):

js
// camelCase trong JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
template
<!-- kiểu gạch nối trong HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

Thẻ tự đóng

Chúng ta đã sử dụng thẻ tự đóng cho các thành phần trong các đoạn mã trước đây:

template
<MyComponent />

Điều này là do bộ phân tích mẫu của Vue coi /> là một dấu hiệu để kết thúc bất kỳ thẻ nào, bất kể loại của nó.

Tuy nhiên, trong mẫu trực tiếp trong DOM, chúng ta luôn phải bao gồm các thẻ đóng cụ thể:

template
<my-component></my-component>

Điều này là do HTML chỉ cho phép một số phần tử cụ thể được loại bỏ thẻ đóng, phổ biến nhất là <input><img>. Đối với tất cả các phần tử khác, nếu bạn bỏ qua thẻ đóng, bộ phân tích HTML tự nhiên sẽ nghĩ rằng bạn chưa bao giờ kết thúc thẻ mở. Ví dụ, đoạn mã dưới đây:

template
<my-component /> <!-- chúng ta định kết thúc thẻ ở đây... -->
<span>hello</span>

sẽ được phân tích thành:

template
<my-component>
  <span>hello</span>
</my-component> <!-- nhưng trình duyệt sẽ đóng nó ở đây. -->

Hạn chế vị trí của Phần tử

Một số phần tử HTML, như <ul>, <ol>, <table><select> có các hạn chế về phần tử nào có thể xuất hiện bên trong chúng, và một số phần tử như <li>, <tr>, và <option> chỉ có thể xuất hiện bên trong một số phần tử khác.

Điều này sẽ gây ra vấn đề khi sử dụng các thành phần với các phần tử có những hạn chế đó. Ví dụ:

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

Thành phần tùy chỉnh <blog-post-row> sẽ bị kéo ra ngoài như là nội dung không hợp lệ, gây ra lỗi trong đầu ra cuối cùng được hiển thị. Chúng ta có thể sử dụng thuộc tính is đặc biệt làm biện pháp tạm thời:

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

TIP

Khi sử dụng trên các phần tử HTML nguyên thuỷ, giá trị của is phải được tiếp đầu với vue: để được hiểu là một thành phần Vue. Điều này là cần thiết để tránh sự nhầm lẫn với [phần tử tích hợp sẵn tùy chỉnh](https://

html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example).

Đó là tất cả những gì bạn cần biết về các lưu ý khi phân tích mẫu trực tiếp trong DOM - và thực sự, đó là cuối cùng của Essentials của Vue. Chúc mừng bạn! Vẫn còn nhiều điều để học, nhưng trước tiên, chúng tôi khuyên bạn nên nghỉ ngơi và chơi với Vue bản thân bạn - xây dựng điều gì đó vui vẻ, hoặc kiểm tra một số Ví dụ nếu bạn chưa làm điều đó.

Khi bạn cảm thấy thoải mái với kiến thức bạn vừa tiêu thụ, hãy tiếp tục với hướng dẫn để tìm hiểu thêm về thành phần chi tiết.

Components Basics has loaded