For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/frontend/vue2.7/component-in-depth/handling-edge-cases.md.

Handling edge cases, recursive components, and inline templates in Vue 2.7.

Handling Edge Cases

Các kỹ thuật ít dùng nhưng cần biết khi gặp tình huống đặc biệt. Phần lớn nên hạn chế dùng vì phá vỡ luồng dữ liệu chuẩn (props xuống, events lên).

Truy cập instance khác

refs

ref lấy tham chiếu trực tiếp tới DOM element hoặc child component instance. Chỉ truy cập sau khi đã mount, dùng cho thao tác bắt buộc (focus input, gọi method của thư viện).

<template>
  <input ref="search">
  <child-form ref="form"></child-form>
</template>

<script>
export default {
  mounted() {
    this.$refs.search.focus()
    this.$refs.form.reset() // gọi method của child
  }
}
</script>

$refs không reactive, không dùng trong template hay computed.

$parent / $root

$parent truy cập instance cha, $root truy cập root. Dễ tạo coupling khó bảo trì, ưu tiên props/events thay thế. Tránh dùng trừ khi thật cần.

Dependency injection: provide / inject

Truyền dữ liệu xuống component con sâu nhiều cấp mà không phải truyền props qua từng tầng.

// component tổ tiên
export default {
  provide() {
    return { theme: 'dark' }
  }
}
// component con bất kỳ phía dưới
export default {
  inject: ['theme'],
  mounted() {
    console.log(this.theme) // 'dark'
  }
}

Lưu ý: inject mặc định không reactive. Dùng cho config tĩnh (theme, locale), không dùng cho state thay đổi liên tục.

Programmatic event listeners

Tự quản lý listener trên instance bằng $on, $once, $off. Hữu ích khi đăng ký listener cho thư viện third-party và cần cleanup.

mounted() {
  this.picker = new Pikaday({ /* ... */ })
  this.$once('hook:beforeDestroy', () => {
    this.picker.destroy()
  })
}

Recursive component

Component có thể tự gọi chính nó qua name, dùng render cấu trúc cây (menu, comment lồng nhau). Phải có điều kiện dừng để tránh vòng lặp vô hạn.

<!-- TreeItem.vue -->
<template>
  <li>
    {{ item.label }}
    <ul v-if="item.children && item.children.length">
      <tree-item
        v-for="child in item.children"
        :key="child.id"
        :item="child" />
    </ul>
  </li>
</template>

<script>
export default {
  name: 'TreeItem',
  props: { item: Object }
}
</script>

Force update và v-once

this.$forceUpdate() ép render lại, gần như không bao giờ cần nếu state đúng reactive. Nếu phải dùng, thường là dấu hiệu data không reactive (xem note Vue Instance về $set).

v-once render một lần rồi cache, bỏ qua cập nhật sau đó. Dùng cho nội dung tĩnh nhiều để tối ưu.

<span v-once>{{ neverChanges }}</span>

Tóm tắt khi nào dùng

Kỹ thuậtDùng khiCảnh báo
$refsThao tác DOM/method bắt buộcKhông reactive, chỉ sau mount
$parent / $rootHầu như không nênCoupling chặt
provide/injectConfig tĩnh xuống sâuMặc định không reactive
$on/$once/$offCleanup listener third-partyTự quản lý vòng đời
Recursive componentCấu trúc câyPhải có điều kiện dừng
$forceUpdateGần như khôngThường do data sai reactive
v-onceNội dung tĩnhKhông cập nhật lại

Tips cho AEM

Tình huống hay gặp: tích hợp thư viện third-party đã có sẵn trong project AEM (carousel, datepicker, chart). Khởi tạo trong mounted dùng $refs, và huỷ trong beforeDestroy để tránh memory leak khi component bị remove (vd khi điều hướng hoặc author xoá component).

<template>
  <div ref="carousel"></div>
</template>

<script>
export default {
  mounted() {
    this.instance = new ThirdPartyCarousel(this.$refs.carousel)
  },
  beforeDestroy() {
    if (this.instance) this.instance.destroy()
  }
}
</script>