Vue.js (5) : Bileşenlerin Birbiriyle Haberleşmesi ve Vuex

Vue.js ile uygulama geliştirirken, bileşenler üzerine bir yapı kuruyoruz. Bu yazımızda, bileşenler birbiriyle nasıl haberleşiyor; ana bileşenden detay bileşenlere, detay bileşenden diğer detay bileşenlere nasıl veri gönderilir? Tüm olası senaryoları inceleyeceğiz ve vuex kütüphanesinin bu konuya nasıl bir katkı sağladığına bakacağız.

Ana Bileşenden Detay Bileşenlere Veri Aktarma

props özelliğini kullanarak veri aktarabiliriz. vue-cli ile webpack-simple şablonuyla bir proje oluşturalım. src dizininin içerisine Components dizini oluşturalım. Components dizininin içinde de Child.vue dosyası oluşturalım.

Child.vue:
<template>
    <div class="child">
        <br>
        <h1>Child</h1>
        <br>
        <span>Message from parent:</span><br>
        <span class="parentMessage">{{parentMessage}}</span>
    </div>
</template>

<script>
export default {
  name: 'app',
  props: ['parentMessage'],
  data () {
    return {      
    }
  }
}
</script>

<style>
  .child {
      height: 80%;
      background-color: lawngreen;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>
App.vue:
<template>
    <div class="wrapper">
      <div class="header">
        <h1>Ana Başlık</h1>
        <input type="text" v-model="message"/>
      </div>
      <child-component :parentMessage="message"></child-component>
    </div>
</template>

<script>
import Child from './components/Child.vue';

export default {
  name: 'app',
  data () {
    return {
      message: ''
    }
  },
  components: {
    'child-component':Child
  }
}
</script>

<style>
  * {
    margin: 0;
    padding: 0;
  }

  html, body {
    height: 100%;
  }

  .wrapper {
    height: 100%;
    background-color: darkorange;
  }

  .header {
    height: 20%;
    background-color: red;
    text-align: center;
    color: blue;
  }
</style>

input’a veri girdiğimiz zaman detay bileşene reaktif olarak yansıdığını görürüz.

Detay bileşenden Ana bileşene Mesaj Gönderme

Tersi durumda nasıl yaparız? Yani detay bileşenden ana bileşene nasıl mesaj göndeririz? Temel olarak üç şekilde yaparız:

custom event kullanarak:

Detay bileşenden custom event gönderiyoruz. Bu eventi dinleyen ana bileşen gelen veriyi işliyor. Bu yönteme göre Child.vue ve App.vue bileşenlerini tekrar yazalım:

Child.vue:
<template>
    <div class="child">
        <br>
        <h1>Child</h1>
        <br>
        <span>Message from parent:</span><br>
        <span class="parentMessage">{{parentMessage}}</span><br>
        <span>Message</span>
        <input type="text" v-on:keyup="keyupEvent" v-model="message"/>
    </div>
</template>

<script>
export default {
  name: 'app',
  props: ['parentMessage'],
  data () {
    return {      
        message: ''
    }
  },
  methods: {
      keyupEvent() {
          this.$emit('messageFromChild',this.message);
      }
  }
}
</script>

<style>
  .child {
      height: 80%;
      background-color: lawngreen;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>
app.vue:
<template>
    <div class="wrapper">
      <div class="header">
        <h1>Ana Başlık</h1>
        <input type="text" v-model="message"/>
        <br><br>
        <span>Message from child</span>
        <span class="child-message">{{messageFromChild}}</span>
        <br>
      </div>
      <child-component :parentMessage="message" @messageFromChild="messageFromChild = $event"></child-component>
    </div>
</template>

<script>
import Child from './components/Child.vue';

export default {
  name: 'app',
  data () {
    return {
      message: '',
      messageFromChild: ''
    }
  },
  components: {
    'child-component':Child
  }
}
</script>

<style>
  * {
    margin: 0;
    padding: 0;
  }

  html, body {
    height: 100%;
  }

  .wrapper {
    height: 100%;
    background-color: darkorange;
  }

  .header {
    height: 20%;
    background-color: red;
    text-align: center;
    color: blue;
  }

  .child-message {
    background-color: lawngreen;
  }
</style>

Ana bileşen @messageFromChild ifadesi ile (v-on:messageFromChild ifadesinin kısaltması), messageFromChild eventini dinler ve olay gerçekleşince, kendi datasını günceller.

Callback İle Ana Bileşene Mesaj Gönderme:

Bahsedeceğimiz diğer yöntem, callback kullanarak veri iletme.

child.vue:
<template>
    <div class="child">
        <br>
        <h1>Child</h1>
        <br>
        <span>Message from parent:</span><br>
        <span class="parentMessage">{{parentMessage}}</span><br>
        <span>Message</span>
        <input type="text" v-on:keyup="keyupEvent" v-model="message"/>
    </div>
</template>

<script>
export default {
  name: 'app',
  props: {
      parentMessage: {
          type: String
      },
      myCallbackFn: Function
  },
  data () {
    return {      
        message: ''
    }
  },
  methods: {
      keyupEvent() {
          this.myCallbackFn(this.message);
      }
  }
}
</script>

<style>
  .child {
      height: 80%;
      width: 50%;
      float: left;
      background-color: lawngreen;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>

App.vue:

<template>
    <div class="wrapper">
      <div class="header">
        <h1>Ana Başlık</h1>
        <input type="text" v-model="message"/>
        <br><br>
        <span>Message from child</span>
        <span class="child-message">{{messageFromChild}}</span>
        <br>
      </div>
      <child-component :parentMessage="message" :myCallbackFn="myCallbackFn"></child-component>
    </div>
</template>

<script>
import Child from './components/Child.vue';

export default {
  name: 'app',
  data () {
    return {
      message: '',
      messageFromChild: ''
    }
  },
  components: {
    'child-component':Child
  },
  methods: {
    myCallbackFn(message) {
      this.messageFromChild = message;
    }
  }
}
</script>

<style>
  * {
    margin: 0;
    padding: 0;
  }

  html, body {
    height: 100%;
  }

  .wrapper {
    height: 100%;
    background-color: darkorange;
  }

  .header {
    height: 20%;
    background-color: red;
    text-align: center;
    color: blue;
  }

  .child-message {
    background-color: lawngreen;
  }
</style>

Ana bileşenden detay bileşene nasıl property gönderdiysek benzer şekilde de callback fonksiyon iletebiliyoruz.

Event Bus Kullanarak Veri Göndermek

Şimdi örneğimizi daha da geliştirelim. Bir detay bileşenden diğer bir detay bileşene veri aktaralım.

vue init webpack-simple eventbusapp

eventbusapp uygulamasını oluşturalım. main.js dosyasına eventBus nesnesi ekleyelim. Herhangi bir dom elemanına bağlanmayan bir vue nesnesi oluşturuyoruz. Bu nesneyi sadece veri iletme kanalı olarak kullanıyoruz. Yani tüm bileşenler buraya event yollayabilirler. Bu eventlerle ilgilenen diğer bileşenlerde dinleyerek ona göre aksiyon alabilirler.

main.js:
import Vue from 'vue'
import App from './App.vue'

export const eventBus = new Vue();

new Vue({
  el: '#app',
  render: h => h(App)
})

Bileşenleri eventBus’ı kullanacak şekilde güncelliyoruz.

ChildA:

<template>
    <div class="child-a">
        <br>
        <h1>Child A</h1>
        <br>
        <span>Message Gönder</span>
        <input type="text" v-on:keyup="keyupEvent" v-model="message"/>
    </div>
</template>

<script>
import { eventBus } from '../main';

export default {
  name: 'app',
  props: {
      parentMessage: {
          type: String
      }
  },
  data () {
    return {      
        message: ''
    }
  },
  methods: {
      keyupEvent() {
          eventBus.$emit('messageFromChildA',this.message);
      }
  }
}
</script>

<style>
  .child-a {
      height: 80%;
      width: 50%;
      float: left;
      background-color: lawngreen;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>
ChildB:
<template>
    <div class="child-b">
        <br>
        <h1>Child B</h1>
        <br>
        <span>Message from Child A:</span>
        <span class="parentMessage">{{messageFromChildA}}</span><br>
    </div>
</template>

<script>
import { eventBus } from '../main';

export default {
  name: 'app',
  props: {
      parentMessage: {
          type: String
      },
      myCallbackFn: Function
  },
  data () {
    return {      
        message: '',
        messageFromChildA: ''
    }
  },
  created() {
      eventBus.$on('messageFromChildA', (message) => {
          this.messageFromChildA = message;
      });
  }
}
</script>

<style>
  .child-b {
      height: 80%;
      width: 50%;
      float: left;
      background-color: yellow;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>

ChildA bileşeni, keyupevent metodunda, bahsettiğimiz eventbus nesnesine event yolluyor. ChildB bileşeni de bu eventi dinlediği için, reaktif olarak kendi datasını güncelliyor.

App.vue:
<template>
    <div class="wrapper">
      <div class="header">
        <h1>Ana Başlık</h1>
      </div>
      <child-a></child-a>
      <child-b></child-b>
    </div>
</template>

<script>
import ChildA from './components/ChildA.vue';
import ChildB from './components/ChildB.vue';

export default {
  name: 'app',
  data () {
    return {
      message: '',
      messageFromChild: ''
    }
  },
  components: {
    'child-a':ChildA,
    'child-b':ChildB
  }
}
</script>

<style>
  * {
    margin: 0;
    padding: 0;
  }

  html, body {
    height: 100%;
  }

  .wrapper {
    height: 100%;
    background-color: darkorange;
  }

  .header {
    height: 20%;
    background-color: red;
    text-align: center;
    color: blue;
  }

  .child-message {
    background-color: lawngreen;
  }
</style>

Vuex Nedir?

Küçük ve orta boy uygulamalar için bu saydığımız yöntemler geçerli ve uygun yöntemlerdir. Fakat uygulama büyüdükçe ve karmaşıklığı arttıkça bu tür yöntemler, uygulamanın işleyişini anlamamızı ve takip edebilmemizi güçleştirecektir. Bu yüzden react sisteminde olduğu gibi flux, redux benzeri yapılara ihtiyaç duyulmuştur.

Vuex, merkezi bir durum yönetim kütüphanesidir. Flux mimarisini Vue sisteminde uygulamamıza yardımcı olmaktadır. Uygulama verilerini, tahmin edilebilir ve şeffaf bir durumda tutar. Bileşenlerin ihtiyaç duyduğu ortak veriler merkezi bir yerde tutulur.

Vue ekosisteminde, vuex kütüphanesi bizim merkezi yapı (central store) ihtiyacımızı karşılayacaktır.

vuexapp uygulamasını oluşturalım.

vue init webpack-simple vuexapp

vuex kütüphanesini kuralım.

npm install --save vuex

Şimdi store yapısını oluşturmak için src dizininde store.js dosyası oluşturuyorum:

store.js:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
    state: {
        messageA: 'test'
    },
    getters: {
        getMessageA: state => {
            return state.messageA;
        }
    },
    mutations: {
        setMessageA: (state, n) => {
            state.messageA = n;            
        }
    }
});
store.js dosyasını main.js dosyasında import edip, vue objemize opsiyon olarak gönderiyoruz:
import Vue from 'vue';
import App from './App.vue';
import { store } from './store';

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

store.js dosyasında, vuex store’u tanımlıyoruz. Bileşenlerin state nesnesine doğrudan ulaşmasını istemiyoruz. getters metodları ile veriler okunuyor. Değişiklik gerektiğinde de mutasyon metodları ile değişiklik gerçekleştiriliyor.

Mutasyon metodları senkron çalışıyor. Bu şekilde çalışması, mevcut state’ın başka bir state’e geçmesi durumunda, asenkron metodların yan etkilerinden kurtulmak için ve tahmin edilebilir bir yapıda kalması için gereklidir.

Aynı şekilde ChildA.vue ve ChildB.vue bileşenlerini src/components dizininin içerisinde tanımlayalım.

ChildA.vue
<template>
    <div class="child-a">
        <br>
        <h1>Child A</h1>
        <br>
        <span>Message Gönder</span>
        <input type="text" v-on:keyup="keyupEvent" v-model="message"/>
    </div>
</template>

<script>

export default {
  name: 'app',
  props: {
      parentMessage: {
          type: String
      }
  },
  data () {
    return {      
        message: ''
    }
  },
  methods: {
      keyupEvent() {
          this.$store.commit('setMessageA',this.message);
      }
  }
}
</script>

<style>
  .child-a {
      height: 80%;
      width: 50%;
      float: left;
      background-color: lawngreen;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>
ChildB.vue
<template>
    <div class="child-b">
        <br>
        <h1>Child B</h1>
        <br>
        <span>Message from Child A:</span>
        <span class="parentMessage">{{messageFromChildA}}</span><br>
    </div>
</template>

<script>

export default {
  name: 'app',
  props: {
      parentMessage: {
          type: String
      },
      myCallbackFn: Function
  },
  data () {
    return {      
        message: '',
    }
  },
  computed: {
    messageFromChildA() {
        return this.$store.getters.getMessageA;
    }
  },
  created() {
  }
}
</script>

<style>
  .child-b {
      height: 80%;
      width: 50%;
      float: left;
      background-color: yellow;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>
App.vue:
<template>
    <div class="wrapper">
      <div class="header">
        <h1>Ana Başlık</h1>
      </div>
      <child-a></child-a>
      <child-b></child-b>
    </div>
</template>

<script>
import ChildA from './components/ChildA.vue';
import ChildB from './components/ChildB.vue';

export default {
  name: 'app',
  data () {
    return {
      message: '',
      messageFromChild: ''
    }
  },
  components: {
    'child-a':ChildA,
    'child-b':ChildB
  }
}
</script>

<style>
  * {
    margin: 0;
    padding: 0;
  }

  html, body {
    height: 100%;
  }

  .wrapper {
    height: 100%;
    background-color: darkorange;
  }

  .header {
    height: 20%;
    background-color: red;
    text-align: center;
    color: blue;
  }

  .child-message {
    background-color: lawngreen;
  }
</style>

Bu sefer görüldüğü gibi ChildA’nın keyup metodunda,

this.$store.commit('setMessageA',this.message);

ifadesiyle setMessageA mutasyon metodunu çağırıyoruz.

ChildB ise getMessageA getteri ile değeri reaktif olarak alıyor. Daha önce belirttiğimiz gibi mutasyon metodları senkron olarak çalışırlar.

Fakat state’ı asenkron çalışan metodlar ile de güncelleyebiliriz. Bu durumda action metodları tanımlamamız gerekiyor. Aslına bakarsanız ne olursa olsun, ister senkron ister asenkron olsun, doğru olan ve tavsiye edilen yöntem, state’ı değiştirmek için action tanımlanması ve action metodlarının bileşenler tarafından çağrılması. Bu yüzden örneğimizi action kullanacak şekilde değiştireceğiz.

store.js:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
    state: {
        messageA: 'test'
    },
    getters: {
        getMessageA: state => {
            return state.messageA;
        }
    },
    mutations: {
        setMessageA: (state, n) => {
            state.messageA = n;            
        }
    },
    actions: {
        setMessageAAction: (context, n) => {
            context.commit('setMessageA',n);
        }
    }
});
ChildA.vue:
<template>
    <div class="child-a">
        <br>
        <h1>Child A</h1>
        <br>
        <span>Message Gönder</span>
        <input type="text" v-on:keyup="keyupEvent" v-model="message"/>
    </div>
</template>

<script>

export default {
  name: 'app',
  props: {
      parentMessage: {
          type: String
      }
  },
  data () {
    return {      
        message: ''
    }
  },
  methods: {
      keyupEvent() {
          this.$store.dispatch('setMessageAAction', this.message);
      }
  }
}
</script>

<style>
  .child-a {
      height: 80%;
      width: 50%;
      float: left;
      background-color: lawngreen;
      text-align: center;
  }

  .parentMessage {
      font-weight: bold;
      color: red;
      font-size: 40px;
  }
</style>

ChildA bileşeninden dispatch metodu ile action metodunu çağırıyoruz.

Son olarak proje dizin yapısına bakalım:

Vuex ile ilgili öğrenilecek çok şey var, biz bu yazıyla bu konuya ufak bir giriş yapmış olduk.

Sorularınızı ve yorumlarınızı bekliyorum.

 

 

 

7 düşünce - “Vue.js (5) : Bileşenlerin Birbiriyle Haberleşmesi ve Vuex”

Yorum Gönder

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Scroll to Top