9. مثال [nuxt-06]: تزریق در زمینهٔ یک مدیر جلسه
9.1. Présentation
مثال [nuxt-05] نشان داد که مخزن حتی زمانی که کاربر اجباراً فراخوانیهایی را به سرور انجام میدهد، میتواند پایدار بماند. عناصر موجود در مخزن واکنشگرا هستند، بنابراین اگر در ویوها ادغام شوند، آن ویوها به تغییرات مخزن واکنش نشان خواهند داد. همچنین ممکن است بخواهید عناصری را در طول تبادلهای کلاینت/سرور حفظ کنید، بدون اینکه بخواهید واکنشگرا باشند، صرفاً به این دلیل که توسط ویوها نمایش داده نمیشوند. این موارد سپس میتوانند در جلسه (session) ذخیره شوند، بدون اینکه لزوماً در مخزن (store) قرار گیرند.
این مخزن از طریق خواصی مانند [context.app.$store] در خارج از ویوها یا [this.$store] در داخل ویوها بهراحتی قابل دسترسی است. ما برای جلسه نیز به چیزی مشابه، چیزی شبیه به [context.app.$session] یا [this.$session] نیاز داریم. در ادامه خواهیم دید که این کار به لطف مفهوم تزریق امکانپذیر است. با این حال، ما نمیتوانیم اشیاء را به کانتکست تزریق کنیم، بلکه فقط توابع را. این تابع سپس از طریق عبارتهای [context.app.$session()] یا [this.$session()] در دسترس خواهد بود.
در نهایت، مفهوم [nuxt] از [plugin] را ارائه خواهیم داد.
مثال [nuxt-06] در ابتدا با کپی کردن پروژه [nuxt-05] به دست میآید:

- در [1]، ما یک پوشه به نام [plugins] اضافه خواهیم کرد؛
9.2. مفهوم پلاگین [nuxt]
[nuxt] به [plugin] به عنوان هر کدی که هنگام شروع برنامه اجرا میشود، حتی قبل از اینکه سرور تابع [nuxtServerInit] را اجرا کند، اشاره دارد؛ تابعی که تا به حال اولین تابع کاربری بود که اجرا میشد. پلاگینهای برنامه باید تحت کلید [plugins] در فایل پیکربندی [nuxt.config.js] اعلام شوند:
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/client/session', mode: 'client' },
{ src: '~/plugins/server/session', mode: 'server' }
],
- خطوط ۵–۶: یک افزونه با مسیر آن [src] و حالت اجرای آن [mode] شناسایی میشود. [mode] میتواند سه مقدار داشته باشد:
- [client]: افزونه باید فقط در سمت کلاینت اجرا شود؛
- [server]: افزونه باید فقط در سمت سرور اجرا شود؛
- اگر کلید [mode] وجود نداشته باشد: در این صورت، افزونه باید هم در سمت کلاینت و هم در سمت سرور اجرا شود؛
- خطوط ۵–۶: ما دو پلاگین خود را در پوشهای به نام [plugins] قرار دادهایم. این کار الزامی نیست. پلاگینها میتوانند در هر نقطهای از ساختار دایرکتوری پروژه قرار گیرند. به همین ترتیب، نامهای زیرپوشههای [client, server] در اینجا دلخواه هستند؛

