9. Örnek [nuxt-06]: oturum yöneticisi bağlamında enjeksiyon
9.1. Présentation
[nuxt-05] örneği, kullanıcı sunucuya zorla çağrılar yapsa bile deponun kalıcı tutulabileceğini göstermiştir. Depodaki öğeler, görünümlere entegre edildiklerinde depodaki değişikliklere tepki verebilmeleri için etkileşimlidir. Ayrıca, istemci/sunucu arasındaki iletişim sırasında öğeleri kalıcı hale getirmek, ancak bunların etkileşimli olmasını istememek de mümkündür; bunun nedeni, bu öğelerin görünümler tarafından görüntülenmemesidir. Bu durumda, bu öğeleri depoda bulunmasalar bile oturumda saklayabiliriz.
Depoya, görünümlerin dışında [context.app.$store] gibi özellikler aracılığıyla veya görünümlerin içinde [this.$store] gibi özellikler aracılığıyla kolayca erişilebilir. Oturum için de benzer bir şey, örneğin [context.app.$session] veya [this.$session] gibi bir şey istiyoruz. Enjeksiyon kavramı sayesinde bunun mümkün olduğunu göreceğiz. Ancak bağlama nesneler enjekte edilemez, yalnızca işlevler enjekte edilebilir. Bu işlev, [context.app.$session()] veya [this.$session()] ifadeleriyle kullanılabilir hale gelecektir.
Son olarak, [plugin]'in [nuxt] kavramını tanıtacağız.
[nuxt-06] örneği, başlangıçta [nuxt-05] projesinin kopyalanmasıyla elde edilir:

- [1] içinde, [plugins] adlı bir klasör ekleyeceğiz;
9.2. [nuxt] eklenti kavramı
[nuxt], [plugin] adını, uygulamanın başlatılması sırasında, şimdiye kadar yürütülen ilk kullanıcı işlevi olan [nuxtServerInit] işlevinin sunucu tarafından yürütülmesinden önce yürütülen tüm kodlara verir. Uygulamanın eklentileri, [nuxt.config.js] yapılandırma dosyasındaki [plugins] anahtarında tanımlanmalıdır:
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/client/session', mode: 'client' },
{ src: '~/plugins/server/session', mode: 'server' }
],
- 5-6. satırlar: Bir eklenti, [src] yoluyla ve [mode] yürütme moduyla tanımlanır. [mode] üç değer alabilir:
- [client]: eklenti yalnızca istemci tarafında çalıştırılmalıdır;
- [server]: eklenti yalnızca sunucu tarafında çalıştırılmalıdır;
- [mode] anahtarı yoksa: bu durumda eklenti hem istemci tarafında hem de sunucu tarafında çalıştırılmalıdır;
- 5-6. satırlar: İki eklentimizi [plugins] adlı bir klasöre yerleştirdik. Bunun için herhangi bir zorunluluk yoktur. Eklentiler, proje dizin yapısı içinde herhangi bir yere yerleştirilebilir. Aynı şekilde, [client, server] alt klasörlerinin adları da burada rastgele seçilmiştir;

