15 Mart 2022 Salı

Vue3 (Vue.js versiyon-3) Öğreniyorum - Bölüm 2

Okumadıysanız önceki bölümde VueJs versiyon 3 bölüm 1 yazısını okumanız burada anlatılanı daha iyi anlamanıza yardımcı olacaktır.

LifeCycle fonksiyonları, Virtual DOM ve Komponentler gibi daha ileri konulara dalıyoruz bu bölümde.

Hep olduğu gibi bir klasör ekleyerek bağlayalım, adı app7 olsun (en son app6 idi) ve içine şu dosyaları koyalım.

app7/app.js

let vm = Vue.createApp({
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  }
}).mount('#app')


app7/index.html

<!DOCTYPE>
<html>

<head>
  <title>VueJS Öğreniyorum</title>
  <link rel="stylesheet" type="text/css" href="main.css" />
</head>

<body>
  <div id="app">
    {{ mesaj }}
  </div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

</html>

app7/main.css

body{
  font-size: 20px;
  font-family: sans-serif;
  background-color: darkseagreen;
}
.red{
  color: red;
}

Basit bir uygulama olarak başlıyoruz. Tarayıcıda index.html dosyasını açalım:

Mesaj göründü. Arka plan rengi sebebi buraya resim koyarken etrafına çerçeve çizmeye çalışmamak , yoksa başka amacım yok. 

Vue uygulamasını sayfaya yerleştirme işlemine mounting denir. Vue uygulaması her zaman üretildiği anda monte edilmez, bazen uygulamayı daha ileri bir zamanda monte etmek isteyebiliriz. Kullanıcıya görseli yayınlamadan önce başka modülleri de hazırlamak isteyebiliriz. app.js dosyası kodumuzda mount() metodu kullanarak uygulamayı monte ediyoruz. Kodumuzu monte işini 3 saniye sonra yapacak şekilde değiştirelim. 

app7/app.js

let vm = Vue.createApp({
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  }
})

setTimeout(() => {
  vm.mount("#app")
}, 3000)

Sayfayı yenilediğimizde 3 saniye boyunca monte yapılmadığını süre sonunda mesajın yerine konduğunu görürüz. 

Çoğunlukla sonradan montaj gerekmez ve Vue uygulaması üretildikten hemen sonra monte edilir. Ama bu olasılığı da bilmemiz iyi olur. Koda geri dönüp gecikmeyi iptal edelim. 

let vm = Vue.createApp({
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  }
})

vm.mount("#app")

Bu kullanımın ilk baştakinden çalışma bakımından bir farkı yok. Bence böyle daha okunabilir bir kod oldu. 


Vue Life Cycle (Yaşam Adımları)

Vue Life Cycle Vue uygulamasının üretilmesinden başlayan ve yok edildiği zamana kadar geçen süreçte başına gelenleri ifade eder. Adım adım Vue uygulamasının başına gelenleri inceleyelim. 


İlk adım Vue.createApp().mount() şeklinde Vue uygulamasının üretilmesi ve montajı. Bu adımla beraber Vue uygulaması yaşamı başlar. 

Sonraki adımda reaktif çalışmada kullanılacak olan data ve methods nesneleri ilk değerlerini alırlar. Bu adıma geçmeden önce bazı kodlar çalıştırmak istersek Vue bize bir imkan tanır.

beforeCreate olayı bu arada tetiklenir. Bu arada daha data oluşmamıştır. Bunun anlamı bu esnada herhangi bir veriye erişme olanağı yoktur. Vue uygulamamızın oluşumu hazır olduğunda başka bir olay tetiklenir.

created olayı tetiklendiğinde artık Vue uygulaması içindeki data ve methods erişilebilir ve kullanılabilir durumdadır. Ancak Vue uygulamamız hala görselde bir şablona monte edilmemiştir. Yani sayfada daha bir şey yayınlanmamıştır. Vue uygulaması monte edilmeden evvel görsel şablonun bir derlemesi yapılır.

Şablonun derlenmesi esnasında görsel şablon içindeki direktifler, eşitlikler, koşullar hepsi işlenerek görselde o anda gösterilecek olanı ifade eden el özelliğinin içeriği oluşturulur. Derleme sonrası oluşan içerik el elemanına değer olarak verilince şablon yayınlanmış olur.

İşte bu yerleştirme öncesi beforeMount olayı tetiklenir. Yerleştirme sonrası Vue uygulaması artık Mounted adımındadır ve artık uygulama çalışmaya başlamıştır. 

Bu adımdan itibaren uygulamamız sürekli bir döngü içine girer. Bu döngüde veri değişimleri, kullanıcı etkileşimleri gibi işlemler sürekli döner durur. Uygulama bu esnada verilerde değişimleri takip ederek gereğini verdiğimiz kodlara göre gerçekleştirir.

Mesela mouse tıklanınca ya da input değer girilince, veri değişir, şablona uygulanır ve tekrar Mounted adımına döner. Bu veri değişim döngüsünde tetiklenen 2 olay daha vardır.

beforeUpdate olayı veri değişmiş ama daha şablona uygulanmamışken tetiklenir. Şablon güncellendikten sonra da updated olayı tetiklenir. Bu 2 olay debug yaparken çok kullanılır. 

Nadir de olsa Vue uygulamamıza yapılacak bir şey daha var o da uygulamayı yok etmek. 

Vue uygulamamızın unmount() metodunu çağırdık mı , artık geri dönülemeyen bir yoldayız demektir. unmount() metodu çağırıldığında önce beforeUnmount olayı tetiklenir. Bu esnada hala veriler ve metodlar aktiftir ve kullanılabilir. Daha sonra Vue oluşumu yok edilir ve unmounted olayı tetiklenir ki burada artık Vue uygulamamızın hiç bir özelliğini kullanamayız. Uygulama yok edildikten sonra yapacağımız bir şey varsa bu olay kodunda yapabiliriz. 



Vue Life Cycle Olayları

Şimdi uygulamamızda bu olayları nasıl kullanacağımıza bakalım. beforeCreate olayı ile başlayalım. beforeCreate olayı esnasında daha verilerimiz oluşmamıştır. Olayları Vue uygulamamıza fonksiyonlar olarak ilave ederiz. Denemek için konsola mesaj yazdıralım.

app7/app.js

let vm = Vue.createApp({
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  },
  beforeCreate() {
    console.log("beforeCreate() olayı tetiklendi.", this.mesaj)
  }
})

Sayfayı yenileyip konsola bakarsak şunu görürüz.

Gördüğümüz gibi daha mesaj değişkeni tanımlanmamış. Sonraki olayımız created olayı.

app7/app.js

  beforeCreate() {
    console.log("beforeCreate() olayı tetiklendi.", this.mesaj)
  },
  created() {
    console.log("created() olayı tetiklendi.", this.mesaj)
  }

Sayfayı yenileyelim.

Artık data içinde tanımlamalar yapılmış. mesaj değeri konsola geldi. Sıradaki olay şablonun derlendiği ama henüz sayfaya yerleştirilmediği anda gerçekleşen beforeMount olayı.

app7/app.js

  beforeMount() {
    console.log("beforeMount() olayı tetiklendi.", this.$el)
  },

Bu $el değişkeni VueJs'in kendi içinde kullandığı bir değer. Monte edilmiş olan şablonu gösterir. Şu anda daha monte edilmediği için Null göreceğiz konsolda. 