9.3. پلاگین [session] سرور
پلاگین [server / session.js] به شرح زیر است:
/* eslint-disable no-console */
export default (context, inject) => {
// مدیریت جلسه سرور
//آیا جلسهٔ موجودی وجود دارد؟
let value = context.app.$cookies.get('session')
if (!value) {
// جلسه جدید
console.log("[plugin session server], démarrage d'une nouvelle session")
value = initValue
} else {
// جلسهٔ موجود
console.log("[plugin session server], reprise d'une session existante")
}
// تعریف جلسه
const session = {
// محتوای جلسه
value,
// جلسه در یک کوکی ذخیره شده است
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
//یک تابع به [context, Vue] تزریق میشود که جلسه را جاری میکند
inject('session', () => session)
}
//مقدار اولیهٔ جلسه
const initValue = {
initSessionDone: false
}
- خط ۲: پلاگینها هر بار که فراخوانی به سرور انجام میشود اجرا میشوند: هنگام راهاندازی و هر بار که کاربر با تایپ دستی URL فراخوانی را اجباری میکند:
- ابتدا، پلاگینهای سرور اجرا میشوند؛
- پس از اینکه مرورگر کلاینت پاسخ را از سرور دریافت کرد، نوبت اجرای پلاگینهای کلاینت است؛
- خط ۲: هر افزونه، چه سمت کلاینت و چه سمت سرور، دو پارامتر دریافت میکند:
- [context]: زمینهٔ سرور یا کلاینت، بسته به اینکه کدامیک در حال اجرای افزونه باشد؛
- [inject]: تابعی که امکان تزریق یک تابع به زمینهٔ سرور یا کلاینت را فراهم میکند؛
- هدف پلاگین [server / session] دوگانه است:
- تعریف یک جلسه (خطوط 16–23)؛
- تعریف یک تابع [$session] در آن زمینه که جلسه را از خط 16 بازمیگرداند. این کار در خط 25 انجام میشود؛
- خطوط ۱۶–۲۳: جلسه دادههای خود را در شی [value] در خط ۱۸ محصور میکند؛
- خطوط ۲۰–۲۲: این تابع [save] را دارد که یک شیء [context] را بهعنوان پارامتر میپذیرد. کد فراخوانی این زمینه را برای آن فراهم میکند. با استفاده از این، تابع [save] مقدار جلسه، یعنی شیء [value] را، در کوکی جلسه ذخیره میکند؛
- خط ۶: هنگامی که افزونه [server / session] اجرا میشود، ابتدا بررسی میکند که آیا سرور کوکی جلسه را دریافت کرده است یا خیر؛
- اگر چنین باشد، شیء [value] در خط ۶ نمایانگر مقدار جلسه، یعنی تمام دادههای دربرگرفته در آن است؛
- در غیر این صورت، خطوط ۷–۱۱ مقدار اولیه جلسه را تعیین میکنند. این مقدار، شیء [initValue] در خطوط ۲۹–۳۱ خواهد بود. عناصر جلسه در تابع [nuxtServerInit] تعریف خواهند شد، که پس از افزونه سرور اجرا میشود؛
- خط ۱۸: نشانه [value] مخففی برای نشانه [value:value] است. [value] در سمت چپ نام یک کلید شیء است؛ [value] در سمت راست، شیء [value] است که در خط ۶ اعلام شده است؛
- خط ۲۵: وقتی به این خط میرسیم، جلسه یا به دلیل عدم وجود ایجاد شده است، یا از درخواست HTTP مرورگر کلاینت بازیابی شده است؛
- خط 25: یک تابع جدید در زمینهٔ سرور تزریق میشود:
- پارامتر اول [inject] نام تابع در حال ایجاد است، که در این مورد 'session' است. در واقع [nuxt] نام '$session' را به آن خواهد داد؛
- پارامتر دوم تعریف تابع است. در اینجا، تابع [$session]
- هیچ پارامتری را قبول نخواهد کرد؛
- شیء [session] را از خط 16 بازمیگرداند؛
- پس از اجرای افزونه:
- تابع [$session] در [context.app.$session] در دسترس است، جایی که شیء [context] در دسترس است، یا [this.$session] در یک نما یا در مخزن [vuex]؛
- توابع [$session] یک شیء [session] را با یک کلید منحصربهفرد [value] بازمیگردانند؛
- در هنگام ایجاد اولیهٔ جلسه، شیء [value] تنها یک کلید دارد، [initStoreDone] (خطوط 29–31). کلید [initStoreDone:false] برای نشان دادن اینکه store هنوز در جلسه قرار داده نشده است استفاده میشود. این کار توسط تابع [nuxtServerInit] انجام خواهد شد؛
9.4. ابتدای جلسه
پس از اجرای پلاگین [session / server] توسط سرور، سرور سپس اسکریپت زیر [store / index.js] را اجرا خواهد کرد:
/* eslint-disable no-console */
export const state = () => ({
// شمارنده
counter: 0
})
export const mutations = {
// شمارنده را یک واحد افزایش دهید [inc]
increment(state, inc) {
state.counter += inc
},
// تعویض وضعیت
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// چه کسی این کد را اجرا میکند؟
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// منتظر تکمیل یک وعده
await new Promise(function(resolve, reject) {
// به طور معمول، در اینجا یک تابع ناهمزمان وجود داشت
// ما آن را با یک وقفه یک ثانیهای شبیهسازی میکنیم
setTimeout(() => {
// راهاندازی جلسه
initSession(store, context)
// موفقیت
resolve()
}, 1000)
})
}
}
function initSession(store, context) {
//`store` همان `store` است که باید اولیه شود
// بازیابی جلسه
const session = context.app.$session()
//آیا جلسه قبلاً راهاندازی شده است؟
if (!session.value.initSessionDone) {
//یک مخزن جدید راهاندازی میشود
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// ابتداییسازی فروشگاه
store.commit('increment', 77)
//فروشگاه به جلسه اضافه میشود
session.value.store = store.state
// یک جلسه جدید در حال راهاندازی است
session.value.somethingImportant = { x: 2, y: 4 }
// جلسه اکنون راهاندازی شده است
session.value.initSessionDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
//فروشگاه با فروشگاه جلسه بهروزرسانی میشود
store.commit('replace', session.value.store)
}
// جلسه ذخیره میشود
session.save(context)
// لاگ
console.log('initSession terminé, store=', store.state, 'session=', session.value)
}
در مقایسه با فروشگاه پروژه [nuxt-05]، تنها تابع [initSession] (قبلاً initStore) در خطوط ۳۸–۶۰ تغییر کرده است:
- خط ۴۲: جلسه با استفاده از تابع [$session] که در زمینهٔ سرور تزریق شده است، بازیابی میشود؛
- خط ۴۴: بررسی میکنیم که آیا جلسه قبلاً инициалиزه شده است یا خیر؛
- خطوط ۴۵–۵۴: اگر نه:
- خط ۴۸: مخزن مقداردهی اولیه میشود؛
- خط ۵۰: وضعیت فروشگاه در جلسه قرار داده میشود؛
- خط ۵۲: یک شیء [somethingImportant] دیگر به جلسه اضافه میشود. این شیء بخشی از مخزن نخواهد بود؛
- خط ۵۴: ذکر میشود که جلسه اکنون راهاندازی شده است؛
- خطوط ۵۵–۵۹: اگر جلسه قبلاً آغاز شده باشد:
- خط ۵۸: فروشگاه جدید با محتویات جلسه اولیه میشود؛
- خط ۶۱: جلسه در کوکی جلسه ذخیره میشود. به یاد داشته باشید که این کار شامل قرار دادن کوکی در پاسخ HTTP است که سرور به مرورگر کلاینت ارسال خواهد کرد؛
9.5. پلاگین [client / session] کلاینت
پس از اینکه سرور اسکریپتهای [plugins / server / session] و [store / index] را اجرا کرد، یکی از صفحات [index, page1] را به مرورگر کلاینت ارسال خواهد کرد. پاسخ HTTP سرور حاوی کوکی جلسه خواهد بود. پس از دریافت صفحه توسط مرورگر کلاینت، اسکریپتهای سمت کلاینت که در صفحه جاسازی شدهاند اجرا خواهند شد. سپس افزونه [client / session] اجرا میشود:
/* eslint-disable no-console */
export default (context, inject) => {
// مدیریت جلسهٔ کلاینت
// سشن باید وجود داشته باشد، توسط سرور اولیه شده باشد
console.log('[plugin session client], reprise de la session du serveur')
// تعریف جلسه
const session = {
//محتوای جلسه
value: context.app.$cookies.get('session'),
//جلسه در یک کوکی ذخیره شده است
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
//یک تابع به [context, Vue] تزریق میشود که جلسه را فعال میکند
inject('session', () => session)
}
- هنگامی که افزونه سمت کلاینت اجرا میشود، کوکی جلسه قبلاً توسط مرورگر کلاینت دریافت شده است؛
- هدف افزونه [client] این است که یک تابع به نام [$session] را نیز در زمینه کلاینت تزریق کند. این تابع، جلسه ارسالشده توسط سرور را بازمیگرداند؛
- خط ۱۹: تابع تزریقی [$session] جلسه را از خطوط ۹–۱۶ بازمیگرداند؛
- خطوط ۹–۱۶: ابجکت [session] که توسط کلاینت مدیریت میشود. این یک کپی از سشن ارسالشده توسط سرور خواهد بود؛
- خط ۱۱: مقدار جلسهٔ کلاینت از کوکی جلسه که توسط سرور ارسال شده ([nuxt]) گرفته میشود؛
- خطوط ۱۳–۱۵: همانند جلسه سرور، جلسه کلاینت نیز دارای تابعی به نام [save] است که مقدار جلسه ([this.value] در خط ۱۴) را در کوکی جلسه ذخیره شده در مرورگر ذخیره میکند؛
9.6. صفحه [index]
صفحه [index] به شرح زیر تغییر میکند:
<!-- صفحه [index] -->
<template>
<Layout :left="true" :right="true">
<!-- ناوبری -->
<Navigation slot="left" />
<!-- پیام-->
<template slot="right">
<b-alert show variant="warning"> Home - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- دکمه -->
<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',
// کامپوننتهای استفاده شده
components: {
Layout,
Navigation
},
computed: {
jsonSession() {
return JSON.stringify(this.$session().value)
}
},
// چرخهٔ عمر
beforeCreate() {
// کلاینت و سرور
console.log('[home beforeCreate]')
},
created() {
// کلاینت و سرور
console.log('[home created], session=', this.$session().value)
},
beforeMount() {
//فقط کلاینت
console.log('[home beforeMount]')
},
mounted() {
//فقط کلاینت
console.log('[home mounted]')
},
//مدیریت رویداد
methods: {
incrementCounter() {
console.log('incrementCounter')
// افزایش شمارنده به اندازه ۱
this.$store.commit('increment', 1)
// تغییر جلسه
const session = this.$session()
session.value.store = this.$store.state
session.value.somethingImportant.x++
session.value.somethingImportant.y++
//ذخیره جلسه در کوکی جلسه
session.save(this.$nuxt.context)
}
}
}
</script>
به یاد داشتن این نکته مهم است که این صفحه هم در سمت سرور و هم در سمت کلاینت اجرا میشود.
- خط ۸: هم جلسه و هم فروشگاه اکنون نمایش داده میشوند؛
- خط ۳۰: [jsonSession] یک ویژگی محاسباتی است که رشته jSON را بر اساس مقدار جلسه تنظیم میکند؛
- خط ۴۱: مقدار جلسه با استفاده از تابع تزریقی [this.$session] نمایش داده میشود. این تابع در هر دو زمینه سرور و کلاینت وجود دارد؛
- خط ۵۳: متد [incrementCounter] تنها در سمت کلاینت اجرا میشود؛
- خط ۵۶: شمارنده فروشگاه افزایش یافته و مانند قبل نمایش داده میشود؛
- خط ۵۸: جلسه با استفاده از تابع تزریقی [this.$session] بازیابی میشود؛
- خط ۵۹: مخزن جلسه بهروزرسانی میشود؛
- خطوط ۶۰–۶۱: ویژگیهای [somethingImportant.x, somethingImportant.y] جلسه افزایش مییابند. این صرفاً برای نشان دادن این است که یک جلسه میتواند برای حمل دادههایی غیر از ذخیرهسازی استفاده شود؛
- خط ۶۳: جلسه در کوکی جلسه که در مرورگر ذخیره شده است، ذخیره میشود. از دیدگاه کلاینت، زمینه این کوکی در [this.$nuxt.context] در دسترس است؛
هدف صفحه [index] نشان دادن این است که جلسه (session) واکنشی (reactive) نیست، در حالی که مخزن (store) واکنشی است. هنگامی که عناصر جلسه افزایش مییابند، مشاهده میشود که نما (view) بهروزرسانی نمیشود. نمای [page1] راهحلی برای این مشکل ارائه میدهد.
9.7. صفحه [page1]
صفحه [page1] با کپی کردن صفحه [index] و سپس تغییر اندکی در آن به دست میآید:
<!-- صفحه [index] -->
<template>
<Layout :left="true" :right="true">
<!-- پیمایش -->
<Navigation slot="left" />
<!-- پیام-->
<template slot="right">
<b-alert show variant="warning"> Page1 - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- دکمه -->
<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',
// کامپوننتهای استفاده شده
components: {
Layout,
Navigation
},
data() {
return {
session: {}
}
},
computed: {
jsonSession() {
return JSON.stringify(this.session.value)
}
},
// چرخهٔ عمر
beforeCreate() {
// کلاینت و سرور
console.log('[page1 beforeCreate]')
},
created() {
// کلاینت و سرور
// ما جلسه را در ویژگیهای واکنشی صفحه قرار میدهیم
this.session = this.$session()
// لاگ
console.log('[page1 created], session=', this.session.value)
},
beforeMount() {
//فقط کلاینت
console.log('[page1 beforeMount]')
},
mounted() {
//فقط کلاینت
console.log('[page1 mounted]')
},
// پردازش رویداد
methods: {
incrementCounter() {
console.log('incrementCounter')
// شمارنده را ۱ واحد افزایش دهید
this.$store.commit('increment', 1)
// تغییر جلسه
this.session.value.store = this.$store.state
this.session.value.somethingImportant.x++
this.session.value.somethingImportant.y++
// ذخیره جلسه در کوکی جلسه
this.session.save(this.$nuxt.context)
}
}
}
</script>
- خط ۴۷: تفاوت اصلی این است که جلسهٔ جاری در ویژگیهای صفحه (خطوط ۲۹–۳۳) تنظیم شده است. این کار تضمین میکند که جلسه اکنون واکنشگرا باشد. هنگامی که تابع [incrementCounter] عناصر جلسه را افزایش میدهد، نمای [page1] بهروزرسانی خواهد شد؛
9.8. اجرای پروژه
قبل از اجرای پروژه، کوکی جلسه مرورگر خود را بررسی کنید و در صورت وجود آن را حذف کنید تا سرور یک جلسه جدید ایجاد کند:

