Chào mừng đến với Cafedev – nơi chia sẻ kiến thức và kinh nghiệm về công nghệ! Trong bài viết này, chúng ta sẽ cùng nhau khám phá về cơ bản của Vuejs với Components. Vuejs, một framework JavaScript linh hoạt và dễ sử dụng, đã làm mưa làm gió trong cộng đồng phát triển web. Với Components, chúng ta có khả năng chia nhỏ giao diện thành các phần độc lập và có thể tái sử dụng, mang lại sự linh hoạt và dễ quản lý cho dự án của bạn. Hãy cùng bắt đầu khám phá cơ bản của Vuejs và Components tại Cafedev!

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

Điều này rất giống với cách chúng ta lồng các phần tử HTML gốc, nhưng Vue thực hiện mô hình component 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 mỗi component. Vue cũng hoạt động tốt với Web Components gốc. Nếu bạn muốn biết về mối quan hệ giữa Vue Components và Web Components gốc, đọc thêm ở đây.

1. Định nghĩa một Component

Khi sử dụng bước xây dựng, chúng ta thường xác định mỗi component Vue trong một tệp riêng bằng cách sử dụng phần mở rộng .vue – được biết đến như là một Single-File Component (SFC viết tắt):

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

const count = ref(0)
</script>

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

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

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
  // Can also target an in-DOM template:
  // template: '#my-template-element'
}

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

2. Sử dụng một Component

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 về components là như nhau bất kể bạn có sử dụng bước xây dựng hay không. Phần Ví dụ sẽ hiển thị cách sử dụng component trong cả hai tình huống.

Để sử dụng một component con, chúng ta cần import nó vào component cha. Giả sử chúng ta đặt component đếm của mình trong một tệp gọi là ButtonCounter.vue, component sẽ được tiết lộ như là giá trị mặc định của tệp:

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

export default {
  components: {
    ButtonCounter
  }
}
</script>
<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>


Với <script setup>, các thành phần đã được nhập sẽ tự động được đưa vào sẵn cho mẫu.

Để tiết lộ component đã được import vào template của chúng ta, chúng ta cần đăng ký nó với tùy chọn components. Sau đó, component sẽ có sẵn dưới dạng một thẻ bằng cách sử dụng khóa mà nó được đăng ký dưới.

Cũng có thể đăng ký một component một cách toàn cục, khiến nó có sẵn cho tất cả các component trong ứng dụng mà không cần phải import nó. Ưu và nhược điểm của đăng ký toàn cục so với đăng ký cục bộ được thảo luận trong phần Đăng ký Component chuyên biệt.
Components có thể được tái sử dụng nhiều lần tùy thích:

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

Thử nghiệm trong Playground

Lưu ý rằng khi nhấp vào các nút, mỗi nút giữ riêng count của nó. Điều đó xảy ra vì mỗi khi bạn sử dụng một component, một instance mới của nó được tạo ra.

Trong SFCs, được khuyến khích sử dụng tên thẻ PascalCase cho các component con để phân biệt với các phần tử HTML gốc. Mặc dù tên thẻ HTML gốc không phân biệt chữ hoa chữ thường, nhưng Vue SFC 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 đó. Chúng ta cũng có thể sử dụng /> để đóng một thẻ.

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

<!-- if this template is written in the 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.

3. 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 component đại diện cho một bài viết blog. Chúng ta muốn tất cả các bài viết 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 component như vậy sẽ không hữu ích trừ khi bạn có thể truyền dữ liệu cho nó, như tiêu đề và nội dung của bài viết cụ thể mà 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 component. Để truyền một tiêu đề cho component bài viết blog của chúng ta, chúng ta phải khai báo nó trong danh sách các props mà component này chấp nhận, sử dụng props tùy chọn defineProps macro:

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

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

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

Xem thêm: Định kiểu Props của Component

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

Một component có thể có bất kỳ số lượng props nào bạn muố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.
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:

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

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

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 render một component cho mỗi bài viết, sử dụng v-for:

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

Thử nghiệm trong Playground

Lưu ý cách cú pháp v-bind (:title="post.title") được sử dụng để truyền các giá trị prop động. Điều này đặc biệt hữu ích khi bạn không biết nội dung cụ thể bạn sẽ render từ trước.
Đó là tất cả những gì bạn cần biết về props 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 hướng dẫn đầy đủ về Props

4. Lắng nghe Sự kiện

Khi chúng ta phát triển component , một số tính năng có thể yêu cầu gửi thông tin trở lại cho component cha. Ví dụ, chúng ta có thể quyết định bao gồm một tính năng truy cập dễ dàng để làm to văn bản của các bài viết blog, trong khi để lại phần còn lại của trang ở kích thước mặc định của nó.
Trong component cha, chúng ta có thể hỗ trợ tính năng này bằng cách thêm một postFontSize dữ liệu thuộc tínhref:


const posts = ref([
  /* ... */
])

const postFontSize = ref(1)

Điều này có thể được sử dụng trong template để kiểm soát kích thước font chữ của tất cả các bài viết blog:


<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 <BlogPost> vào template của component:


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

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


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

Sau đó, component 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 tích hợp $emit truyền tên của sự kiện:

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

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