Sırada mounted olayı var. Konsol mesajı aynı olsun.

app7/app.js

  mounted() {
    console.log("mounted() olayı tetiklendi.", this.$el)
  }

Artık $el değeri var çünkü monte işlemi bitti.

div elemanı içinde sadece basit bir yazı olduğu için $el elemanı sadece yazı olarak geldi. Ama div elemanı içinde başka elemanlar da olan gelişmiş uygulamalarda $el değeri kompleks bir nesne olacaktır. 

Sırada veri değerlerinde değişim olunca gerçekleşen olaylar var. beforeUpdate olayı ve updated olayı.

app7/app.js

  beforeUpdate() {
    console.log("beforeUpdate() olayı tetiklendi.", this.$el)
  },
  updated() {
    console.log("updated() olayı tetiklendi.", this.$el)
  }
}).mount("#app")

Bu arada sizin tarayıcınızda nasıl görünüyor bilmiyorum ama bende Opera var ve vm değişkenine konsoldan ulaşabilmek için mount() metodunu tekrar birleştirmek zorunda kaldım. Ya da monte edilmiş hale erişmek için

let vmm = vm.mount("#app")

Şeklinde yazıp konsoldan vmm adıyla Vue uygulamamıza erişebiliriz. Sayfayı yenileyip mesaj değerini konsoldan değiştirelim.

Şunu gördük ki update işlemi öncesinde aslında değer değişmiş oluyor. Bu sadece görsele güncelleme yapmadan öncesi ve sonrasını ifade ediyor. Tabii ki konsoldan değerlere erişmek yerine Geliştirici Araçları Vue sekmesinden de Vue nesnesi ve değişkenlere erişip değiştirmeyi tercih edebilirsiniz. 

Son olarak Vue uygulamamızın yok edildiği ana bakalım.

app7/app.js

  beforeUnmount() {
    console.log("beforeUnmount() olayı tetiklendi.", this.$el)
  },
  unmounted() {
    console.log("unmounted() olayı tetiklendi.", this.$el)
  }

Konsoldan unmount() metodu çağırarak test edelim.

Görselden Vue mesajı yok oldu. Ama gördüğümüz gibi Vue uygulamamız hala yaşıyor, sadece görselden ayırıldı. 



Virtual DOM

Vue HTML görsellerimizi güncellerken Virtual DOM elemanlarını kullanır. Vue uygulama görseli için şablonu derleyip $el elemanını elde ediyordu hatırlarsak. Derlemek kodu bir dilden alıp kullanılabilecek başka bir koda çevirme işidir. Sonuçta tarayıcımız da verdiğimiz JavaScript kodu bilgisayarın anlayacağı koda çeviriyor mesela. Vue de bizim görsel şablonumuzu derliyor.

Bu örnek bir derleme işlemi. HTML kodu JavaScript içinde kullanmak için bir nesneye dönüştürülüyor. Bu derleme işini hem daha kolay görsele hükmedebilmek hem de performansı arttırmak için yapıyor. 

Önceki bölümlerde listeleri işlerken :key diye bir direktif görmüştük. Bu sayede VueJs div elemanlarını birbirine karıştırmıyordu ancak bu tarayıcıda baktığımızda bize görünmüyordu. VueJs işte buna benzer şeyleri yukarıda gösterilen Virtual DOM yapısına derleme yaparak çözüyor. Nasıl yapıyoru bırakalım ustalar bilsin, bana bu kadar yeter. 



Reaktif Çalışma Nasıl Oluyor

Vue uygulamalarında veri değiştiği zaman görsel de değişimi yansıtacak şekilde değişiyor. Arkada Vue Proxy denilen işlemi kullanıyor. Bu sefer uygulamamızda sadece index.html dosyası olacak. app8 klasöründe bu dosyayı şu içerikle oluşturalım. 

app8/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Document</title>
</head>
<body style="background-color: darkseagreen;">
    <p id="mesaj">Merhaba</p>

    <script>
        document.getElementById("mesaj").innerText = "Hello"
    </script>
</body>
</html>

Burada script bölümüne girdiğimiz satır bir HTML sayfasında nasıl içerik değiştirilire bir örnek. Vue de bunları kullanıyor ama nasıl yapıyor adım adım gidelim.

    <script>
        const data = {
            isim: "Ümit"
        }
    </script>

Burada sanki Vue uygulaması gibi data nesnesinde isim adında bir değişken oluşturuyoruz. Şimdi buna yapılabilecek değişimleri takip etmemiz gerekiyor. Programlama işinde buna observed data deniyor. Bir objenin değişikliklerini takip etmenin bir yolu Proxy nesnesi kullanmak. 

        const data = {
            isim: "Ümit"
        }

        const observedData = new Proxy(data, {})

Proxy nesnesi üretilirken ilk parametre takip edilecek olan nesne, ikinci ise olay işleyicileri içerecek nesne şimdilik boş bırakılmış. İlk parametre data nesnemizi ve observedData nesnemizi birleştiriyor. Konsolda test edebiliriz.

Gördüğümüz gibi isim değerini nerede değiştirsek diğeri de değişiyor yani aynı şeyler. Ama observedData başka özelliklere sahip. Mesela değer set edilirken yani yeni değer girilirken çalışacak bir metod tanımlayabiliriz. Bu amaçla Proxy() new metodu ikinci parametresini kullanacağız. 

        const data = {
            isim: "Ümit"
        }

        const observedData = new Proxy(data, {
            set(hedef, özellik, değer){
                hedef[özellik] = değer
            }
        })

Bu standart set yapısı hedef yerine bizde data nesnesi gelir, özellik yerine isim gelir değer de yeni girilen değer. Çalışmasını konsoldan test edelim.

Sistem çalışmaya devam ediyor. Ama artık set() metodu içinde ilaveler yapabiliriz. 

        const observedData = new Proxy(data, {
            set(hedef, özellik, değer){
                document.querySelector("#mesaj").innerText = değer
                hedef[özellik] = değer
            }
        })

Şimdi konsolda deneme yapalım. 

data.isim değerinde yapılan değişiklik görsele yansımazken observedData.isim değerini değiştirdiğimizde görsel reaktif olarak güncelleniyor. İşte VueJs de bu Proxy nesneleri ile verileri takip ediyor. Bunları tek tek elle yazmak baya bir zor olurdu herhalde. Neyse ki bunlarla uğraşmıyoruz VueJs bizim için hepsini ayarlıyor. Burada amacımız nasıl oluyor da oluyor? Sorusunu biraz yanıtlayalım. 



Vue Görsel Şablonları

Yeni bir Vue uygulaması ile başlayalım. app9 oldu klasör adımız. Dosyalar şöyle:

app9/index.html

<!DOCTYPE>
<html>

<head>
  <title>VueJS Öğreniyorum</title>
  <link rel="stylesheet" type="text/css" href="main.css" />
</head>

<body>
  <div id="app">
    {{ mesaj }}
  </div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

</html>

app9/app.js

let vm = Vue.createApp({
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  }
})

vm.mount("#app")

app9/main.css

body{
  font-size: 20px;
  font-family: sans-serif;
  background-color: darkseagreen;
}
.red{
  color: red;
}

Uygulama-7 nin başına döndük. Görsel şablon kullanmak için önce uygulamayı monte ettiğimiz div elemanı içini boşaltıyoruz.

app9/index.html

<body>
  <div id="app"></div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