اکنون بیایید URL و [http://localhost:81/nuxt-06/] را درخواست کنیم:

لاگهای مرورگر سپس به شرح زیر است:

- در [2]، سرور یک جلسه جدید را در افزونه [session] خود آغاز میکند؛
- در [3]، این جلسهٔ جدید در [nuxtServerInit] آغاز میشود؛
- در [4]، جلسه جدید همانطور که در سرور شناخته میشود؛
- در [5]، کلاینت با موفقیت این جلسه را بازیابی کرده است؛
حالا شمارنده را سه بار افزایش میدهیم:

- در [3]، شمارنده واقعاً افزایش یافته است اما جلسه در [2] افزایش نیافته است. در حالی که [3] فروشگاه را که فعال است نشان میدهد، [2] جلسه را که غیرفعال است نشان میدهد:
حالا صفحه را دوباره بارگذاری کنیم (F5). لاگهای پس از این بارگذاری به شرح زیر است:

- در [2]، میبینیم که سرور یک کوکی جلسه را که توسط مرورگر کلاینت ارسال شده است، دریافت کرده است؛
- در [4]، میبینیم که مقدار store ریست نشده، بلکه از جلسه دریافتشده منتقل شده است؛
- در [4-5]: میتوانیم ببینیم که تمام ویژگیهای جلسه در واقع سه بار افزایش یافتهاند؛
صفحه ارسالشده توسط سرور به شرح زیر است؛

نتیجهگیری که از این صفحه میتوان گرفت این است که جلسه (session) میتواند عناصری غیر از store را حمل کند، اما اینها واکنشگرا نیستند.
اکنون روی لینک [Page 1] [4] کلیک کنیم. صفحه جدید نمایش داده شده به شرح زیر است:

سپس دکمه [Incrémenter] را سه بار کلیک کنیم. صفحه به شکل زیر تغییر میکند:

این بار، جلسه به درستی به صورت [2] نمایش داده میشود. اکنون واکنشگرا است. این را میتوان در لاگها مشاهده کرد:

- در [1-3]، مقادیر جلسه؛
- در [4-6]، گترها و سترهای واکنشگرا برای عناصر جلسه؛
حالا روی لینک [Home] [4] کلیک کنیم. به صفحهٔ زیر هدایت میشویم:

سپس روی دکمه [Incrémenter] [4] دوبار کلیک کنیم. صفحه به شکل زیر تغییر میکند:

میتوانیم ببینیم که در اینجا نیز، جلسه فعال شده است: [2].
بیایید مقدار بازگردانده شده توسط تابع [this.$session()] را بازیابی کنیم:

- در زبانه [Vue]، صفحه جاری [Home] را برای به دست آوردن مرجع آن [$vm0] [3] انتخاب کنید؛
سپس، در برگه [Console] [4]، مقدار تابع [$vm0.$session()] را بازیابی کنید:

- در [5]، میتوانیم ببینیم که جلسه فعال شده است، در حالی که در ابتدا فعال نبود؛
- در [6]، مقدار session را درخواست میکنیم؛
- در [7-8]، متوجه میشویم که این مقدار نیز واکنشگرا شده است؛
این امر در نتیجه یک نتیجه غیرمنتظره به همراه دارد: اگر یک عنصر به دلیل گنجانده شدن در ویژگیهای صفحه واکنشپذیر شود، آنگاه در صفحاتی که بخشی از ویژگیها نیست نیز واکنشپذیر میشود.
9.9. Conclusion
مثال [nuxt-05] نشان داد که این مخزن میتواند در طول درخواستهای ارسالشده به سرور حفظ شود. مثال [nuxt-06] همین کار را با ابجکتی انجام میدهد که ما با الهام از جلسه وب آن را [session] نامیدهایم. دیدیم که این جلسه میتوانست همان ویژگیهای store [Vuex] را داشته باشد و همچنین واکنشگرا شود، هرچند که بهطور پیشفرض واکنشگرا نبود.
پس هدف از مخزن [Vuex] چیست؟ باید اعتراف کنم که در حال حاضر برایم روشن نشده است. احتمالاً چیزی را نادیده گرفتهام. بنابراین، در صورت شک، توصیه میکنم از موارد زیر استفاده کنید:
- یک مخزن [Vuex] برای نگهداری همه چیزهایی که نیاز به اشتراکگذاری بین صفحات سمت کلاینت دارند، و هر چیزی که ممکن است بین کلاینت و سرور نیاز به اشتراکگذاری داشته باشد؛
- یک کوکی جلسه (session cookie) اگر نیاز باشد که مخزن در طول تماس کلاینت-به-سرور پایدار بماند، در این صورت جلسه تنها شامل مخزن خواهد بود؛
مثالهای [nuxt-05] و [nuxt-06] برای نشان دادن این بودند که چگونه میتوان تداوم برنامه را زمانی که کاربر با وارد کردن دستی URL، فراخوانی به سرور را اجباری میکند، تضمین کرد. شایان ذکر است که رفتار پیشفرض در این حالت، راهاندازی مجدد برنامه است، به این معنی که وضعیت فعلی آن از بین میرود.