9.3. Sunucudaki [session] eklentisi
[server / session.js] eklentisi şöyledir:
/* eslint-disable no-console */
export default (context, inject) => {
// sunucu oturum yönetimi
// mevcut bir oturum var mı?
let value = context.app.$cookies.get('session')
if (!value) {
// yeni oturum
console.log("[plugin session server], démarrage d'une nouvelle session")
value = initValue
} else {
// mevcut oturum
console.log("[plugin session server], reprise d'une session existante")
}
// oturum tanımı
const session = {
// oturum içeriği
value,
// oturumun bir çerezde kaydedilmesi
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
// [context, Vue]'e, oturumu geçerli hale getirecek bir işlev eklenir
inject('session', () => session)
}
// oturumun başlangıç değeri
const initValue = {
initSessionDone: false
}
- 2. satır: Eklentiler, sunucuya her çağrı yapıldığında çalıştırılır: başlangıçta ve kullanıcı URL'i elle yazarak sunucuya bir çağrı yaptığında:
- öncelikle sunucudaki eklenti(ler) çalıştırılır;
- istemci tarayıcısı sunucudan yanıtı aldığında, sıra istemci eklentisinin (veya eklentilerinin) çalıştırılmasına gelir;
- 2. satır: İster istemci ister sunucu olsun, her eklenti iki parametre alır:
- [context]: eklentiyi çalıştıran tarafa göre sunucu veya istemci bağlamı;
- [inject]: sunucu veya istemci bağlamına bir işlev eklemeye olanak tanıyan bir işlev;
- [server / session] eklentisinin iki amacı vardır:
- bir oturum tanımlamak (16-23. satırlar);
- bağlam içinde, 16. satırdaki oturumu sonuç olarak döndürecek bir [$session] işlevi tanımlamak. Bunu yapan 25. satırdır;
- 16-23. satırlar: oturum, verilerini 18. satırdaki [value] nesnesine kapsülleyecektir;
- 20-22. satırlar: Oturum, parametre olarak bir [context] nesnesi alan [save] işlevine sahiptir. Bu bağlamı ona sağlayan, çağıran koddur. Bu bağlamla [save] fonksiyonu, oturum değerini ([value] nesnesi) oturum çerezine kaydeder;
- 6. satır: [server / session] eklentisi çalıştırıldığında, öncelikle sunucunun bir oturum çerezi alıp almadığını kontrol eder;
- eğer aldıysa, 6. satırdaki [value] nesnesi oturum değerini, yani içinde kapsüllenmiş tüm verileri temsil eder;
- değilse, 7-11. satırlarda oturumun başlangıç değeri belirlenir. Bu değer, 29-31. satırlardaki [initValue] nesnesidir. Oturumun öğeleri, sunucu eklentisinden sonra çalıştırılan [nuxtServerInit] işlevinde tanımlanacaktır;
- 18. satır: [value] notasyonu, [value:value] notasyonunun kısaltmasıdır. Soldaki [value], bir nesne anahtarının adıdır; sağdaki [value] ise 6. satırda tanımlanan [value] nesnesidir;
- 25. satır: Bu satıra gelindiğinde, oturum ya mevcut olmadığı için oluşturulmuş ya da istemci tarayıcısının HTTP isteğinden alınmıştır;
- 25. satır: sunucu bağlamına yeni bir işlev eklenir:
- [inject] işlevinin ilk parametresi, oluşturulan işlevin adıdır; burada ‘session’. [nuxt] işlevi, ona aslında ‘$session’ adını verecektir;
- ikinci parametre ise fonksiyonun tanımıdır. Burada [$session] fonksiyonu
- hiçbir parametre kabul etmeyecektir;
- 16. satırdaki [session] nesnesini döndürecektir;
- eklenti çalıştırıldıktan sonra:
- [$session] işlevi, [context.app.$session] içinde, [context] nesnesinin mevcut olduğu yerde kullanılabilir, veya bir görünümde ya da [vuex] mağazasında [this.$session];
- [$session] işlevi, tek bir [value] anahtarına sahip bir [session] nesnesi döndürür;
- oturumun ilk oluşturulmasında, [value] nesnesinin yalnızca [initStoreDone] anahtarı vardır (satır 29-31). [initStoreDone:false] anahtarı, deponun henüz oturuma eklenmediğini belirtmek için kullanılır. Bu işlem, [nuxtServerInit] işlevi tarafından gerçekleştirilecektir;
9.4. Oturumun başlatılması
[session / server] eklentisi sunucu tarafından çalıştırıldıktan sonra, sunucu aşağıdaki [store / index.js] komut dosyasını çalıştıracaktır:
/* eslint-disable no-console */
export const state = () => ({
// sayaç
counter: 0
})
export const mutations = {
// sayaç değerini bir artırma [inc]
increment(state, inc) {
state.counter += inc
},
// durumun değiştirilmesi
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// bu kodu kim çalıştırıyor?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// bir sözün tamamlanmasını bekliyoruz
await new Promise(function(resolve, reject) {
// burada normalde asenkron bir işlev bulunur
// bir saniye bekleyerek simüle ediyoruz
setTimeout(() => {
// oturum başlatılıyor
initSession(store, context)
// başarılı
resolve()
}, 1000)
})
}
}
function initSession(store, context) {
// store, başlatılacak depodur
// oturum alınır
const session = context.app.$session()
// oturum daha önce başlatılmış mı?
if (!session.value.initSessionDone) {
// yeni bir depo başlatılıyor
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// depolama alanı başlatılıyor
store.commit('increment', 77)
// depolama alanı oturuma ekleniyor
session.value.store = store.state
// yeni bir oturum başlatılıyor
session.value.somethingImportant = { x: 2, y: 4 }
// oturum artık başlatıldı
session.value.initSessionDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
// depolama alanı, oturumun depolama alanıyla güncelleniyor
store.commit('replace', session.value.store)
}
// oturum kaydediliyor
session.save(context)
// günlük
console.log('initSession terminé, store=', store.state, 'session=', session.value)
}
[nuxt-05] projesinin depolama alanına kıyasla, yalnızca 38-60. satırlardaki [initSession] işlevi (eski adıyla initStore) değişmektedir:
- 42. satır: sunucu bağlamına eklenmiş olan [$session] işlevi kullanılarak oturum alınır;
- 44. satır: oturumun daha önce başlatılıp başlatılmadığını kontrol ediyoruz;
- 45-54. satırlar: Eğer başlatılmamışsa:
- 48. satır: depo başlatılır;
- 50. satır: deponun durumu oturuma kaydedilir;
- 52. satır: Oturuma başka bir [somethingImportant] nesnesi eklenir. Bu nesne deponun bir parçası olmayacaktır;
- satır 54: oturumun artık başlatılmış olduğu not edilir;
- satır 55-59: oturum zaten başlatılmışsa:
- satır 58: yeni depo, oturumun içeriğiyle başlatılır;
- 61. satır: Oturum, oturum çerezine kaydedilir. Bunun, sunucunun istemci tarayıcısına göndereceği HTTP yanıtına çerezi eklemek anlamına geldiğini hatırlatırız;
9.5. İstemci tarafındaki [client / session] eklentisi
Sunucu, [plugins / server / session] ve [store / index] komut dosyalarını çalıştırdıktan sonra, [index, page1] sayfalarından birini istemci tarayıcısına gönderecektir. Sunucunun HTTP yanıtında oturum çerezi bulunacaktır. Sayfa, istemci tarayıcısı tarafından alındığında, sayfaya gömülü istemci komut dosyaları çalıştırılacaktır. Ardından [client / session] eklentisi çalıştırılacaktır:
/* eslint-disable no-console */
export default (context, inject) => {
// istemci oturumu yönetimi
// oturum mutlaka mevcuttur, sunucu tarafından başlatılır
console.log('[plugin session client], reprise de la session du serveur')
// oturum tanımı
const session = {
// oturum içeriği
value: context.app.$cookies.get('session'),
// oturumun bir çerezde saklanması
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
// [context, Vue] adresine, oturumu geçerli hale getirecek bir işlev eklenir
inject('session', () => session)
}
- istemci eklentisi çalıştırıldığında, oturum çerezi istemci tarayıcısı tarafından çoktan alınmış olacaktır;
- [client] eklentisinin amacı da [$session] işlevini istemci bağlamına enjekte etmektir. Bu işlev, sunucu tarafından gönderilen oturumu etkinleştirir;
- 19. satır: Eklenen [$session] işlevi, 9-16. satırlardaki oturumu döndürür;
- 9-16. satırlar: istemci tarafından yönetilen [session] nesnesi. Bu, sunucu tarafından gönderilen oturumun bir kopyası olacaktır;
- 11. satır: İstemci oturumunun değeri, [nuxt] sunucusu tarafından gönderilen oturum çerezinden alınır;
- 13-15. satırlar: Sunucu oturumunda olduğu gibi, istemci oturumunda da oturum değerini (14. satırdaki [this.value]) tarayıcıda depolanan oturum çerezine kaydetmeye yarayan [save] işlevi bulunur;
9.6. [index] sayfası
[index] sayfası şu şekilde gelişir:
<!-- [index] sayfası -->
<template>
<Layout :left="true" :right="true">
<!-- gezinme -->
<Navigation slot="left" />
<!-- mesaj-->
<template slot="right">
<b-alert show variant="warning"> Home - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- düğme -->
<b-button @click="incrementCounter" class="ml-3" variant="primary">Incrémenter</b-button>
</template>
</Layout>
</template>
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
import Layout from '@/components/layout'
import Navigation from '@/components/navigation'
export default {
name: 'Home',
// kullanılan bileşenler
components: {
Layout,
Navigation
},
computed: {
jsonSession() {
return JSON.stringify(this.$session().value)
}
},
// yaşam döngüsü
beforeCreate() {
// istemci ve sunucu
console.log('[home beforeCreate]')
},
created() {
// istemci ve sunucu
console.log('[home created], session=', this.$session().value)
},
beforeMount() {
// sadece istemci
console.log('[home beforeMount]')
},
mounted() {
// sadece istemci
console.log('[home mounted]')
},
// olay yönetimi
methods: {
incrementCounter() {
console.log('incrementCounter')
// sayaçta 1 artış
this.$store.commit('increment', 1)
// oturum değişikliği
const session = this.$session()
session.value.store = this.$store.state
session.value.somethingImportant.x++
session.value.somethingImportant.y++
// oturumun oturum çerezine kaydedilmesi
session.save(this.$nuxt.context)
}
}
}
</script>
Bu sayfanın hem sunucu tarafında hem de istemci tarafında çalıştırıldığını unutmamak gerekir.
- 8. satır: Artık hem oturum hem de depo görüntüleniyor;
- 30. satır: [jsonSession], oturum değerinden jSON dizesini oluşturan hesaplanmış bir özelliktir;
- 41. satır: Oturum değerini, enjekte edilen [this.$session] işlevi kullanılarak görüntülenir. Bu işlev hem sunucu hem de istemci bağlamında mevcuttur;
- 53. satır: [incrementCounter] yöntemi yalnızca istemci tarafında yürütülür;
- 56. satır: depo sayacı artırılır ve daha önce olduğu gibi görüntülenir;
- 58. satır: Enjekte edilen [this.$session] işlevi kullanılarak oturum alınır;
- satır 59: oturumun depolama alanı güncellenir;
- 60-61. satırlar: oturumun [somethingImportant.x, somethingImportant.y] öznitelikleri artırılır. Bu, bir oturumun depodan başka şeyler de taşımak için kullanılabileceğini göstermek içindir;
- 63. satır: Oturum, tarayıcıda depolanan oturum çerezine kaydedilir. Bir istemci görünümünde, oturumun bağlamı [this.$nuxt.context]'te mevcuttur;
[index] sayfasının amacı, deponun etkileşimli olmasına karşın oturumun etkileşimli olmadığını göstermektir. Oturum öğelerini artırdığımızda, görünümün güncellenmediğini göreceğiz. [page1] görünümü bu soruna bir çözüm sunar.
9.7. [page1] sayfası
[page1] sayfası, [index] sayfasının kopyalanıp biraz değiştirilmesiyle elde edilir:
<!-- [index] sayfası -->
<template>
<Layout :left="true" :right="true">
<!-- gezinme -->
<Navigation slot="left" />
<!-- mesaj-->
<template slot="right">
<b-alert show variant="warning"> Page1 - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- düğme -->
<b-button @click="incrementCounter" class="ml-3" variant="primary">Incrémenter</b-button>
</template>
</Layout>
</template>
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
import Layout from '@/components/layout'
import Navigation from '@/components/navigation'
export default {
name: 'Page1',
// kullanılan bileşenler
components: {
Layout,
Navigation
},
data() {
return {
session: {}
}
},
computed: {
jsonSession() {
return JSON.stringify(this.session.value)
}
},
// yaşam döngüsü
beforeCreate() {
// istemci ve sunucu
console.log('[page1 beforeCreate]')
},
created() {
// istemci ve sunucu
// oturumu sayfanın reaktif özelliklerine ekliyoruz
this.session = this.$session()
// günlük
console.log('[page1 created], session=', this.session.value)
},
beforeMount() {
// sadece istemci
console.log('[page1 beforeMount]')
},
mounted() {
// sadece istemci
console.log('[page1 mounted]')
},
// olay yönetimi
methods: {
incrementCounter() {
console.log('incrementCounter')
// sayaç 1 artırılır
this.$store.commit('increment', 1)
// oturum değişikliği
this.session.value.store = this.$store.state
this.session.value.somethingImportant.x++
this.session.value.somethingImportant.y++
// oturumu oturum çerezine kaydetme
this.session.save(this.$nuxt.context)
}
}
}
</script>
- 47. satır: Temel fark, geçerli oturumun sayfa özelliklerine eklenmesinden kaynaklanmaktadır (29-33. satırlar). Bu, oturumun artık etkileşimli hale gelmesini sağlayacaktır. [incrementCounter] işlevi oturum öğelerini artırdığında, [page1] görünümü güncellenecektir;
9.8. Projenin çalıştırılması
Projeyi çalıştırmadan önce, tarayıcınızdaki oturum çerezini kontrol edin ve varsa, sunucunun yeni bir oturum oluşturması için silin:

Şimdi URL ve [http://localhost:81/nuxt-06/]'i isteyelim:

Tarayıcıdaki günlük kayıtları şu şekildedir:

- [2]'te, sunucu sunucunun [session] eklentisinde yeni bir oturum başlatır;
- [3]'te, bu yeni oturum [nuxtServerInit]'te başlatılır;
- [4]'te, sunucuda bilinen haliyle yeni oturum;
- [5]'te, istemci bu oturumu doğru bir şekilde almıştır;
Şimdi sayacı üç kez artıralım:

- [3]'te, sayaç başarıyla artırıldı ancak [2]'teki oturum artırılmadı. [3], etkin olan oturumu gösterirken, [2] ise etkin olmayan oturumu gösteriyor:
Şimdi sayfayı yeniden yükleyelim (F5). Yeniden yüklemenin ardından günlükler şu şekildedir:

- [2]'te, sunucunun istemci tarayıcısı tarafından gönderilen bir oturum çerezi aldığını görüyoruz;
- [4]'te, deponun sıfırlanmadığı, ancak alınan oturumda devralındığı görülüyor;
- [4-5]'te: oturum özniteliklerinin hepsinin üç kez artırıldığını görüyoruz;
Sunucu tarafından gönderilen sayfa şu şekildedir;

Bu sayfadan çıkarılan sonuç, oturumun store dışında başka öğeler de taşıyabileceğidir, ancak bu öğeler etkileşimli değildir.
Şimdi [Page 1] [4] bağlantısına tıklayalım. Görüntülenen yeni sayfa şöyledir:

Ardından [Incrémenter] düğmesini üç kez kullanalım. Sayfa şu hale gelir:

Bu sefer oturum, [2] olarak doğru şekilde görüntüleniyor. Burada oturum etkileşimli durumda. Bu durum günlüklerde de görülebilir:

- [1-3]'te oturum değerleri;
- [4-6]'te, oturum öğelerinin etkileşimli getter ve setter'ları;
Şimdi [Home] [4] bağlantısına tıklayalım. Aşağıdaki sayfaya ulaşırız:

Ardından [Incrémenter] [4] düğmesine iki kez tıklayalım. Sayfa şu hale gelir:

Burada da oturumun [2] olarak aktif hale geldiğini görüyoruz.
[this.$session()] işlevinin döndürdüğü değeri sorgulayalım:

- [Vue] sekmesinde, mevcut sayfayı ([Home]) seçerek referansını ([$vm0] [3]) elde edelim;
Ardından [Console] [4] sekmesinde, [$vm0.$session()] işlevinin değerini talep edelim:

- [5]'te, oturumun başlangıçta aktif olmadığı halde artık aktif hale geldiğini görüyoruz;
- [6]'te oturumun değerini görüntülemek istiyoruz;
- [7-8]'te, bu değerin de etkileşimli hale geldiği görülüyor;
Dolayısıyla burada beklenmedik bir sonuçla karşı karşıyayız: Bir öğe, sayfanın özelliklerine eklendiği için bir sayfada reaktif hale gelirse, o öğe, özelliklerin bir parçası olmadığı sayfalarda da reaktif hale gelir.
9.9. Conclusion
[nuxt-05] örneği, sunucuya yapılan istekler boyunca deponun kalıcı tutulabileceğini göstermiştir. [nuxt-06] örneği ise, web oturumuna benzetilerek [session] olarak adlandırdığımız bir nesneyle aynı şeyi yapmaktadır. Bu oturumun, [Vuex] depolama alanı ile aynı özelliklere sahip olabileceğini ve doğal olarak reaktif olmamasına rağmen reaktif hale gelebileceğini gördük.
Peki, [Vuex] deposunun ne gibi bir faydası var? Şu an için bunun ne olduğunu tam olarak anlayamadığımı itiraf etmeliyim. Muhtemelen gözümden bir şey kaçmıştır. Bu nedenle, şüpheye düşerseniz şunu kullanmanızı tavsiye ederim:
- [Vuex] adlı bir depo, istemcinin sayfaları arasında paylaşılması gereken her şeyi ve gerekirse istemci ile sunucu arasında paylaşılması gerekenleri barındırmak için;
- istemciden sunucuya yapılan bir çağrı sırasında deponun kalıcı olması gerekiyorsa bir oturum çerezi; bu durumda oturumda yalnızca depo bulunur;
[nuxt-05] ve [nuxt-06] örnekleri, kullanıcının URL kodunu manuel olarak girerek sunucuya çağrıyı zorladığında uygulamanın sürekliliğinin nasıl sağlanabileceğini göstermeyi amaçlıyordu. Bu durumda varsayılan davranışın uygulamanın yeniden başlatılması olduğunu ve bu durumda uygulamanın o andaki durumunun kaybolacağını hatırlatmak isteriz.