Daha sonra Vue nesnemize template özelliği ekliyoruz ve div içinde olması gerekenleri bu özelliğe değer olarak veriyoruz.

app9/app.js

let vm = Vue.createApp({
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  },
  template: `{{ mesaj }}`
})

vm.mount("#app")

Test edersek çalışmanın devam ettiğini göreceğiz. E peki farkı ne böyle yapmanın? Ama büyük uygulamalar yaparken Vue uygulamasının HTML kodunu ayrı bir dosyada tutmak isteyebilirsiniz. 




Vue Komponentler

Komponentler VueJs'in çok kullanılan özelliklerinden biri. Sayfamızda ayrı ayrı bölümlerde çalışırken hepsine ayrı uygulama yazmak yerine sayfayı komponent adı verilen bölümlere ayırarak tek uygulama içinde çalışabiliriz. Böylece sayfamızdaki bölümleri yönetmek daha kolay olur. Ayrıca bu komponentleri ayrı dosyalarda saklayarak da hakimiyetimizi arttırabiliriz. Komponentler sadece görsel şablonlar değildir. Verileri, hesaplanmış değerleri ve metodları vs içerebilirler. Örnek vermek gerekirse web sitesi için tüm sayfalarda yer alacak bir header komponenti tanımlayabiliriz, sadece bazı sayfalarda yer alacak gönderileri içeren posts komponenti de olabilir. 

Önceki uygulamamızdaki template olayını eski haline döndürelim. Komponentler uygulamamızı monte etmeden önce tanımlanmalıdır. Bu önemli. Sadece bir mesaj yayınlayan bir komponent tanımlayacağız. Vue uygulaması üretildikten sonra ve monte edilmeden önce component() metodu kullanarak komponentimizi tanımlarız.

app9/app.js

let vm = Vue.createApp({
 
})

vm.component("hello", {
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  }
})

vm.mount("#app")

Vue nesnemizin içini boşalttım , çünkü komponent kullanacağız, ama boşaltmasak da olur amaç kafa karışmasın. component() metodu ilk parametresi komponentin adı. Bu ad için standart olarak camelCase ya da kebap-case isimlendirme önerilir. Komponent de aynı Vue nesnesinin kendisi gibi data , methods , computed vs bölümleri içerebilir. Komponentlerin ayrı mount() komutu olmaz uygulamanın monte edildiği bölüm içine yerleşirler. Ama bir template özelliği belirterek görselimizi burada vermek zorundayız. 

app9/app.js

vvm.component("hello", {
  template: `<h1>{{ mesaj }}</h1>`,
  data() {
    return {
      mesaj: "Merhaba Dünya!"
    }
  }
})

Bu komponenti görselde kullanmak için komponentle aynı isimde elemanları HTML kod içine koyarız.

app9/index.html

  <div id="app">
    <hello></hello>
    <hello></hello>
    <hello></hello>
  </div>

Daha önce aynı uygulamayı 2 bölüme monte edememiştik ama 1 komponent bir sürü bölüm yapılabiliyor. Sayfa şöyle görünür. 

Vue Development Tool'da baktığımızda Root uygulama altında 3 tane hello komponent nesnesi görüyoruz. Bunların her birinin lojiği ayrı çalışır. Denemek için mesela ortadakinin mesaj değerini değiştirelim. 

Yerleştirilen her komponent ayrı bir çalışmaya sahip. Uygulama büyüdükçe böyle komponentlere bölerek uygulamayı yönetmeyi kolaylaştıracağız. Ama işler bu kadar karmaşıklaşmaya başlamışken yeni bir şeylere atlamak gerekiyor. Gelişmiş uygulamalar yapabilmeye yardımcı olmak üzere VueJs geliştiricileri bir araç geliştirmişler ve adına Vue CLI demişler. Bu JavaScript'çiler herşeyi sonunda getirir NodeJs'e dayatırlar. Bakalım bu tool bize neler getirecek?

Vue CLI bize Sass, Webpack ve Babel kabiliyetlerini kullanarak gelişmiş uygulamalar yapmakta kolaylık sağlar. 



Webpack ne yapar?

Kendi sitesinde verilen bu resimde görüldüğü gibi WebpackJs uygulamamızda kodu dağıttığımız bir çok dosyayı birleştirerek yayınlanma öncesi daha az yer kaplar hale getirir. Dosyaların içinde kod görünüşü pek sade olmaz ama performans artar. Hem bazen kodumuzun karmakarışık görünmesini de isteyebiliriz. 

Sol tarafta geliştirme yaparken kullandığımız dosyalar var. Bir çok kütüphane dosyası, resimler stil dosyaları vs. Bunları geliştirme yaparken böyle parça parça yapmak yönetebilmek bakımından faydalı, ancak uygulamayı yayınlarken bir sürü dosya yüklenmesi performansı ciddi ölçüde düşürecektir. 

Nasıl çalıştırıldığını örnek bir uygulama ile görelim. app10 klasörünü oluşturup içine şu dosyaları koyalım.

app10/index.js

import pizza from './pizza'

pizza.pepperoni()
pizza.sucuk()

app10/pizza.js

export default {
  pepperoni: function() {
    console.log('Pepperoni ürünü eklendi!')
  },
  sucuk: function() {
    console.log('Sucuk ürünü eklendi!')
  }
}

Ana dosya olarak kullandığımız bir index.js dosyası var. Bu dosyada pizza.js modülünü import ile kullanıyoruz. pizza.js içinde bir nesneyi modül olarak export ediyoruz. Modülün içinde peperoni ve sucuk adında 2 adet fonksiyon mevcut. Her 2 metod da konsola mesaj yazıyor. index.js dosya içinde bu 2 fonksiyonu çağırıyoruz. Basit bir uygulama yapısı. Siteyi yayınlarken bu iki dosyanın birleşmesi performansı arttıracaktır. İyi bilinen bir şeydir, 2 tane 10kB dosya yerine 1 tane 20kB dosya indirmek daha az zaman alır. 

Webpack kurulumu yaparak başlayalım. VSCode editörümde Terminal -> new Terminal menü seçeneğiyle bulunulan klasörde bir PowerShell penceresi açabiliyorum.  Gerekli komutları buradan girebilirim. Önce bir NodeJs kontrolü

npm init -y

Bu komut Node Package Manager'i klasörde aktif eder bu komutla beraber klasörümüzde package.json adında bir yeni dosya oluşur.

app10/package.json

{
  "name": "app10",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

İçinde bazı özellikleri olan bir nesne tanımlanmış. Şimdilik kalsın, tekrar konsola gidelim. 

npm install webpack webpack-cli --save-dev

Bu komut webpack kurulumu yaptı ve klasörümüze node_modules adında bir klasör eklendi. Bunlar da webpack çalışması için gerekecek dosyalar. package.json dosya içeriği de değişti.

app10/package.json

{
  "name": "app10",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.70.0",
    "webpack-cli": "^4.9.2"
  }
}

devDependencies özelliği webpack kurulumu esnasında dosyaya eklendi. webpack ile çalışırken 2 ayarlama yapmamız gerekiyor. webpack'e dosyalarımızı nerede bulacağını göstermeliyiz ve çıktıyı nereye koyacağını göstermeliyiz. Öncelikle app10 klasörümüze webpack.config.js adında yeni bir dosya ekliyoruz. webpack çalıştırılınca bu dosyayı arar ve içinden ayarlarını öğrenir. 

app10/webpack.config.js

