Vue.js Component: Props

Şimdiye değin Vue.js’de veriyi data ve computed ile yönetmiştik.

AA

Bir önceki yazıda, components başlığında giriş yaptığımız props‘u (properties) veriyi yönetebileceğimiz bir diğer Vue.js veri tipi olarak ifade edebiliriz. Özetle, props ile tanımlanan component içeriğine kolaylıkla bilgi aktarabilir (set) ve kullanabiliriz. Gelelim örneklere ve diğer detaylara.

Vue.js Props Kullanımı

HTML nitelik (attribute) isimleri case-insensitive‘dir. Yani, büyük-küçük harf ayrımı yoktur. Bu nedenle internet tarayıcıları büyük harfli yazımları da küçük harf olarak yorumlar. Bu nedenle, DOM şablonlarında camelCased prop isimlerini kebab-cased (hyphen-delimited) olarak kullanırız. Hemen bir örnek oluşturalım.

Vue.component('blog-post', {
  // camelCase in JS: postTitle,
  // kebab-case in HTML: post-title
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<blog-post post-title="It's a New Post!"></blog-post>

Elbette component birden fazla prop değeri alabilir.

props: ['name', 'image', 'description', 'sku', 'brand', 'price', 'variant']

Bu tanımlamada tüm prop değerleri aynı özelliklere sahiptir. Ancak, gerektiği durumlarda kullanmak üzere Vue.js bize prop tanımları için tip başta olmak üzere çeşitli tanımlamalar yapma imkanı sunar. Değer belirtilen tipten farklı ise hata döner.

props: {
  name: String,
  sku: Number,
  variant: Boolean,
  brand: String,
  price: Number,
  image: Array,
  description: Object,
  callback: Function
  ...
}

Prop tanımlarken tip (type) dışında, ayrıca ön tanımlı değer (default) ve zorunluluk (require) ifadeleri de kullanabilmekteyiz. Unutmadan, birden fazla tip de tanımlayabilmekteyiz1 2.

props: {
  name: [String, Number],
  sku: {
      type: Number,
      required: true
  },
  variant: {
      type: Boolean,
      required: false,
      default: 0
  },
  brand: String,
  price: Number,
  image: Array,
  description: {
      type: Object,
      default: function () {
        return { message: 'Vue' }
      }
    },
  custom: {
      validator: function (value) {
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  ...
}

Statik ve Dinamik Değerler

Prop üzeriden statik veya dinamik değerler gönderebiliriz. Statik değerler için attr ifade yeterli iken, dinamik değerler için v-bind direktifinini kullanmamız gerekir.

<single-product title="Echo Dot - Smart speaker with Alexa - Charcoal"></single-product>
<single-product v-bind:title="item.title"></single-product>

Bu kullanımı, Vue.js Bileşenler (Components) başlıklı bir önceki yazıda, yazarları listelerken tercih etmiştik.

Unutmadan, props dıştan içe, yani component’i kapsayan alandan component’e ve sub-component’lere doğru aktarılır. Bu one-way-down binding olarak ifade edilmektedir. Bu, sub-component içeriğinden yanlışlıkla ana kapsayıcıya (parent diyelim) müdahale edilmesini önler. Böylelikle, veri akışıyla ilgili karmaşa ve sorunlar da önlenmiş olur. Ek olarak, ana bileşen (parent component) her güncellendiğinde, alt bileşene (child component) ait tüm props değerleri de yenilenir.

Bir üst paragrafa ek olarak şöyle bir not düşmek istiyorum. İstisnai bir durum gereği, bir child component içeriğinden parent component’teki bir değere ulaşmak ve değişiklik yapmak isteyebiliriz. Bu gibi durumlarda this.$attrs.attrName kullanabiliriz3. Örneğin, aşağıdaki gibi bir component için (availability prop olarak tanımlı değil iken) availability‘e this.availability=this.$attrs.availability şeklinde erişebiliriz.

<single-product title="" availability=""></single-product>

Açıklamaların ardından, örneklerimize kaldığımız yerden devam edebiliriz.

Vue.component('single-product', {
      props: {
        product: {
          type: Object,
          require: true
        }
      },
      template: `
      <div>
        <p>ID: {{ product.id }}</p>
        <p>Name: {{ product.name }}</p>
      </div>`,
    });

// Vue v2.x
new Vue({
  el: '#app',
  data: {
    products: [
      { id: 1, name: 'Echo Dot (3rd Gen) - Smart speaker with Alexa - Charcoal' },
      { id: 2, name: 'Echo Plus (2nd Gen) Bundle with Philips Hue Bulb - Sandstone' },
      { id: 3, name: 'Sonos One (Gen 2) - with Amazon Alexa (Black)' }
    ]
  }
});

// Vue v3.x
Vue.createApp({
  data(){
    return {
      products: [
        { id: 1, name: 'Echo Dot (3rd Gen) - Smart speaker with Alexa - Charcoal' },
        { id: 2, name: 'Echo Plus (2nd Gen) Bundle with Philips Hue Bulb - Sandstone' },
        { id: 3, name: 'Sonos One (Gen 2) - with Amazon Alexa (Black)' }
      ]
    }
  }
}).mount('#app');

Yukarıdaki içeriği kullanalım.

<single-product v-for="(product, index) in products" :key="product.id" :product="product" />

:product="product" ile object olarak v-for direktifi ile id ve name (ve elbette daha ne varsa) doğrudan aktarılabilmekte. Bu sayede ayrı ayrı prop tanımı yapmamıza gerek kalmamakta. Ancak, prop’ları tanımladığımız şekilde de kullanabilmekteyiz.

Olay (Event) İle Değer Aktarmak (Emitting)

Kimi durumlarda bir spesifik değeri olay (event) ile emit etmemiz gerekebilir. Bu durumda, parent component içerisinden child component’teki olayları (etkinlik) $event ile dinlememiz gerekir. Hemen bir örnek işlem gerçekleştirelim:

<div id="app">
  <div v-cloak>
    <p v-show="count == 0">Your cart is empty!</p>
    <p v-show="count > 0">{{ count }} products in your cart.</p>
    <hr />
    <p>
      <single-product v-on:addtocart="count += $event" v-for="product of products" :key="product.id" :product="product" />
    </p>
  </div>
</div>

single-product‘da tanımlı addtocart ile component içerisinden gelen $emit('addtocart', 1) değerini dinlemekteyiz.

Vue.component('single-product', {
  props: {
    'product': {
      type: Object,
      require: true
    }
  },
  template: `
  <div>
    <h3>{{ product.name }}</h3>
    <button v-on:click="$emit('addtocart', 1)">Add to cart</button>
  </div>`,
});

// Vue v2.z
const app = new Vue({
  el: '#app',
  data: {
    count: 0,
    products: [{
      id: 1,
      name: 'Echo Dot (3rd Gen) - Smart speaker with Alexa - Charcoal'
    }, {
      id: 2,
      name: 'Echo Plus (2nd Gen) Bundle with Philips Hue Bulb - Sandstone'
    }, {
      id: 3,
      name: 'Sonos One (Gen 2) - with Amazon Alexa (Black)'
    }]
  }
});

// Vue v3.z
const app = Vue.createApp({
  data(){
    return {
      count: 0,
      products: [{
        id: 1,
        name: 'Echo Dot (3rd Gen) - Smart speaker with Alexa - Charcoal'
      }, {
        id: 2,
        name: 'Echo Plus (2nd Gen) Bundle with Philips Hue Bulb - Sandstone'
      }, {
        id: 3,
        name: 'Sonos One (Gen 2) - with Amazon Alexa (Black)'
      }]
    }
  }
}).mount('#app');

Bu sayede, Add to Cart butonlarından herhangi birini her tıklamamızda count değerini bir artırıyoruz. Şahane, değil mi? Peki, bu işlemi bir de method ile yapmaya ne dersiniz?

<single-product v-on:cartcounter="addToCart" v-for="product of products" :key="product.id" :product="product" />

Evet, addToCart adında bir metot oluşturuldu ve herhangi bir buton tıklandığında bu metot ile count değerinin artırılmasını sağladık.

Vue.component('single-product', {
  props: {
    'product': {
      type: Object,
      require: true
    }
  },
  template: `
  <div>
    <h3>{{ product.name }}</h3>
    <button v-on:click="$emit('cartcounter', 1)">Add to cart</button>
  </div>`,
});

// Vue v2.x
const app = new Vue({
  el: '#app',
  data: {
    count: 0,
    products: [{
      id: 1,
      name: 'Echo Dot (3rd Gen) - Smart speaker with Alexa - Charcoal'
    }, {
      id: 2,
      name: 'Echo Plus (2nd Gen) Bundle with Philips Hue Bulb - Sandstone'
    }, {
      id: 3,
      name: 'Sonos One (Gen 2) - with Amazon Alexa (Black)'
    }]
  },
  methods: {
    addToCart(e) {
      this.count += e
    }
  }
});

// Vue v3.x
const app = Vue.createApp({
  data(){
    return {
      count: 0,
      products: [{
        id: 1,
        name: 'Echo Dot (3rd Gen) - Smart speaker with Alexa - Charcoal'
      }, {
        id: 2,
        name: 'Echo Plus (2nd Gen) Bundle with Philips Hue Bulb - Sandstone'
      }, {
        id: 3,
        name: 'Sonos One (Gen 2) - with Amazon Alexa (Black)'
      }]
    }
  },
  methods: {
    addToCart(e) {
      this.count += e
    }
  }
}).mount('#app');

Form işlemlerinde v-model direktifini kullanarak form elemanlarıyla çeşitli işlemler (select, input, checked) yapmıştık. Şimdi, bu işlemleri bir de component içerisinde gerçekleştirelim.

<div id="app">
  <div v-cloak>
    <p>
      <strong>Component</strong><br />
      <custom-input v-model="searchText"></custom-input>
      ya da<br />
      <custom-input v-bind:value="searchText" v-on:input="searchText = $event" />
    </p>
    <p>
      <strong>Input Element</strong><br />
      <input v-model="searchText" /> Value (searchText): {{ searchText }}<br />
      ya da<br />
      <input v-bind:value="searchText" v-on:input="searchText = $event.target.value" /> Value (searchText):
      {{ searchText }}
    </p>
  </div>
</div>
Vue.component('custom-input', {
  props: ['value'],
  template: `
  <div>
    <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)">
    Value (value): {{ value }}
  </div>
  `
});

// Vue v2.x
const app = new Vue({
  el: '#app',
  data: {
    value: null,
    searchText: null
  },
  methods: {
    //...
  }
});

// Vue v3.x
const app = Vue.createApp({
  data(){
    return {
      value: null,
      searchText: null
    },
  }
  methods: {
    //...
  }
}).mount('#app');

Yukarıdaki örnekte component ve/veya doğrudan input içerisine girilen değerler parent ve child component arasında dinlenmekte ve değişiklikler (input) value olarak (v-on:input ve v-model ile) yansıtılmaktadır.

Sonuç Olarak

Component kullanımı pek çok örnek altında sıklıkla tercih edeceğimiz bir özellik. Bu sebeple, örneklerin yanı sıra çeşitli ek açıklamalarla da yazıyı zengin tutmaya çalışacağım. Yazı içerisinde bahsi geçen örnekle ilgili olarak GitLab > Vue Examples içeriğine göz atabilirsiniz. Eklemek istediğiniz hususlar var ise yorum olarak paylaşabilir ve/veya mesaj olarak iletebilirsiniz.