Thử nghiệm trong Playground
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 emits tùy chọn defineEmits macro:


<!-- 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 component phát ra và tùy chọn kiểm tra tính hợp lệ. Nó cũng cho phép Vue tránh áp dụng chúng một cách ngụ ý như là người lắng nghe gốc native cho phần tử gốc của component 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 các sự kiện trong phần <script setup> của một thành phần, nơi mà $emit không trực tiếp có sẵn:

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

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

Xem thêm: Định kiểu Sự kiện của Component

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 component 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 nghị quay lại sau để đọc hướng dẫn đầy đủ về Sự kiện Tùy chỉnh.

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

Giống như với các phần tử HTML, thường hữu ích khi có thể truyền nội dung vào một component, như thế này:

<AlertBox>
  Something bad happened.
</AlertBox>

Điều này có thể render ra một cái gì đó như:
Đây là Một Lỗi cho Mục đích Demo Đã xảy ra một điều gì đó tồi tệ.

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

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

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

Như bạn sẽ thấy ở trên, chúng tôi sử dụng như một vị trí đặt nơi chúng ta muốn nội dung được đặt vào – và đó là tất cả. Chúng ta đã hoàn thành!

Thử nghiệm trong Playground
Đó 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 hướng dẫn đầy đủ về Slots.

6. Components Động

Đôi khi, việc chuyển đổi giữa các components một cách động có ích, như trong giao diện tab:

Mở ví dụ trong Playground

Điều trên được làm có thể nhờ vào phần tử của Vue với thuộc tính đặc biệt is:

<!-- Component changes when currentTab changes -->
<component :is="tabs[currentTab]"></component>

Trong ví dụ trên, giá trị được truyền cho :is có thể chứa một trong hai:
– chuỗi tên của một component đã đăng ký, HOẶC
– đối tượng component đã được import thực tế
Bạn cũng có thể sử dụng thuộc tính is để tạo các phần tử HTML thông thường.
Khi chuyển đổi giữa nhiều components với <component :is=”…”>, một component sẽ bị hủy khi nó được chuyển đi. Chúng ta có thể bắt buộc các components không hoạt động để vẫn “sống” với component tích hợp sẵn <KeepAlive> component.

7. Cảnh báo Phân tích Mẫu Trong DOM

Nếu bạn đang viết các 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 gốc của trình duyệt.

tip Cần 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 đang viết các mẫu của mình trực tiếp trong DOM. Chúng KHÔNG áp dụng nếu bạn đang sử dụng các mẫu chuỗi từ các nguồn sau:

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

7.1 Không phân biệt chữ hoa chữ thường

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

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

7.2 Đóng tự động thẻ

Chúng ta đã sử dụng các thẻ tự đóng cho các component trong các mẫu mã trước đó:

<MyComponent />

Điều này là do trình phân tích mẫu của Vue tôn trọng /> như một chỉ báo để kết thúc bất kỳ thẻ nào, bất kể loại của nó.
Tuy nhiên, trong các mẫu mã trong DOM, chúng ta luôn phải bao gồm các thẻ đóng rõ ràng:

<my-component></my-component>

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

<my-component /> <!-- we intend to close the tag here... -->
<span>hello</span>

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

<my-component>
  <span>hello</span>
</my-component> <!-- but the browser will close it here. -->

7.3 Hạn chế về Vị trí Phần tử

Một số phần tử HTML, như <ul>, <ol>, <table> và <select>, có các ràng buộc về những 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ó các hạn chế như vậy. Ví dụ:

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

Component <blog-post-row> tùy chỉnh sẽ được di chuyển ra ngoài như là nội dung không hợp lệ, gây ra lỗi trong kết quả cuối cùng được render. Chúng ta có thể sử dụng thuộc tính đặc biệt is attribute như một phương án tạm thời:

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

tip Khi sử dụng trên các phần tử HTML gốc, giá trị của is phải được tiền tố bằng vue: để được hiểu là một component Vue. Điều này là bắt buộc để tránh nhầm lẫn với các phần tử tùy chỉnh gốc được xây dựng.

Đó là tất cả những gì bạn cần biết về các lưu ý phân tích mẫu trong DOM cho đến lúc này – và thực sự, đó là cuối cùng của Cần Thiết của Vue. Chúc mừng bạn! Vẫn còn nhiều thứ để học, nhưng trước tiên, chúng tôi khuyến nghị bạn nên nghỉ ngơi để tự mình tìm hiểu về Vue – xây dựng một cái 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 mà bạn vừa học, tiếp tục với các hướng dẫn để tìm hiểu thêm về các component chi tiết hơn.

Hãy cảm ơn bạn đã tham gia cùng Cafedev trong hành trình khám phá cơ bản về Vuejs và Components! Chúng ta đã có cơ hội tìm hiểu về sức mạnh của Vuejs trong việc phát triển ứng dụng web, cũng như lợi ích mà Components mang lại trong việc tái sử dụng và quản lý mã nguồn. Với kiến thức này, bạn có thêm công cụ để xây dựng những ứng dụng web đẹp mắt và linh hoạt hơn. Hãy tiếp tục khám phá và áp dụng những điều bạn đã học tại Cafedev vào dự án của mình!

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!