module.exports = {
    entry: "./index.js",
    output: {
        filename: "bundle.js",
        path: __dirname + "/dist"
    }
}

Dosya bir modül içinde bir nesne export ediyor. entry özelliği webpack çalışınca işlenmeye başlayacak dosyayı gösteriyor, bu da bizim index.js dosyamız olacak tabi ki. Çıktı için ise 2 şey belirtmek gerekiyor, biri dosya adı ki buna standart herkesin kabul ettiği bir isim olan bundle.js adını verdik. 2. parametre ise dosyanın konacağı tam path, bunu elde etmek için bulunulan klasörü NodeJs sistem değişkeni olan __dirname ile aldık. Bu durumda çıktı app10 klasörümüzün altında yer alan dist klasöründe bundle.js adıyla üretilecek. 

Konfigürasyon hazır , öyleyse webpack'i terminalde çalıştıralım. 

./node_modules/.bin/webpack

Komut dist klasörünü ve içinde bundle.js dosyasını oluşturur. bundle.js içine bakarsak baştaki 2 js dosyamızın birleştirilip sıkıştırılmış halini buluruz. 

app10/dist/bundle.js

(()=>{"use strict";console.log("Pepperoni ürünü eklendi!"),
console.log("Sucuk ürünü eklendi!")})();

Terminalde bu kompleks komutu kullanacağımıza webpack için package.json dosyasında bir script ilavesi yapabiliriz. 

app10/package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack"
  },

Artık start komutu webpack çalıştıracak. Burada uygulamanın bulunduğu alt klasörü göstermedik çünkü NodeJs zaten onu orada arayacak. webpack komutu çalıştırırken çalışma ya da geliştirme modu da belirtebiliriz. Bir şey belirtmedik çalışma modunda işlem yaptı ve en küçük boyutlu çıktıyı elde etti. start komutunun geliştirme modunda çalışması için satırı şöyle değiştirelim.

app10/package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --mode=development"
  },

build adında bir komut daha ekleyip onda da çalışma modunu aktif edelim.

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --mode=development",
    "build": "webpack --mode=production",
  },

Test edelim. Terminalde 

npm run start

start komutu geliştirme modunda çalıştığı için şimdi üretilen bundle.js dosyası öncekine nazaran çok ayrıntılı olacaktır. 

Şimdi bu elde attiğimiz çıktı dosyası iş yapıyor mu test edelim. Bu amaçla app10 klasörümüze index.html adında bir dosya ekleyelim. 

app10/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Document</title>
</head>
<body>
    <script src="dist/bundle.js"></script>
</body>
</html>

Tarayıcıda dosyayı açarsak konsolda log yazılarını görürüz. 



Babel Ne Yapar?

Babel modern JavaScript kodlarının çalışmadığı bazı tarayıcılarda çalışmasını sağlayan araçtır. Biz kodlarımızı en yeni JavaScript teknikleri ile yazıyoruz. Bunların eski tarayıcılarda da çalışmasını istiyorsak Babel kullanırız. Terminalde şunları yazarak küçük uygulamamıza Babel eklemesi yapalım.

npm install --save-dev @babel/core @babel/preset-env babel-loader       

En sondaki loader diğer özelliklerle birlikte kullanmaya yarıyor, mesela bizde webpack. Babel'i Webpack ile birlikte kullanmak için Webpack konfigürasyon dosyasını açalım.

app10/webpack.config.js

module.exports = {
    entry: "./index.js",
    output: {
        filename: "bundle.js",
        path: __dirname + "/dist"
    },
    module: {
        rules: [
            {
                test: /\.js%/,
                exclude: /(node_modules)/,
                use: "babel-loader"
            }
        ]
    }
}

module özelliğini ekledik. Bu özellik içinde rules özelliğinde nesne array şeklinde webpack işletildiğinde çalıştırılacak ilave modülleri belirtiyoruz. test özelliği Webpack'e JavaScript dosyalarına bu kuralın uygulanacağını belirtiyor. exclude özelliği ise node_modules klasörü içindeki dosyalarla uğraşma onlar dahil değil diyor. use özelliği de bu kurala uyan dosyalar için babel-loader aracını kullan diyor. 

Bu kısım Webpack içindi yapılacaklar bitmedi. .babelrc adında bir dosya eklememiz gerekiyor klasöre. Bu dosya Babel için konfigürasyon dosyası.

app10/.babelrc

{
    "presets": [
        "@babel/preset-env"
    ]
}

preset olarak yüklediğimiz preset-env kullanılacağını belirtiyor. Bu kadar, şimdi test edelim. Terminalde:

 npm run start                                       

Sayfayı yenileyip konsola baktığımızda log mesajlarının hala sorunsuz geldiğini görürüz. Uygulamamıza Babel eklemesi de gerçekleşti. 



Sass Kabiliyeti Eklemek

Sass stilleri düzenlemekte kullanılan CSS gibi ama daha kabiliyetli bir kodlama dilidir. Web geliştirmede çok kullanılır, ancak henüz tarayıcılar tarafından desteklenmiyor. Bu yüzden Sass ile yazılan stiller CSS'e dönüştürülerek kullanılır. Webpack kullanırken Sass kodunu CSS koduna çevirebiliriz. Sass kullanabilmek için gereken NodeJs modüllerini yüklemek için terminalde:

 npm install node-sass sass-loader css-loader --save-dev      

İlki Sass dosyaları CSS'e çevirebilmek için. Loader'lar ise Webpack çalıştığında Sass ve CSS dosyaları da paketleyebilsin diye. Webpack konfigürasyonu ile başlayalım.

app10/webpack.config.js

        rules: [
            {
                test: /\.js%/,
                exclude: /(node_modules)/,
                use: "babel-loader"
            },
            {
                test: /\.scss$/,
                use: [
                    "css-loader", "sass-loader"
                ]
            }
        ]

Bir kural daha ekledik. Sass dosyaları scss uzantılı olur. Kullanılacak işlem olarak ise önce sass-loader sonra css-loader çalışması için sıralamayı görüldüğü gibi verdik. Artık uygulamamızda Sass kodu kullanabiliriz. Klasörümüze main.scss adında bir dosya ekleyelim. 

app10/main.scss

h1 {
    color: red;
}

Bu dosyanın varlığından Webpack nasıl haberdar olacak? Konfigürasyonda entry olarak belirtebilir ya da JavaScript dosyasında import edebiliriz. index.js dosyasında import kullanalım.

app10/index.js

import pizza from './pizza'
import "./main.scss"

pizza.pepperoni()
pizza.sucuk()

Bu import satırı JavaScript için bir şey ifade etmiyor çünkü o dosyada JavaScript olarak export edilen bir şey yok. Ama Webpack index.js dosyasını işlerken bu import satırını görünce bunu bir bağımlılık dosyası (dependency) olarak değerlendirir ve onu da verilen kurallara göre işleme alır. Hadi paketlemeyi çalıştıralım.

 npm run start                                       

Yazılanlara bakınca Webpack main.scss dosyasını işlemiş görünüyor. Ama CSS dosyası nerede? Sadece bundle.js dosyası üretildi. Açıp içine bakalım. Burada araştırdığımızda main.scss dosyasından import edilen CSS'e dair izler buluruz. Webpack , Sass kodunu JavaScript gibi işleyip sonuç bundle.js dosyasına dahil etmiş. Ancak tarayıcı bundan anlamaz, CSS dosya ister. Denemek için index.html görseline bir <h1> elemanı ekleyip test edelim.

