Vue.js Components: Custom Events
Vue.js Component Props başlıklı yazıda, Olay (Event) İle Değer Aktarmak (Emitting) başlığı altıda olayların (events) oluşturulması ve aktarılmasına değinmeye çalışmıştım.
Bu yazıda, event (olay) kullanımını biraz daha detaylandırmak, component kullanımlarıyla da ilişkili olarak işlemlerin nasıl paylaşılabileceğinden bahsetmek istiyorum.
Custom Events
Props yazısında basit bir sepet (cart) örneği oluşturmuştuk. Ürünleri component olarak listelemiş, buton aracılığıyla da cart’a eklenmelerini (cart ürün adet değerini artırarak) sağlamıştık. Bu yazıda ise örnekleri değiştirerek ilerleyeceğim. Ek olarak, yakın zamanda Bulma CSS ve Semantic UI CSS framework‘lerinden bahsettim. Bu vesile ile, örnekleri daha kapsamlı bir şekilde sunmak istiyorum. Konuyu çok dağıtmadan öncelikle event tanımlama konusuna bakalım.
Component ve prop tanımlarken dikkat ettiğimiz, automatic case transformation kuralı vardı. Bu kural neticesinde PascalCase veya camelCased bir tanımlama otomatik olarak (JS ve HTML kurallarına göre) yeniden ele alınırdı. Emitted event herhangi bir kural barındırmaz ve tanımlanan ne ise o şekilde ulaşılır. Örneğin anEvent
şeklinde tanımladığımız olaya an-event
veya anevent
yerine, yine anEvent
ile ulaşabiliriz.
this.$emit('anEvent')
Aşağıdaki kullanımlarla anEvent
ilişkisini test edebilirsiniz.
<component v-on:an-event="..."></component>
<component v-on:anevent="..."></component>
<component v-on:anEvent="..."></component>
Ayrıca, component ve prop’un aksine, event isimleri JS variable veya propperty isimlerinde kullanılamaz. Bu nedenle camelCase ve/veya PascalCase gereksiz olacaktır. Diğer yandan, DOM şablonları (templates) otomatik olarak listener’ları lowercase olarak ele alacaktır. Örneğin, v-on:anEvent
kullanımı v-on:anevent
olarak değerlendirilir. Bu nedenle, event isimleri için kebab-case kullanımı önerilir. Örneğin, v-on:an-event
. Şimdi, basit bir örnek işlem gerçekleştirelim.
<section id="app" class="section">
<div class="container">
<p>{{ count }}</p>
<hr />
<comp v-on:calculate="count += $event">New Day!</comp>
<comp v-on:calculate="count += $event">New World!</comp>
</div>
</section>
Oluşturduğumuz component’i istediğimiz kadar çoğaltabiliriz. Her durumda, component içeriğinde yer alan butonlar tanımlayacağımız olayı (event) işleyeceklerdir. Olayımız şöyle olsun: v-on:click="$emit('...', data)"
.
// Vue v2.x
Vue.component('comp', {
template: `
<div class="box">
<p><slot></slot></p>
<button v-on:click="$emit('calculate', 1)">+1</button>
<button v-on:click="$emit('calculate', -1)">-1</button>
</div>`,
});
var app = new Vue({
el: '#app',
data: {
count: 0
}
});
// Vue v3.x
const app = Vue.createApp({
data() {
return {
count: 0
}
}
});
app.component('comp', {
template: `
<div class="box">
<p><slot></slot></p>
<button v-on:click="$emit('calculate', 1)">+1</button>
<button v-on:click="$emit('calculate', -1)">-1</button>
</div>`
});
app.mount('#app');
Evet, button
elementine tıklama sonucunda işleme alacağı olayı calculate
adıyla ilettik. Bu olay aynı zamanda +1
ve -1
değerlerini de taşımakta. Şimdi, comp adıyla oluşturduğumuz component içerisinden gelen olayı dinlememiz gerekiyor. v-on:calculate
ile olayı dinliyoruz. Bu aşamada v-on:calculate="count += $event"
ile $event
‘i yakalamış ve beraberinde getirdiği değeri count
değerine eklemiş oluyoruz. Yani, component içerisindeki butonlar $root
içerisinde yer alan count
değerine müdahale edebiliyorlar. Örneğin, yeni bir component daha ekleyelim, ancak listen
durumunu bu defa bir method ile ilişkilendirelim.
<comp v-on:calculate="counter">New World!</comp>
Vue instance içeriğine şu method tanımını ekleyelim.
methods: {
counter(val){
console.log(val)
}
}
Eklediğimiz component içeriğindeki butonlara tıkladığımızda console’da buton değerini görebilirsiniz. Görüldüğü üzere, inline statement kullanımında $event
property ile işlemler yapabilmekteyiz. Yukarıdaki örneği biraz daha geliştirelim ve v-on:click="..."
içerisinde method ile işlem gerçekleştirelim.
<section id="app" class="section">
<div class="container">
<div class="field is-grouped">
<comp @applied="compApplied">Comp 1</comp>
<compnew @applied="compApplied">Comp 2</compnew>
<custom-input v-model="searchText" @writesmth="searchText = $event"></custom-input>
</div>
<div class="box" v-if="status">
<div class="media-content">
<div class="content">
<p v-if="!searchText"><strong>It's a new world! Isn't it?</strong></p>
<p v-else>{{ searchText }}</p>
</div>
</div>
</div>
</div>
</section>
comp
ve compnew
adında 2 component oluşturalım. İlk component bir önceki örnekte olduğu gibi inline olarak event'i işlesin1 2. Bir diğer component ise method olarak aynı etkinliği işleme alsın.
// Vue v2.x
Vue.component('comp', {
template: `
<p class="control">
<a class="button" @click="childCompApplied">
<slot></slot>
</a>
</p>
`,
methods: {
childCompApplied() {
this.$emit('applied')
}
}
});
Vue.component('compnew', {
template: `
<p class="control">
<a class="button" @click="$emit('applied')">
<slot></slot>
</a>
</p>
`,
});
Vue.component('custom-input', {
props: ['value'],
template: `
<input
class="input" type="text"
v-bind:value="value"
placeholder="Type something..."
@input="$emit('writesmth', $event.target.value)">
`
});
var app = new Vue({
el: '#app',
data: {
status: false,
count: 0,
searchText: ''
},
methods: {
compApplied() {
(this.status == false) ? this.status = true: this.status = false;
}
},
computed: {
//
}
});
// Vue v3.x
const app = Vue.createApp({
data() {
return {
status: false,
count: 0,
searchText: ''
}
},
methods: {
compApplied() {
(this.status == false) ? this.status = true: this.status = false;
}
},
computed: {
//
}
})
app.component('comp', {
template: `
<p class="control">
<a class="button" @click="childCompApplied">
<slot></slot>
</a>
</p>
`,
methods: {
childCompApplied() {
this.$emit('applied')
}
}
});
app.component('compnew', {
template: `
<p class="control">
<a class="button" @click="$emit('applied')">
<slot></slot>
</a>
</p>
`,
});
app.component('custom-input', {
props: ['value'],
template: `
<input
class="input" type="text"
v-bind:value="value"
placeholder="Type something..."
@input="$emit('writesmth', $event.target.value)">
`
});
app.mount('#app');
Görüldüğü üzere, sonuçta hiçbir farklılık yok; event fire ve listen süreçleri aynı şekilde gerçekleşmekte3 4. Yine, yukarıdaki örnekte custom-input
adında bir component yer almakta. Form input elemanı içeriğine bir değer girildiğinde, value searchText = $event
ile searchText
‘e değer olarak aktarılmakta.
v-model Direktifi
Varsayılan olarak, v-model içeren bir component değeri (value) prop ve input
event olarak alır. Ancak, checkbox ve radio button gibi input tiplerinde value farklı amaçlarla kullanılabilir. model: { prop: '', event: ''}
option bu gibi durumlarda ortaya çıkabilecek sorunları önlemeye yardımcı olabilir.
<base-checkbox v-model="lovingVue"></base-checkbox> {{ lovingVue }}
// Vue v2.x
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
});
// Vue v3.x
app.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
});
Inline statement ve method ile örnek işlemler yaptık. Bu örneklerde parent ve child component iletişimine değindik. Peki, sürece ilişkisiz component’ler arasında iletişimi dahil etmek istersek ne yapmalıyız?
Herhangi bir Vue instance $emit
event’i dinleyebilir (listen). O halde şöyle bir kullanım işimize yarayabilir: window.Event = new Vue();
window.Event = new class {
constructor() {
this.vue = new Vue();
}
fire(event, data = null) {
this.vue.$emit(event, data);
}
listen(event, callback) {
this.vue.$on(event, callback);
}
}
// Vue v2.x
Vue.component('comp', {
template: `
<div @click="compApplied">
<slot></slot>
</div>`,
methods: {
compApplied() {
Event.fire('applied')
}
}
});
Vue.component('compnew', {
template: `
<comp @applied>Hello!</comp>`,
methods: {
compApplied() {
Event.fire('applied')
}
}
});
var app = new Vue({
el: '#app',
data: {
status: false,
},
created() {
Event.listen('applied', () => (this.status == false) ? this.status = true : this.status = false)
}
});
// Vue v3.x
//
//
//
//
$emit
ve $on
yerine constructor
ile fire
ve listen
tanımlamalarını yaptık ve tüm işlemlerimizin yine aynı şekilde uygulandığını gördük.
<section id="app" class="section" v-cloak>
<comp @applied>Hello!</comp>
<compnew></compnew>
<p v-show="status"><strong>It's a new world! Isn't it?</strong></p>
</section>
Custom events ile ilgili örnekler ve anlatımlar şimdilik bu kadar. Özellikle bu yazı çerçevesinde laracasts.com eğitimlerinin çok faydasını gördüm. Ayrıca, bu yazıyı yayınladığım zaman içerisinde Vue 3 ile ilgili de yeni paylaşımlar yapılmaktaydı. Özellikle, Chris Fritz‘in (Core Team Member) Vue 3: What I’m Most Excited About sunumunu yazı ile de ilintili olması vesilesiyle öneriyorum.