Skip to content

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:

Image

  • [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;

Image

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:

Image

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

Image

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

Image

  • [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:

Image

  • [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:

Image

  • [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;

Image

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:

Image

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

Image

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:

Image

  • [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:

Image

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

Image

Burada da oturumun [2] olarak aktif hale geldiğini görüyoruz.

[this.$session()] işlevinin döndürdüğü değeri sorgulayalım:

Image

  • [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:

Image

  • [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.