app10/index.html

<body>
    <h1>Merhaba!</h1>
    <script src="dist/bundle.js"></script>
</body>

Sayfayı yenilersek yazının kırmızı renk olmadığını görürüz. 

Webpack'in stillleri yüklemesi için 2 yol var. Birinde direk görsele satır ekliyor, diğerinde ise bundle.js gibi stil için de bir dosya oluşturuyor. Bunları görmeden önce stilller için yüklenmesi gereken loader var. Terminalde:

 npm install style-loader --save-dev            

Bu loader'ın Sass dosyaları işlerken en son devreye girmesini istiyoruz. Webpack konfigürasyon dosyasını açalım.

app10/webpack.config.js

            {
                test: /\.scss$/,
                use: [
                    "style-loader", "css-loader", "sass-loader"
                ]
            }

Sondan başa doğru çalışıyorlardı hatırlarsak. Tekrar Webpack çalıştıralım.

 npm run start                                       

Sayfayı yenilersek yazı kırmızı olur. index.html içinde bir değişiklik olmadığı halde sayfaya Geliştirici Araçlarında bakarsak <head> kısmına bundle.js tarafından bir <style> elemanı eklendiğini görürüz.

Ayrı bir CSS dosyası üretilmesi işi ise bir Plugin sayesinde yapılır. mini-css-extract-plugin kullanacağız. Terminalde :

 npm install mini-css-extract-plugin --save-dev       

Plugin loader'lara göre daha fazla konfigürasyon ister. Konfigürasyon dosyamızın başına şunları ekleyeceğiz. 

app10/webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    entry: "./index.js",
...

Sonra module.export sonuna plugins adında bir özellik içinde array olarak plugin tarifini yaparız.

app10/webpack.config.js

...
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'main.css'
        })
    ]
}

Sonra da style-loader yerine bu plugin loader'ını kullanmasını isteyelim.

app10/webpack.config.js

            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader, "css-loader", "sass-loader"
                ]
            }

Artık Webpack çalışmaya hazır. Terminalde:

 npm run start                                       

dist klasörü altında main.css dosyamızda üretilecektir. Şimdi index.html içinden bu stili de sayfaya dahil edelim.

app10/index.html

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Document</title>
    <link rel="stylesheet" href="dist/main.css">
</head>

Sayfayı tazelediğimizde artık stil olarak main.css dosyasının kullanıldığını görürüz.



Neden Sass Kullanılır?

Burada ayrıntıya pek girmeden neden Sass kullanılmaya başladı bir bakalım. Webpack çalışmasını Sass kullanacak hale getirdik, ama şu andaki stil dosyamızın standart CSS'ten farkı yok. Sass bize stil dosyalarında değişken kullanmaya, döngüler yapmaya, içiçe elemanları daha rahat göstermeye vb. bir çok gelişmiş özelliğe imkan sağlar. Mesela kullanılan her türlü CSS özelliğini değişken olarak verebilme imkanı sağlar. 

app10/main.scss

$kırmızı: #cc3244;

h1 {
    color: $kırmızı;
}

Sass değişken isimleri $ işareti ile başlamalıdır, atama için : sembolü kullanılır. Burada biraz değişik bir kırmızı renk tanımı yaptık. Terminalde npm start run deyip sayfayı yenilersek yeni rengin aktif olduğunu görürüz. Ayrıca dist/main.css dosyası da değişkenli değil standart CSS kodlu üretilir, çünkü tarayıcı CSS içinde değişkenleri bilmez. 

Webpack yapılanmasında bir değişiklik yaparak işleyeceği dosyalarda yapılan değişiklikleri takip etmesini sağlayabiliriz. Bunun için export nesnemize bir özellik daha ekleriz.

app10/webpack.config.js

...
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'main.css'
        })
    ],
    watch: true
}

Şimdi terminalde npm run start girersek komut satırına geri dönmez, Webpack işleyici çalışmaya devam ederek dosyalarda yapılacak bir değişikliği bekler. 

main.scss dosyası içinde $kırmızı değerini değiştirip dosyayı kaydedersek terminalde dosyanın hemen işlendiğini görürüz. Her değişiklikte npm run start yazmaya gerek kalmadı. Değişikliği yapıp tarayıcıda sayfa yenilemek yeterli artık. 

Fonksiyon kullanımını görmek için stil dosyamıza şunu ekleyelim:

app10/main.scss

....
h1:hover {
    color: darken($kırmızı, 25%);
}

darken() metodu bir Sass fonksiyonu ve verilen rengi verilen oranda karartıyor. Buradan beklentimiz mouse üzerine gelince daha koyu bir kırmızı yazı olması. Dosyayı kaydedip sayfayı yenileyelim ve test edelim. 

Sass'ın en önemli özelliklerinden birisi, içiçe elemanlarda stil yazabilme kolaylığı. Örnek verelim

app10/main.scss

h1 {
    color: $kırmızı;

    span {
        color: blue;
    }
}

h1 elemanı içindeki bir span elemanı. Bunu normal CSS ile ifade etmeye kalksak şöyle olmalıydı.

h1 {
  color: #cc3244;
}
h1 span {
  color: blue;
}

Sass kodunda anlatılan net bir şekilde görülüyor, ama standart CSS kodunda hayalimizde canlandırmak gerekiyor. Çalışmasını test için sayfamıza ilave yapalım.

app10/index.html

    <h1>Merhaba <span>Dünya</span>!</h1>

Sayfayı yenileyelim.

h1:hover stili de h1 stili içine eklenebilir. Bunu yaparken & işareti kullanırız.

app10/main.scss

$kırmızı: #cc3244;

h1 {
    color: $kırmızı;

    span {
        color: blue;
    }

    &:hover {
        color: darken($kırmızı, 25%);
    }
}

İçiçe elemanlar gelişmiş HTML kodlarının çok kullanılan yöntemleri. Bunların stillerine de içiçe stiller şeklinde hakim olabilmek güzel bir şey. 

Bir önemli özellik daha var. Bir çok CSS dosyası ile çalıştığınızda sayfa kodunda her birini tek tek çağırmanız lazım. Ama Sass kullanıp import satırları ile stilleri dahil ederseniz derlenirken tek bir stil dosyası olarak derlenir. Mesela değişken tanımlarını başka bir dosyaya alalım. variables.scss adında bir dosya daha ekleyelim klasöre.

app10/variables.scss

$kırmızı: #cc3244;

ve main.scss dosyasından değişken tanımını silip yerine import satırı koyalım.

app10/main.scss

@import "variables";

