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/custom-events.md.

Handling component v-model and custom events in Vue 2.7.

Custom Events

Con báo ngược lên cha bằng cách emit event. Cha lắng nghe bằng v-on/@. Đây là chiều còn lại của luồng dữ liệu một chiều (cha xuống con bằng props, con lên cha bằng events).

Emit và lắng nghe

Con phát event bằng this.$emit('event-name', payload).

<!-- Child: ToggleButton.vue -->
<template>
  <button @click="$emit('toggle')">Toggle</button>
</template>
<!-- Parent -->
<toggle-button @toggle="onToggle"></toggle-button>

Truyền dữ liệu kèm event

Tham số sau tên event là payload, cha nhận trong handler.

<template>
  <button @click="$emit('select', item.id)">Chọn</button>
</template>
<item-row @select="onSelect"></item-row>
methods: {
  onSelect(id) {
    console.log('selected', id)
  }
}

Cần nhiều giá trị thì emit một object.

this.$emit('change', { field: 'name', value: 'Nhan' })

Tên event phải kebab-case

Khác với props/component, tên event không tự chuyển đổi case. Vue khuyến nghị luôn dùng kebab-case cho tên event, vì HTML không phân biệt hoa thường.

// emit
this.$emit('item-selected')
<!-- lắng nghe đúng tên -->
<my-item @item-selected="..."></my-item>

v-model trên component

v-model trên component là cú pháp rút gọn của: bind prop value cộng lắng nghe event input.

<custom-input v-model="searchText"></custom-input>

<!-- tương đương -->
<custom-input
  :value="searchText"
  @input="searchText = $event">
</custom-input>

Component phải khai báo prop value và emit input:

<template>
  <input :value="value" @input="$emit('input', $event.target.value)">
</template>

<script>
export default {
  props: { value: String }
}
</script>

Đổi tên prop/event mặc định bằng option model:

export default {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: { checked: Boolean }
}

.sync modifier

.sync cho phép two-way binding một prop bất kỳ. Cha viết :prop.sync="x", con emit update:prop.

<modal-box :visible.sync="showModal"></modal-box>
// trong con: đóng modal
this.$emit('update:visible', false)

Giao tiếp ngoài quan hệ cha-con

$emit/props chỉ giải quyết cha-con trực tiếp. Component không liên quan trực tiếp thì dùng event bus đơn giản (Vue 2) hoặc state store. Với app nhỏ trong AEM thường không cần; nâng state lên component cha chung là đủ.

// event bus tối giản (Vue 2.7)
const bus = new Vue()
bus.$emit('refresh')
bus.$on('refresh', handler)

Tips cho AEM

Trong một AEM component mount Vue, child component emit event lên root để root xử lý gọi API hoặc cập nhật state chung.

<!-- root: SearchWidget.vue -->
<template>
  <div>
    <search-input @search="fetchResults" />
    <result-list :items="results" />
  </div>
</template>

<script>
import SearchInput from './SearchInput.vue'
import ResultList from './ResultList.vue'

export default {
  components: { SearchInput, ResultList },
  data() {
    return { results: [] }
  },
  methods: {
    async fetchResults(keyword) {
      const res = await fetch(`/bin/search?q=${encodeURIComponent(keyword)}`)
      this.results = await res.json()
    }
  }
}
</script>