h1 {
....

Dosya uzantısı vermezsek direk .scss uzantı olduğu kabul edilir. Sayfayı yenilediğimizde hala eskisi gibi çalıştığını görürüz. Ancak 2 tane scss dosyası olmasına rağmen tek bir css dosyasına birleştirdi. 

Bu kadar sıkıntı yeter. Bunlardan başka Vue geliştirirken kullanılan PostCSS ve ESLint araçları da var. Şükür ki bunları tek tek bilmek zorunda değiliz. Şimdi Vue-CLI (Command Line Interface) öğrenme zamanı.



Vue-CLI Nedir?

Vue CLI kullanarak az önce gördüğümüz bir sürü araçları kullanabilen Vue uygulamalarını üretebilir ve kolayca yapılandırabiliriz. Vue CLI kurulumu için terminalde :

  npm install -g @vue/cli     

Bu komut Vue CLI aracını global olarak kuracaktır. Böylece sistemimizde her yerde aracı kullanabiliriz. Çalıştığını görmek için terminalde vue yazıp enter basalım.

Burada nasıl yeni proje oluşturulur vs olası komut satırı opsiyonları kısaca gösteriliyor. Yeni uygulama üretmek için önce uygulamanın bir üst klasöründe terminali açmalıyız, çünkü uygulama klasörünü Vue CLI oluşturacak. Örnek uygulamammızda komponentleri göreceğiz uygulama adımız komponentler olsun. Terminalde :

 vue create komponentler      

satırını girdiğimizde bize sorula sormaya başlar. 

Burada eski versiyon ya da manual seçim opsiyonları da var biz zaten seçili gelen Vue 3 seçip enter ile devam edelim.

Eğer hangi package manager'ı kullanacağımızı sorarsa şimdiye kadar kullandığımız npm seçelim. komponentler klasörü oluşturulup içine bir sürü gerekli dosya yerleştirilir. 

Son satırlarda bize ne yapmamız gerektiğini yazmış. Terminalde önce klaöre gireceğiz sonra da server komutu çalıştırıp uygulamamızı yayınlayacağız. Terminalde :

 cd komponentler    

 npm run serve         

satırlarını girdiğimizde uygulamamız lokal olarak yayınlanmaya başlar.

Artık tarayıcıda localhost:8080 adresine gidersek uygulamamızın giriş sayfasını görebiliriz. Network yanında verilen adresten de aynı ağa bağlı başka bir PC ya da cep telefonundan da uygulamaya bakabiliriz. 

Oluşan uygulama klasöründeki dosyalar bir göz atalım. Vue CLI bizim için bir çok şeyi hazırlamış. Bazı dosyaları önceki bölümlerden hatırlıyoruz. Tebrikler artık Vue CLI kullanmaya başladık. Hem uygulamamızı server üzerinden yayınlanmış halini test edebiliyoruz. Sonuçta web uygulaması dediğin bilgisayardaki bir HTML dosyası değil bir server tarafından internette yayınlanan bir sitedir. Ayrıca Vue CLI tarafından kullanılan bu server uygulamamızda olan değişiklikleri de takip eder. .Kapatıp açmaya gerek kalmaz. 

2 tane klasör var bakacağımız. public klasöründe uygulamamızın görselleri var. Bir favicon dosyası ki içinde VueJs ikonu var, bir de index.html ise ana görselimiz. İçine bakalım meşhur div elemanımız orada duruyor. src klasörü source (kaynak) dosyalarının olduğu klasör. Bu klasör uygulamamızın hemen tüm lojiğini, görsel şablonlarını, yardımcı dosyalarını içeren klasör. Vaktimizin çoğunu burada geçireceğiz. 

Ana JavaScript dosyamız main.js dosyamıza bakalım.

src/main.js

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Vue paketinden createApp fonksiyonunu import ediyoruz. İkinci satırda ise bir JavaScript dosyasından import yapmıyoruz, uzantısı .vue olan bir dosyayı çekiyoruz. .vue dosyaları komponentler şeklinde tasarlanmıştır. App.vue dosyası içindeki komponent kullanarak bir uygulama üretilmiş ve görsele monte edilmiş. Bakalım ne var içinde.

src/App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>

<template> , <script> ve <style> bölümlerinden oluşuyor. Eğer VSCode ile dosyayı renklendirilmiş göremiyorsanız Vetur extension kurarsanız renklenir. Bu komponent dosyaları standart olarak görsel, script ve stil bölümlerinden oluşur. Bu 3 bölüm derlenirken JavaScript nesnelerine dönüştürülür. 

Görsele bir şey ekleyip bakalım sayfaya gelecek mi?

src/App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <h1>Merhaba!</h1>
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

Sayfayı yenilemeye bile gerek kalmadı görsele yaptığımız ilave geldi

Burada örnek olarak nasıl komponent yapılır ve yayınlanır küçük bir örnek olmuş ama biz kendi uygulamamızı yapmak için temizlik yapalım biraz. 



İlk Vue CLI uygulamamız

components ve assets klasörlerini komple silerek başlayalım işe. Sonra App.vue içinde <style> bölümünü silelim. <script> ve <template> bölümlerini de içini boşaltalım.

src/App.vue

<template>

</template>

<script>

</script>

<template> içinde görsel şablon belirtmek için en az bir eleman koymak zorundayız. Basit bir merhaba diyelim.

src/App.vue

<template>
  <p>Merhaba!</p>
</template>

Bu kadar yeterli tarayıcıya mesaj gelecektir. Buna dinamiklik verebilmek için <script> bölümünde bir nesne export etmeliyiz. Bu nesne data, methods vs içerir. 

<template>
  <p>{{ mesaj }}</p>
</template>

<script>
  export default {
    name: "App",
    data() {
      return {
        mesaj: "Merhaba Dünya!"
      }
    }
  };
</script>

name özelliği ile komponente isim veriyoruz, zorunlu değil ama debug yaparken işe yarar. data() metodu bildiğimiz Vue data() metodu, içinde değerler var. mesaj adında bir değişken tanımladık. Bu değeri de paragraf içinde gösterdik.

Sayfayı açıp Vue DevTools'ta bakarsak komponenti verdiğimiz App ismiyle göreceğiz. 




Alt Komponent Dosyaları

Düzenli olmak için alt komponentleri ayrı bir klasör içinde bulundurmak iyidir. src klasörü içinde components adında bir alt klasör tanımlayalım. Bu klasör içinde Selam.vue adında yeni bir komponent dosyası ekleyelim. VSCode kullanarak dosyayı açarsak vue yazdığımızda bize olası örnek kodları sunar.

İlk seçeneği seçip boş bir komponent içeriği oluşturalım.

src/components/Selam.vue

<template>
 
</template>

<script>
export default {

}
</script>

<style>

</style>

<style> bölümünü silelim , kullanmayacağız. Şablon olarak App komponenti içindeki <p> elemanını kopyalayalım. App içindekini de silelim. <script> bölümü içini de kopyalayıp sadece komponent adını değiştirelim. 

src/components/Selam.vue

<script>
  export default {
    name: "Selam",
    data() {
      return {
        mesaj: "Merhaba Dünya!"
      }
    }
  };
</script>

Selam komponentinin tanımlamasını bitirdik. Şimdi bunu App komponenti içine çağıralım. App komponentine dönelim. Önce data() metodunu silelim gerek kalmadı.

src/App.vue

<template>
 
</template>

<script>
  export default {
    name: "App",
  };
</script>

Bir komponenti dahil etmenin 2 yolu var. Global veya Lokal olarak komponenti dahil edebiliriz. Global olarak dahil etmek için component() metodu kullanılır. Bunun daha önce benzerini yapmıştık. main.js dosyamızı açalım. Hatırlarsak component() metodunu kullanmak için createApp() ve mount() arasındaki zinciri ayırmış araya girmiştik. 

src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import Selam from './components/Selam.vue'

const vm = createApp(App)
vm.component('Selam', Selam)
vm.mount('#app')

Fakat bu şekil kullanım sorun çıkarıyor lokal yöntem kullanmak tavsiye edilir. main.js eski haline dönelim. 

src/main.js

import { createApp } from 'vue'
import App from './App.vue'

const vm = createApp(App)
vm.mount('#app')

Lokal komponent ilavesi ise mevcut komponent içinede yapılabilir. Şu anda sahip olduğumuz tek komponent App.vue dosyasındaki ana komponent. Burada export nesnesine components özelliği ile yeni komponent kaydı yapılır. Bu özelliğin değeri içerilen komponentlerin bir listesini içeren JAvaScript nesnesidir. 

src/App.vue

<template>
  <Selam></Selam>
</template>

<script>
  import Selam from "./components/Selam.vue";

  export default {
    name: "App",
    components: {
      Selam: Selam
    }
  };
</script>

Önce import ile dosyayı çekiyoruz, sonra export nesnesine ekliyoruz, en son da şablon içinde komponenti kullanıyoruz. Burada components özelliği içinde key ve value değerleri aynı olan Selam: Selam yerine tek bir Selam yazma imkanı da var JavaScript izin veriyor.

    components: {
      Selam
    }

Test etmeye kalkınca bende ESLint bir hata verdi, büyük ihtimal sizde de verecek. 

7:11  error  Component name "Selam" should always be multi-word  vue/multi-word-component-names

Default olarak Vue komponent isimleri çok kelimeli olmalıymış. Bu özelliği kapatmaya uğraşmayacağım gidip komponenti export ederken verdiğim ismi değiştireyim.

src/components/Selam.vue

<script>
  export default {
    name: "SelamKomp",
    data() {

Artık hatasız çalıştı. Vue DevTools'da komponentlerimizi görebiliriz.

App komponenti altında SelamKomp komponenti bir alt komponent olarak yer almış. Komponentin sağlıklı alındığını görmek için Vue DevTools yararlı bir araç. 




Vue Komponentlere Stil Eklemek

Sıra geldi daha önce kalabalık etmesin diye sildiğimiz <style> bölümüne. Selam.vue komponentinde en sona stili ekleyelim.

src/components/Selam.vue

...
</script>

<style>
  p {
    color: red;
  }
</style>

Mesajımızın rengi kırmızıya dönecektir. Yalnız bir eksik var. Sayfaya komponent dışında başka bir paragraf eklersek o da kırmızı olur. 

src/App.vue

<template>
  <Selam></Selam>
  <p>Selam</p>
</template>


Halbuki komponent içinde tanımladığımız stilin o komponentte geçerli olmasını isteriz. Bunu sağlamanın 2 yolu var. İlki bildiğimiz id ve class teknikleri ile CSS seçicilerle ayrı görsel yapmak, bu meşakkatli bir iş. Vue bize tarayıcıların Shadow DOM dedikleri bir tekniği kullanarak kolay bir çözüm verir. Sadece <style> öğersine scoped özelliği ekleriz. 

src/components/Selam.vue

<style scoped>
  p {
    color: red;
  }
</style>

Sayfaya bakarsak sorun çözüldü. Geliştirici araçlarında elemanı incelediğimizde VueJs'in görsele bir özellik eklediğini ve sonra da buna göre stil eklendiğini görürüz. Yani id class ile bizim yapacağımızı otomatik kendi yapıyor, gözümüzün önünde kod kalabalığı olmuyor. 




Vue Projesinde Sass Kullanımı

Şimdiye kadar projemizde CSS stiller kullandık. Sass kullanmaya başlayalım. Şu anda projemiz Sass kullanacak halde değil. Server çalışıyorsa Ctrl+C ile durduralım. Terminalde :

 npm install node-sass sass-loader --save-dev     

Komponent stilimize gidip kullandığımız dilin Sass olduğunu belirtmemiz gerekiyor.

src/components/Selam.vue

<style scoped lang="scss">
  p {
    color: red;
  }
</style>

Hemen bakalım Sass aktif mi?

src/components/Selam.vue

<style scoped lang="scss">
  $kırmızı: red;
  p {
    color: $kırmızı;
  }
</style>

Server'ı çalıştırıp sayfayı açınca Sıkıntısız çalıştığını göreceğiz. Sass stilimiz işlenip CSS stile dönüştürülmüş. Biz müdahale etmeden ufak dokunuşlarla uygulamamızı Sass stil kullanacak hale getirdik. 




Vue Komponentler Arası İletişim

Komponentler içindeki verilere diğerleri nasıl erişecek. Aynı isimde değişkenler varsa nasıl değerlendirilecek? 

Şekile bakarsak 3 komponentimiz var. A komponenti B ve C komponentlerinin üst komponenti. A komponenti ayrıca Root komponent olarak adlandırılır. B ve C komponentleri sadece A komponentinin alt komponentleri değil aynı zamanda birbirlerinin kardeş komponentleri (Siblings). 

Selam adında bir alt komponentimiz var. Kardeş olarak User adında bir komponentimiz daha olsun. User komponentinde yaş adında bir verimiz olsun ve diyelim yaş değeri 20'den küçükse ona selam vermek istemiyoruz. Bu yaş değerini Selam komponentine nasıl ileteceğiz? Kardeş komponentler arasında veri iletişimi VueJs'de mevcut değil. Üst komponent veriyi alt komponentlerle paylaşabilir. Bu durumda şöyle bir şekil düşünmek gerekiyor. 

Üst komponentler veriyi alt komponentlerine gönderme kabiliyetine sahipler. Bu durumda paylaşılması gereken verileri üst komponentte saklamak gerekiyor. 



Vue props Kullanımı

User komponentinin oluşturulması ile başlayalım.

src/components/User.vue

<template>
  <p>Kullanıcı x yaşında</p>
</template>

<script>
export default {
    name: "UserKomp"
}
</script>

Şimdilik bu kadar. App.vue komponenti içinde kaydını yapalım.

src/App.vue

<template>
  <Selam></Selam>
  <User></User>
</template>

<script>
  import Selam from "./components/Selam.vue";
  import User from "./components/User.vue";

  export default {
    name: "App",
    components: {
      Selam,
      User
    }
  };
</script>

Sayfamız şöyle görünür

Komponent görsellerini şablona yerleştirirken isimlerine büyük harfle başlamak zorunda değiliz. İstersek küçük harfle de başlayabiliriz. Bu HTML kodlama için daha iyi standart tag gibi olur.

<template>
  <selam></selam>
  <user></user>
</template>

Şimdi App komponentinde yaş değerini tanımlayalım.

src/App.vue

  export default {
    name: "App",
    components: {
      Selam,
      User
    },
    data() {
      return {
        yaş: 20
      }
    }
  };

Bu değeri User alt komponentine göndermek için şablonda yerleştirirken özellik olarak ekleriz. 

src/App.vue

<template>
  <selam></selam>
  <user :yaş="yaş"></user>
</template>

Özelliğe aynı ismi verdik istesek başka isim de seçebiliriz. User komponentimizin bu gelen değeri aldım kabul ettim deyip bir yere koyması lazım. Bunu da komponent tanımında props özelliği ekleyerek yaparız.

src/components/User.vue

export default {
    name: "UserKomp",
    props: [
        "yaş"
    ]
}

props özelliği bir array şeklinde verilir ve içinde bu komponente yukarıdan gönderilecek değerlerin isimlerini içerir. Şablonda bu değeri kullanalım.

src/components/User.vue

<template>
  <p>Kullanıcı {{ yaş }} yaşında</p>
</template>

Sayfayı yenilersek

Değer yukarıdan aşağıya gönderildi. Vue DevTools kullanarak App komponentinde yaş değerini değiştirirsek görselin dinamik olarak değiştiğini görürüz. Fakat tersi olmaz, yani User komponent içinde yaş değerini değiştiremiyoruz. 

Şimdi Selam komponentinde de yaş değerini kullanalım. Önce App komponentinden aşağı doğru yaş değerini gönderelim. 

src/App.vue

<template>
  <selam :yaş="yaş"></selam>
  <user :yaş="yaş"></user>
</template>

Selam komponenti içinde de yaş değerini props ile alalım.

src/components/Selam.vue

<script>
  export default {
    name: "SelamKomp",
    data() {
      return {
        mesaj: "Merhaba Dünya!"
      }
    },
    props: ["yaş"]
  };
</script>

Aldığımız değeri görselde kullanalım.

src/components/Selam.vue

<template>
  <p v-if="yaş > 25">{{ mesaj }}</p>
  <p v-else>Bu mesajı görmek için 25'ten daha büyük yaşta olmalısınız.</p>
</template>

İlk paragraf eğer yaş değeri 25'ten büyükse görünecek, değilse ikinci paragraf görünecektir. Sonuç:

Uygulamada şu anda kullanıcının yaş değerini değiştirebileceği bir şey eklemedik. Bu yüzden hep ikinci paragraf görünecektir. App komponenti içinde bir buton ekleyerek yaş değerini arttırmasına imkan verelim.

src/App.vue

<template>
  <button @click="yaş++">Yaşı Arttır</button>
  <selam :yaş="yaş"></selam>
  <user :yaş="yaş"></user>
</template>

Butona tıklaya tıklaya yaş değerinin 25'i geömesini sağladığımızda ilk paragraf görünecektir.

Ancak bu butonun alt komponentlerden birinde olmasını istersek ne olacak? 

src/components/User.vue

<template>
  <button @click="yaş++">Yaşı Arttır</button>
  <p>Kullanıcı {{ yaş }} yaşında</p>
</template>

Bu değişiklik bir hata mesajı gelmesine neden olur. 

komponentler\src\components\User.vue

  2:19  error  Unexpected mutation of "yaş" prop  vue/no-mutating-props

Bunun sebebi alt elemanda yukarıdan gelmiş bir değerde yaptığımız değişikliği üst elemana bildirmemiz lazım. Kardeş Selam komponentine değerin değiştiğini nasıl haber vereceğiz?




Olay Yayınlama (Emitting)

Alt komponente props ile gönderilen değeri nasıl değiştireceğiz demiştik. Bu amaçla alt komponentten butona basılınca bir olay tetikleyeceğiz. 

src/components/User.vue

<template>
  <button @click="onClickYaş">Yaşı Arttır</button>
  <p>Kullanıcı {{ yaş }} yaşında</p>
</template>

<script>
export default {
    name: "UserKomp",
    props: [
        "yaş"
    ],
    methods: {
        onClickYaş() {
            this.$emit("yaş-değişti")
        }
    }
}
</script>

this.$emit("yaş-değişti") satırı User komponentinden yaş-değişti adında bir olay yayınlaması yapar. Bu yayını App üst komponentinden takip edip işleyebiliriz. Bunun standart JavaScript olaylarını izlemekten farkı yok.

src/App.vue

<template>
  <selam :yaş="yaş"></selam>
  <user :yaş="yaş" @yaş-değişti="yaş++"></user>
</template>

Türkçe karakterlerden renkler biraz abuk oluyor. Test edersek alt komponente bulunan buton ile üst komponente olay bildiriminin çalıştığını göreceğiz. 

Bir adım öteye gidelim. Alt komponentten olay bildirirken bir de parametre ile bildirsin. 

src/components/User.vue

        onClickYaş() {
            this.$emit("yaş-değişti", 3)
        }

Bu şekilde yaş-değişti olayını parametrede 3 değeri ile bildiriyoruz. Bu durumda üst elemanda parametreyi alabilmek için bir metod çağırırız. 

src/App.vue

<template>
  <selam :yaş="yaş"></selam>
  <user :yaş="yaş" @yaş-değişti="yaşDeğiştir"></user>
</template>

<script> bölümüne de metod ilavesi yapalım.

src/App.vue

    data() {
      return {
        yaş: 20
      }
    },
    methods: {
      yaşDeğiştir(num) {
        this.yaş += num
      }
    }

Bu metoda göre User komponentinde her buton tıklanma olayında yaş değeri 3 artacaktır. Test edip görelim.

Bu esnada konsolu açarsak VueJs'in bize warning mesajları verdiğini görürüz.

Bu mesaj alt komponentten nelerin emit edileceğini bildirmemizi istiyor. Bunun için export nesnesine emits özelliği eklemeliyiz. Burada bir array içinde bildirilecek olayları listeleriz.

src/components/User.vue

<script>
export default {
    name: "UserKomp",
    emits: ["yaş-değişti"],
    props: ["yaş"],




prop'la Gelen Değeri Kontrol Etmek

Karmaşık uygulamalarda hata yapmamak adına üst komponentten gelen değeri kontrol etmek gerekebilir. Mesela takım çalışmalarında komponentleri başka yazılımcılar yazıyorsa bu çok daha önem kazanır. 

Bizim uygulamamızda yaş değeri prop ile geliyor. Bu değerin sayı olmasını istiyoruz. Bu amaçla props özelliğine array yerine bir nesne veririz. 

src/components/User.vue

    props: {
        yaş: {
            type: Number,
            required: true
        }
    },

Değişken adına ait bir nesne tanımlarız. Bu nesne içinde kurallar yazarız. type özelliği her hangi bir JavaScript veri tipi kullanılabilir. Bizde type Number olmalı. Birden fazla veri tipi kabul edilebilirse bir array içinde verilmelidir. required özelliği bu prop değerinin mutlaka girilmesi gerektiğini belirtiyor, çünkü değeri true

Hadi test için üst komponentten bu prop değerini vermeyelim. 

src/App.vue

<template>
  <selam :yaş="yaş"></selam>
  <user @yaş-değişti="yaşDeğiştir"></user>
</template>

Sayfa yenilenince konsolda VueJs tarafından gelen bir uyarı göreceğiz.

Tekrar prop değerini geri koyalım. Şimdi de type özelliğinde girdiğimiz kuralı test edelim. Vue Devtools üzerinden App komponenti yaş değerine mesela "test" yazalım. Sayı yerine yazı girince konsolda hata gelecektir.

Diyor ki Number bekliyorduk String geldi. yaş için yapabileceğimiz bir şey daha var. Default değer verebiliriz. 

src/components/User.vue

        yaş: {
            type: Number,
            default: 20
        }

Tabi ki default değer olunca required gereksiz oldu, değer girilmez se 20 kabul edilecek. 

Bir de validator var. validator ile true ya da false değer dönen bir fonksiyon ile değeri test ederiz.

src/components/User.vue

        yaş: {
            type: Number,
            validator(value) {
                return value < 130
            }
        }

yaş değeri 130'dan küçük değilse konsola bie hata mesajı gelecektir. 

Üst komponentteki veriyi değiştirmenin bir yolu daha var Call Back fonksiyonlar. Ama şimdilik bu yazıyı yayınlayalım çünkü çok uzadı, bir sonraki yazıda buluşmak üzere kalın sağlıcakla..

Sonraki Bölüm 


Hiç yorum yok:

Yorum Gönder