5. مثال [nuxt-02]: صفحات الخادم والعميل
في هذا المشروع، نوضح:
- أن الصفحة التي أنشأها العميل قد تختلف بصريًا عن تلك التي تم استلامها من الخادم. ونلاحظ هنا تغييرًا سريعًا في الصفحة، يمكن للمستخدم ملاحظته، مما يضر بسهولة استخدام التطبيق. لذا، فإن هذه خيار يجب تجنبه؛
- حلًا يجعل صفحة العميل تعيد إنشاء نفس الصفحة التي أرسلها الخادم؛
تم الحصول على المشروع [nuxt-02] في البداية عن طريق نسخ المشروع [nuxt-01].

تمت إضافة مجلد [store] إلى المشروع بالإضافة إلى صفحتين جديدتين. سنعود إلى هذا الموضوع لاحقًا.
5.1. الصفحة [index]
5.1.1. كود الصفحة
يصبح كود الصفحة [index] كما يلي:
<!-- الصفحة الرئيسية -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة-->
<b-alert slot="right" show variant="warning"> Home - value= {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Home',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[home beforeCreate]')
},
created() {
// العميل والخادم
console.log('[home created]')
// الخادم فقط
if (process.server) {
this.value = 10
}
// العميل والخادم
console.log('value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[home beforeMount]')
},
mounted() {
// العميل فقط
console.log('[home mounted]')
}
}
</script>
تعليقات
- السطر 7: ستعرض الصفحة [index] قيمة خاصيتها [value] (السطر 28)؛
- الأسطر 36-45: يجب أن نتذكر هنا أن الدالة [created] يتم تنفيذها في كل من جانب الخادم وجانب العميل. في الأسطر 40-42، سيقوم الخادم بتغيير قيمة الخاصية [value] إلى 10. أما العميل، فلن يغير هذه القيمة. نريد فقط معرفة ما إذا كان العميل يحتفظ بهذه القيمة. سنكتشف أن الإجابة هي لا؛
5.1.2. التنفيذ
نقوم بتعديل الملف [/nuxt.config.js] لتنفيذ المشروع [nuxt-02]:
...
// مجلد شفرة المصدر
srcDir: 'nuxt-02',
// جهاز التوجيه
router: {
// جذر URL للتطبيق
base: '/nuxt-02/'
},
// الخادم
server: {
// منفذ الخدمة، 3000 افتراضيًا
port: 81,
// عناوين الشبكة المستمعة، افتراضيًا localhost: 127.0.0.1
// 0.0.0.0 = جميع عناوين الشبكة الخاصة بالجهاز
host: 'localhost'
}
...
نقوم بتنفيذ المشروع [1]:

ثم يتم عرض الصفحة [index] [2-3]. تعرض القيمة [10] لبضع لحظات ثم تعرض القيمة [0]. ماذا حدث؟
الخطوة 1
الخادم هو الذي يعمل أولاً. يقوم بتنفيذ كود الصفحة [index]:
export default {
name: 'Home',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[home beforeCreate]')
},
created() {
// العميل والخادم
console.log('[home created]')
// الخادم فقط
if (process.server) {
this.value = 10
}
// العميل والخادم
console.log('value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[home beforeMount]')
},
mounted() {
// العميل فقط
console.log('[home mounted]')
}
}
- بسبب السطر 23، تأخذ الخاصية [value] في السطر 10 القيمة 10؛
يمكن التحقق من ذلك من خلال الاطلاع على كود المصدر للصفحة التي استقبلها المتصفح (خيار [code source] في المتصفح):
<!doctype html>
<html data-n-head-ssr>
<head>
<title>Introduction à [nuxt.js]</title>
<meta data-n-head="ssr" charset="utf-8">
<meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
<meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
<link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
<base href="/nuxt-02/">
<link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
....
</head>
<body>
<div data-server-rendered="true" id="__nuxt">
<div id="__layout">
<div class="container">
<div class="card">
<div class="card-body">
<div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
<h4>[nuxt-02] : page serveur, page client</h4>
</div> <div>
<div class="row">
<div class="col-2">
<ul class="nav flex-column">
<li class="nav-item">
<a href="/nuxt-02/" target="_self" class="nav-link active nuxt-link-active">
Home
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page1" target="_self" class="nav-link">
Page 1
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page2" target="_self" class="nav-link">
Page 2
</a>
</li>
</ul>
</div> <div class="col-10">
<div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-warning">
Home - value= 10
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>window.__NUXT__ = ....;</script>
<script src="/nuxt-02/_nuxt/runtime.js" defer></script>
<script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
<script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
<script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
- السطر 46: في الصفحة المستلمة، كانت قيمة [value] هي 10؛
الخطوة 2
نعلم أنه بعد استلام الصفحة، تتولى البرامج النصية في الأسطر 57-60 زمام الأمور وتغير سلوك الصفحة المستلمة، بل وحتى المعلومات المعروضة كما هو الحال هنا. تشكل هذه البرامج النصية العميل الذي ينفذ بدوره كود الصفحة [index]، وهو نفس الكود الذي يستخدمه الخادم:
export default {
name: 'Home',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[home beforeCreate]')
},
created() {
// العميل والخادم
console.log('[home created]')
// الخادم فقط
if (process.server) {
this.value = 10
}
// العميل والخادم
console.log('value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[home beforeMount]')
},
mounted() {
// العميل فقط
console.log('[home mounted]')
}
}
- لفهم ما يحدث، يجب أن نفهم أن العميل [nuxt] لن ينفذ الأسطر 22-24 (process.server=false)؛
- في تطبيق [vue] كلاسيكي، تظل الخاصية [value] في السطر 10 عند 0. ولهذا السبب، بمجرد انتقال العميل إلى الصفحة المستلمة، تصبح القيمة المعروضة [0]؛
القيمة التي أنشأها الخادم [nuxt] للخاصية [value] لم تكن ذات فائدة.
5.2. الصفحة [page1]
5.2.1. المتجر [Vuex]
أضفنا مجلد [store] إلى المشروع [nuxt-02]:

إن وجود هذا الملف يؤدي تلقائيًا إلى قيام [nuxt] بتنفيذ مخزن [Vuex]. الملف [index.js] هو الذي ينفذ هذا المخزن. هنا، الملف [index.js] هو التالي:
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state, inc) {
state.counter += inc
}
}
يقوم [nuxt] بتنفيذ مخزن [Vuex] استنادًا إلى محتوى [index.js]:
- الأسطر 1-3: تعريف حالة مخزن [state]. يتم إرجاع هذه الحالة بواسطة دالة. هنا، تحتوي الحالة على خاصية واحدة فقط، وهي العداد الموجود في السطر 2. يجب أن تسمى الدالة المصدرة [state]؛
- الأسطر 5-9: العمليات الممكنة على حالة المخزن. نسميها [mutations]. هنا، تسمح عملية التغيير [increment] بزيادة الخاصية [counter] بمقدار [inc]. يجب أن يُسمى الكائن المُصدَّر [mutations]؛
يتوفر [store] Vuex الذي تم تنفيذه بواسطة [nuxt] في أماكن مختلفة. في العروض، يتوفر في الخاصية [this.$store].
5.2.2. كود الصفحة
مثل الصفحة [index]، ستعرض الصفحة [page1] قيمة، وهي قيمة عداد مخزن Vuex:
<!-- الصفحة 1 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة-->
<b-alert slot="right" show variant="primary"> Page 1 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[home beforeCreate]')
},
created() {
// العميل والخادم
console.log('[home created]')
// الخادم فقط
if (process.server) {
this.$store.commit('increment', 25)
}
// العميل والخادم
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[home beforeMount]')
},
mounted() {
// العميل فقط
console.log('[home mounted]')
}
}
</script>
تعليقات
- الأسطر 38-40: سيقوم الخادم بزيادة العداد بمقدار 25؛
- السطر 42: سيعرض كل من الخادم والعميل قيمة العداد؛
- السطر 7: يتم عرض قيمة العداد؛
عند قراءة هذا الكود، يجب فهم أمرين:
- الرمز المنفذ هو نفسه بالنسبة للخادم والعميل؛
- الكائن [this] ليس هو نفسه: هناك إصدار [this] على جانب الخادم وإصدار آخر [client] على جانب العميل؛
نحن نسعى لمعرفة ما إذا كان [this.$store] الخاص بالخادم هو نفسه [this.$store] الخاص بالعميل. نظرًا لأن الخادم هو الذي يتم تشغيله أولاً (عند بدء تشغيل التطبيق)، فإن هذا يعود إلى طرح السؤال التالي: هل يتم إرسال [store] الذي تم تهيئته بواسطة الخادم إلى العميل؟
5.2.3. التنفيذ
نقوم بتنفيذ المشروع [nuxt-02] ونكتب [localhost:81/nuxt-02/page1] يدويًا حتى يتم استدعاء الخادم. كما هو الحال عند بدء تشغيل الصفحة [index]:
- يقوم الخادم بتنفيذ الصفحة [page1.vue]؛
- يرسل الصفحة التي تم إنشاؤها إلى المتصفح. يتم عرض هذه الصفحة؛
- تتولى البرامج النصية للعميل المضمنة في الصفحة المرسلة زمام الأمور وتنفذ الصفحة [page1.vue] مرة أخرى؛
- ثم يتم تعديل الصفحة المعروضة؛
والنتيجة النهائية هي كما يلي:

هذه المرة، القيمة المعروضة هي بالفعل تلك التي حددها الخادم، وبصريًا، لا نرى الصفحة "تتذبذب" بسبب تغيير العميل للقيمة المعروضة من قبل الخادم. ماذا حدث هذه المرة؟
قام الخادم بتنفيذ الصفحة التالية [page1]:
...
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page1 beforeCreate]')
},
created() {
// العميل والخادم
console.log('[page1 created]')
// الخادم فقط
if (process.server) {
this.$store.commit('increment', 25)
}
// العميل والخادم
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[page1 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page1 mounted]')
}
}
</script>
- تم تنفيذ الأسطر 30-32 دون أخطاء. وهذا يعني أنه على جانب الخادم أيضًا، تشير [this.$store] إلى المخزن [Vuex]. قامت السطر 31 بزيادة عداد المخزن إلى 25؛
- وبعد ذلك، تم إرسال الصفحة إلى العميل؛
إذا نظرنا إلى الصفحة التي استلمها العميل، نجد العناصر التالية:
<!doctype html>
<html data-n-head-ssr>
<head>
<title>Introduction à [nuxt.js]</title>
<meta data-n-head="ssr" charset="utf-8">
<meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
<meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
<link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
<base href="/nuxt-02/">
<link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
...
</head>
<body>
<div data-server-rendered="true" id="__nuxt">
<div id="__layout">
<div class="container">
<div class="card">
<div class="card-body">
<div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
<h4>[nuxt-02] : page serveur, page client</h4>
</div>
<div>
<div class="row">
<div class="col-2">
<ul class="nav flex-column">
<li class="nav-item">
<a href="/nuxt-02/" target="_self" class="nav-link">
Home
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page1" target="_self" class="nav-link active nuxt-link-active">
Page 1
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page2" target="_self" class="nav-link">
Page 2
</a>
</li>
</ul>
</div> <div class="col-10">
<div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-primary">
Page 1 - value = 25
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.__NUXT__ = (function (a, b, c) {
return {
layout: "default", data: [{}], error: null, state: { counter: 25 }, serverRendered: true,
logs: [
{ date: new Date(1574085336802), args: ["[home beforeCreate]"], type: a, level: b, tag: c },
{ date: new Date(1574085336839), args: ["[home created]"], type: a, level: b, tag: c },
{ date: new Date(1574085336869), args: ["value=", "25"], type: a, level: b, tag: c }
]
}
}("log", 2, ""));</script>
<script src="/nuxt-02/_nuxt/runtime.js" defer></script>
<script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
<script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
<script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
- السطر 47: القيمة المرسلة من الخادم؛
- السطر 60: نلاحظ أن حالة المخزن [Vuex] قد تم تضمينها في الصفحة. سيسمح ذلك للعميل الذي سيتم تنفيذه بعد استلام الصفحة، بإعادة تكوين مخزن جديد [Vuex] بقيمة 25 كقيمة أولية للعداد؛
بعد استلام وعرض الصفحة الواردة من الخادم، يتولى العميل زمام الأمور ويقوم بدوره بتنفيذ الصفحة [page1]:
...
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page1 beforeCreate]')
},
created() {
// العميل والخادم
console.log('[page1 created]')
// الخادم فقط
if (process.server) {
this.$store.commit('increment', 25)
}
// العميل والخادم
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[page1 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page1 mounted]')
}
}
</script>
- السطر 34: تتلقى الخاصية [value] في السطر 18 بدورها القيمة 25 من العداد؛
وبالتالي، فإن مخزن [nuxt] يسمح للخادم بنقل المعلومات إلى العميل أثناء التحميل الأولي للصفحة، عندما يتم البحث عنها على الخادم. تجدر الإشارة إلى أنه بمجرد الحصول على هذه الصفحة، لا يتم طلب الخادم مرة أخرى وتعمل التطبيق كتطبيق [vue] كلاسيكي، في وضع SAP.
5.3. الصفحة [page2]
في الصفحة [page2]، نعرض طريقة أخرى لكي:
- الخادم يضمّن معلومات محسوبة في الصفحة؛
- لا يقوم العميل بتعديل هذه المعلومات؛
5.3.1. كود الصفحة
يتطور كود الصفحة [page2] على النحو التالي:
<!-- الصفحة 2 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة -->
<b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page2',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
asyncData(context) {
// من الذي ينفذ هذا الكود؟
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// للخادم فقط
if (process.server) {
// نقوم بإرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما تكون لدينا هنا وظيفة غير متزامنة
// نحاكيها بانتظار لمدة ثانية
setTimeout(() => {
// سيتم تضمين هذه النتيجة في خصائص [data]
resolve({ value: 87 })
// سجل
console.log('asynData terminée')
}, 1000)
})
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page2 beforeCreate]')
},
created() {
// العميل والخادم
console.log('[page2 created]')
},
beforeMount() {
// العميل فقط
console.log('[page2 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page2 mounted]')
}
}
</script>
- السطر 7: تعرض الصفحة قيمة خاصية تسمى [value]؛
- الخاصية [value] غير موجودة كعنصر في كائن يتم عرضه بواسطة الدالة [data]. هنا، هذه الدالة غير موجودة. يتم إنشاء الخاصية [value] ديناميكيًا بواسطة السطر 36؛
- السطر 25: الدالة [asyncData] هي دالة [nuxt]. وكما يشير اسمها، فهي عادةً وظيفة غير متزامنة. وتتمثل مهمتها المعتادة في جلب البيانات الخارجية. تضمن [nuxt] عدم إرسال الصفحة إلى متصفح العميل قبل أن تقوم الوظيفة [asyncData] بعرض بياناتها غير المتزامنة؛
- تستقبل الدالة [asyncData] كمعلمة السياق [nuxt]. هذا الكائن غني جدًا بالمعلومات ويتيح الوصول إلى الكثير من المعلومات حول تطبيق [nuxt. سنكتشف ذلك في الأقسام القادمة؛
- السطر 31: يتم تنفيذ الدالة [asyncData] باستخدام [Promise] (انظر الوثيقة |مقدمة إلى لغة ECMASCRIPT 6 من خلال الأمثلة|). يقبل منشئ هذه الفئة كمعلمة دالة غير متزامنة تقوم بما يلي:
- تشير إلى النجاح عن طريق إرجاع البيانات باستخدام الدالة [resolve]. يتم تضمين الكائن الذي ترجعه هذه الدالة تلقائيًا في خصائص [data] للصفحة؛
- تشير إلى الفشل عن طريق إرجاع خطأ باستخدام الدالة [reject]؛
- السطر 34: يتم محاكاة وظيفة غير متزامنة باستخدام الوظيفة [setTimeout]. تُرجع هذه الدالة الكائن [{ value: 87 }] (السطر 36) بعد ثانية واحدة (السطر 31) بفضل الدالة [resolve] التي تشير إلى نجاح الدالة [Promise]. يتم تضمين الكائن الذي تعرضه الدالة غير المتزامنة تلقائيًا في خصائص [data] للصفحة. وبالتالي، فإن السطر 7 يعرض هذه الخاصية؛
- السطر 27: سنكتشف أن الدالة [asyncData] يتم تنفيذها بواسطة الخادم وليس بواسطة العميل؛
- السطر 29: يتم تهيئة الخاصية [value] بواسطة الخادم؛
ملاحظة: الكائن [this] غير معروف في الدالة [asyncData] لأن الكائن الذي يغلف المكون [vue] لم يتم إنشاؤه بعد؛
5.3.2. التنفيذ
يتم تشغيل المشروع [nuxt-02]، ثم يتم إدخال العنوان [localhost:81/nuxt-02/page2] يدويًّا لتوجيه الطلب إلى الخادم. وكما حدث عند بدء تشغيل الصفحة [index]:
- يقوم الخادم بتنفيذ الصفحة [page2.vue]؛
- يرسل الصفحة التي تم إنشاؤها إلى المتصفح. يتم عرض هذه الصفحة؛
- تتولى البرامج النصية للعميل المضمنة في الصفحة المرسلة زمام الأمور وتنفذ الصفحة [page2.vue] مرة أخرى؛
- ثم يتم تعديل الصفحة المعروضة؛
والنتيجة النهائية هي كما يلي:

هذه المرة، القيمة المعروضة هي بالفعل تلك التي حددها الخادم، وبصريًا، لا نرى الصفحة "تتذبذب" بسبب تغيير العميل للقيمة المعروضة من قبل الخادم. ماذا حدث هذه المرة؟
قام الخادم بتنفيذ الصفحة التالية [page2]:
<!-- الصفحة 2 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة -->
<b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page2',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
asyncData(context) {
// من الذي ينفذ هذا الكود؟
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// للخادم فقط
if (process.server) {
// نقوم بإرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما يكون لدينا هنا دالة غير متزامنة
// نحاكيها بانتظار لمدة ثانية
setTimeout(() => {
// سيتم تضمين هذه النتيجة في خصائص [data]
resolve({ value: 87 })
// سجل
console.log('asynData terminée')
}, 1000)
})
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page2 beforeCreate]')
},
created() {
// العميل والخادم
console.log('[page2 created]')
},
beforeMount() {
// العميل فقط
console.log('[page2 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page2 mounted]')
}
}
</script>
السطر 36 هو الذي حدد القيمة المعروضة في السطر 7. وهذا هو ما تلقّاه متصفح العميل. وبالتحديد، تلقى الصفحة التالية:
<!doctype html>
<html data-n-head-ssr>
<head>
<title>Introduction à [nuxt.js]</title>
<meta data-n-head="ssr" charset="utf-8">
<meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
<meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
<link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
<base href="/nuxt-02/">
<link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
...
</head>
<body>
<div data-server-rendered="true" id="__nuxt">
<div id="__layout">
<div class="container">
<div class="card">
<div class="card-body">
<div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
<h4>[nuxt-02] : page serveur, page client</h4>
</div>
<div>
<div class="row">
<div class="col-2">
<ul class="nav flex-column">
<li class="nav-item">
<a href="/nuxt-02/" target="_self" class="nav-link">
Home
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page1" target="_self" class="nav-link">
Page 1
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page2" target="_self" class="nav-link active nuxt-link-active">
Page 2
</a>
</li>
</ul>
</div>
<div class="col-10">
<div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-secondary">
Page 2 - value = 87
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.__NUXT__ = (function (a, b, c) {
return {
layout: "default", data: [{ value: 87 }], error: null, state: { counter: 0 }, serverRendered: true,
logs: [
{ date: new Date(1574096608555), args: ["asyncData, client=", "false", "serveur=", "true"], type: a, level: b, tag: c },
{ date: new Date(1574096608575), args: ["[page2 beforeCreate]"], type: a, level: b, tag: c },
{ date: new Date(1574096608599), args: ["[page2 created]"], type: a, level: b, tag: c }
]
}
}("log", 2, ""));</script>
<script src="/nuxt-02/_nuxt/runtime.js" defer></script>
<script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
<script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
<script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
- السطر 48: نرى أن القيمة في الصفحة المستلمة هي 87؛
- السطر 61: في استجابة الخادم، نرى كائنين: [data] و [state]:
- [state] هو حالة مخزن [Vuex]. وقد تم إنشاء مثيل لهذا الأخير من محتوى المجلد [store] للتطبيق [nuxt-02]؛
- يحتوي [data] على الخصائص التي أنشأها الخادم باستخدام الدالة [asyncData]. نجد الخاصية [value : 87] التي أنشأها الخادم. ستقوم نصوص العميل بدمج هذه الخاصية في خصائص الصفحة [page2]؛
لنعد إلى كود الصفحة [page2]:
<!-- الصفحة 2 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة -->
<b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page2',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
asyncData(context) {
// من ينفذ هذا الكود؟
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// للخادم فقط
if (process.server) {
// نقوم بإرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما تكون لدينا هنا وظيفة غير متزامنة
// نحاكيها بانتظار لمدة ثانية
setTimeout(() => {
// سيتم تضمين هذه النتيجة في خصائص [data]
resolve({ value: 87 })
// سجل
console.log('asynData terminée')
}, 1000)
})
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page2 beforeCreate]')
},
created() {
// العميل والخادم
console.log('[page2 created]')
},
beforeMount() {
// العميل فقط
console.log('[page2 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page2 mounted]')
}
}
</script>
- يستخدم السطر 7 الخاصية [value]. لكن الصفحة لا تحدد أي خاصية باسم [value]. ومع ذلك، أنشأت نصوص العميل هذه الخاصية تلقائيًا بفضل الكائن [data: [{ value: 87 }]] الذي تم استلامه من الخادم؛
تُظهر السجلات أيضًا أن الوظيفة [asyncData] لم يتم تنفيذها من قبل العميل:

تم تنفيذ الدالة [asyncData] بواسطة الخادم [1] ولكن لم يتم تنفيذها بواسطة العميل [2]. علاوة على ذلك، يمكن ملاحظة أن وظائف دورة الحياة لا يتم تنفيذها من قبل الخادم قبل انتهاء الوظيفة [asyncData]. يمكن زيادة مدة الانتظار داخل الوظيفة [asyncData] للتحقق من ذلك.
5.4. الصفحة [page3]
نضيف صفحة جديدة [page3] إلى تطبيقنا:

5.4.1. المكون [navigation]
تم تعديل المكون [vavigation] للسماح بالتنقل إلى الصفحة الجديدة:
<template>
<!-- قائمة Bootstrap ذات الخيارات الثلاثة -->
<b-nav vertical>
<b-nav-item to="/" exact exact-active-class="active">
Home
</b-nav-item>
<b-nav-item to="/page1" exact exact-active-class="active">
Page 1
</b-nav-item>
<b-nav-item to="/page2" exact exact-active-class="active">
Page 2
</b-nav-item>
<b-nav-item to="/page3" exact exact-active-class="active">
Page 3
</b-nav-item>
</b-nav>
</template>
5.4.2. كود [page3]
رمز الصفحة [page3] هو التالي:
<!-- الصفحة 3 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة -->
<b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page3',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// من الذي يقوم بتنفيذ هذا الرمز؟
console.log('fetch, client=', process.client, 'serveur=', process.server)
// للخادم فقط
if (process.server) {
// يتم إرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما تكون لدينا هنا دالة غير متزامنة
// نحاكيها بانتظار لمدة ثانية
setTimeout(() => {
// نجاح
resolve()
}, 1000)
}).then(() => {
// يتم تعديل المخزن
context.store.commit('increment', 28)
})
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page3 beforeCreate]')
},
created() {
// العميل والخادم
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[page3 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page3 mounted]')
}
}
</script>
- السطر 30: تعمل الدالة [fetch] بشكل مشابه للدالة [asyncData]:
- يتم تنفيذها قبل وظائف دورة الحياة؛
- الكائن [this] غير معروف في هذه الوظيفة؛
- يعمل بشكل غير متزامن؛
- لا يبدأ دورة الحياة حتى تقوم الدالة غير المتزامنة بإرجاع نتيجتها؛
- يتم إرجاع النتيجة هنا بواسطة الطريقة [then] من [Promise]، السطر 43؛
- تستقبل الدالة [fetch] المعلمة [context]. تمثل هذه المعلمة السياق [nuxt] في الوقت الحالي؛
- السطر 30: من بين خصائصه العديدة، يمتلك الكائن [context] خاصية [store] التي تمثل مخزن [Vuex] للتطبيق؛
- السطر 41: بشكل مصطنع، يتم الإبلاغ عن نجاح [Promise] بعد ثانية واحدة (انظر الوثيقة |مقدمة إلى لغة ECMASCRIPT 6 من خلال الأمثلة|)؛
- السطر 45: يتم بعد ذلك تنفيذ الطريقة [then]. يتم فيها زيادة عداد [store]؛
5.4.3. التنفيذ
يتم تنفيذ المشروع [nuxt-02] ويتم كتابة [localhost:81/nuxt-02/page3] يدويًا حتى يتم استدعاء الخادم. كما هو الحال عند بدء تشغيل الصفحة [index]:
- يقوم الخادم بتنفيذ الصفحة [page3.vue]؛
- يرسل الصفحة التي تم إنشاؤها إلى المتصفح. يتم عرض هذه الصفحة؛
- تتولى البرامج النصية للعميل المضمنة في الصفحة المرسلة زمام الأمور وتنفذ الصفحة [page3.vue] مرة أخرى؛
- ثم يتم تعديل الصفحة المعروضة؛
والنتيجة النهائية هي كما يلي:

القيمة المعروضة هي بالفعل تلك التي حددها الخادم، وبصريًا، لا نرى الصفحة "تتذبذب" بسبب تغيير العميل للقيمة التي يعرضها الخادم. ماذا حدث هذه المرة؟
قام الخادم بتنفيذ الصفحة التالية [page3]:
<!-- الصفحة 3 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة -->
<b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page3',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// من الذي ينفذ هذا الكود؟
console.log('fetch, client=', process.client, 'serveur=', process.server)
// للخادم فقط
if (process.server) {
// نقوم بإرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما يكون لدينا هنا دالة غير متزامنة
// نحاكيها بانتظار لمدة ثانية
setTimeout(() => {
// نجاح
resolve()
}, 1000)
}).then(() => {
// يتم تعديل المخزن
context.store.commit('increment', 28)
// سجل
console.log('fetch commit terminé')
})
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page3 beforeCreate]')
},
created() {
// العميل والخادم
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[page3 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page3 mounted]')
}
}
</script>
- السطر 45: الدالة غير المتزامنة [fetch] هي أول الدوال المذكورة أعلاه التي يتم تنفيذها. تتلقى كمعلمة كائنًا يسمى [context] وهو سياق [nuxt] في ذلك الوقت. من بين الخصائص العديدة لهذا الكائن، تمثل الخاصية [context.store] المخزن [Vuex]؛
- السطر 45: في الدالة غير المتزامنة [fetch]، يضبط الخادم عداد المخزن على 28؛
- السطر 56: عند تنفيذ الدالة [created]، تضمن [nuxt] أن الدالة غير المتزامنة [fetch] قد أنهت عملها؛
- السطر 58: يتم تعيين قيمة عداد المخزن إلى الخاصية [value] في السطر 27؛
- السطر 7: عرض قيمة [value]، وبالتالي عداد المخزن؛
يتلقى متصفح العميل الصفحة التالية:
<!doctype html>
<html data-n-head-ssr>
<head>
<title>Introduction à [nuxt.js]</title>
<meta data-n-head="ssr" charset="utf-8">
<meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
<meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
<link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
<base href="/nuxt-02/">
<link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
<link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
...
</head>
<body>
<div data-server-rendered="true" id="__nuxt">
<div id="__layout">
<div class="container">
<div class="card">
<div class="card-body">
<div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
<h4>[nuxt-02] : page serveur, page client</h4>
</div>
<div>
<div class="row">
<div class="col-2">
<ul class="nav flex-column">
<li class="nav-item">
<a href="/nuxt-02/" target="_self" class="nav-link">
Home
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page1" target="_self" class="nav-link">
Page 1
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page2" target="_self" class="nav-link">
Page 2
</a>
</li>
<li class="nav-item">
<a href="/nuxt-02/page3" target="_self" class="nav-link active nuxt-link-active">
Page 3
</a>
</li>
</ul>
</div> <div class="col-10">
<div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-secondary">
Page 3 - value = 28
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.__NUXT__ = (function (a, b, c) {
return {
layout: "default", data: [{}], error: null, state: { counter: 28 }, serverRendered: true,
logs: [
{ date: new Date(1574169916025), args: ["fetch, client=", "false", "serveur=", "true"], type: a, level: b, tag: c },
{ date: new Date(1574169917038), args: ["fetch commit terminé"], type: a, level: b, tag: c },
{ date: new Date(1574169917137), args: ["[page3 beforeCreate]"], type: a, level: b, tag: c },
{ date: new Date(1574169917167), args: ["[page3 created], value=", "28"], type: a, level: b, tag: c }
]
}
}("log", 2, ""));</script>
<script src="/nuxt-02/_nuxt/runtime.js" defer></script>
<script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
<script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
<script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
- السطر 52: نرى أن القيمة في الصفحة المستلمة هي 28؛
- السطر 65: في استجابة الخادم، نرى أن الخادم أرسل إلى العميل الحالة [state] للمتجر [Vuex]. بفضل هذه المعلومات، ستتمكن نصوص العميل من إعادة تكوين متجر [Vuex]؛
ستقوم نصوص العميل بدورها بتنفيذ كود الصفحة [page3]:
<!-- الصفحة 3 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة -->
<b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page3',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// من الذي ينفذ هذا الكود؟
console.log('fetch, client=', process.client, 'serveur=', process.server)
// للخادم فقط
if (process.server) {
// نقوم بإرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما يكون لدينا هنا دالة غير متزامنة
// نحاكيها بانتظار لمدة ثانية
setTimeout(() => {
// نجاح
resolve()
}, 1000)
}).then(() => {
// يتم تعديل المخزن
context.store.commit('increment', 28)
// سجل
console.log('fetch commit terminé')
})
}
},
// دورة الحياة
beforeCreate() {
// العميل والخادم
console.log('[page3 beforeCreate]')
},
created() {
// العميل والخادم
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[page3 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page3 mounted]')
}
}
</script>
- السطر 58: تقوم الدالة [created] التي ينفذها العميل بتعيين قيمة العداد إلى الخاصية [value] في السطر 27؛
- السطر 7 يعرض هذه القيمة. وبما أنها نفس القيمة التي أرسلها الخادم، لا نرى الصفحة "تتذبذب" بسبب أي تغيير؛
تُظهر السجلات أيضًا أن الدالة [fetch] لم يتم تنفيذها من قبل العميل:

تم تنفيذ الدالة [fetch] بواسطة الخادم [1] ولكن لم يتم تنفيذها بواسطة العميل [2]. علاوة على ذلك، يمكن ملاحظة أن وظائف دورة الحياة لا يتم تنفيذها من قبل الخادم قبل انتهاء الوظيفة [fetch] [3]. يمكن زيادة مدة الانتظار داخل الوظيفة [fetch] للتحقق من ذلك.
أظهرت الصفحتان [page1] و [page3] طريقتين تستخدمان المخزن [Vuex] لنقل المعلومات من الخادم إلى العميل. قد يتساءل المرء عما إذا كانتا متكافئتين. نقوم بإنشاء صفحة [page4] للتحقق من ذلك.
5.5. الصفحة [page4]
نضيف صفحة جديدة [page4] إلى تطبيقنا:

5.5.1. المكون [navigation]
تم تعديل المكون [navigation] للسماح بالتنقل إلى الصفحة الجديدة:
<template>
<!-- قائمة Bootstrap ذات الخيارات الخمسة -->
<b-nav vertical>
<b-nav-item to="/" exact exact-active-class="active">
Home
</b-nav-item>
<b-nav-item to="/page1" exact exact-active-class="active">
Page 1
</b-nav-item>
<b-nav-item to="/page2" exact exact-active-class="active">
Page 2
</b-nav-item>
<b-nav-item to="/page3" exact exact-active-class="active">
Page 3
</b-nav-item>
<b-nav-item to="/page4" exact exact-active-class="active">
Page 4
</b-nav-item>
</b-nav>
</template>
5.5.2. كود [page4]
رمز الصفحة [page4] هو التالي:
<!-- الصفحة 4 -->
<template>
<Layout :left="true" :right="true">
<!-- التنقل -->
<Navigation slot="left" />
<!-- رسالة -->
<b-alert slot="right" show variant="secondary"> Page 4 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page4',
// المكونات المستخدمة
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// دورة الحياة
async beforeCreate() {
// العميل والخادم
console.log('[page4 beforeCreate]')
// للخادم فقط
if (process.server) {
// يتم تنفيذ الوظيفة غير المتزامنة
const valeur = await new Promise(function(resolve, reject) {
// عادة ما تكون لدينا هنا وظيفة غير متزامنة
// يتم محاكاتها بانتظار لمدة 10 ثوانٍ
setTimeout(() => {
// نجاح - يتم إرجاع قيمة العداد
resolve(52)
}, 10000)
})
// يتم تعديل المخزن
this.$store.commit('increment', valeur)
// سجل
console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
}
},
created() {
// العميل والخادم
this.value = this.$store.state.counter
console.log('[page4 created], value=', this.value)
},
beforeMount() {
// العميل فقط
console.log('[page4 beforeMount]')
},
mounted() {
// العميل فقط
console.log('[page4 mounted]')
}
}
</script>
- السطر 30: ما كان يتم تنفيذه سابقًا في الدالة [fetch] يتم تنفيذه الآن في الطريقة [beforeCreate]. يتم استخدام الزوج async (السطر 30) / await (السطر 36) لانتظار انتهاء الدالة غير المتزامنة؛
- السطر 36: يتم استرداد نتيجة الدالة غير المتزامنة التي تم عرضها في السطر 41 بعد 10 ثوانٍ (السطر 42)؛
- الأسطر 50-54: في الطريقة [created] التي يتم تنفيذها على جانب الخادم وجانب العميل على حد سواء، يتم تعيين العداد إلى الخاصية [value] للصفحة؛
5.5.3. التنفيذ
يتم تنفيذ المشروع [nuxt-02] ويتم كتابة [localhost:81/nuxt-02/page4] يدويًا لطلب الخادم. كما هو الحال عند بدء تشغيل الصفحة [index]:
- يقوم الخادم بتنفيذ الصفحة [page4.vue]؛
- يرسل الصفحة التي تم إنشاؤها إلى المتصفح. يتم عرض هذه الصفحة؛
- تتولى البرامج النصية للعميل المضمنة في الصفحة المرسلة زمام الأمور وتنفذ الصفحة [page4.vue] مرة أخرى؛
- ثم يتم تعديل الصفحة المعروضة؛
والنتيجة النهائية هي كما يلي:

على عكس المتوقع، القيمة المعروضة في [2] ليست 52. ماذا حدث؟
السجلات هي كما يلي:

يمكن ملاحظة أنه في [1] لم يتم عرض سجل نهاية الإجراء غير المتزامن. تُظهر الدالة [created] التي تعرض قيمة العداد الرقم 0. كل هذا يشير إلى أن [nuxt] لم تنتظر انتهاء العملية غير المتزامنة.
إذا عدنا إلى محطة VSCode التي استُخدمت لتشغيل التطبيق، نجد سجلات [3-4]. نرى أن الوظيفة غير المتزامنة قد نُفذت بالفعل على جانب الخادم.
في النهاية، تم تنفيذ الدالة [beforeCreate] بالكامل على جانب الخادم، لكن [nuxt] لم تنتظر انتهاء تنفيذها لإرسال الصفحة إلى متصفح العميل، في حين أنها تنتظر بالفعل انتهاء الدالة [fetch]. لذلك، يجب استخدام هذه الطريقة إذا أردنا أن يقوم الخادم بتهيئة مخزن [Vuex].
5.6. التنقل في التطبيق [vue]
لقد أوضحنا ما يحدث عندما يتم تحميل كل صفحة من صفحات [index, page1, page2, page3, page4] مبدئيًا بواسطة الخادم. في الواقع، هذا ليس ما يحدث: في حالة التشغيل العادي، يتم البحث عن الصفحة [index] فقط على الخادم. لنلقِ نظرة على الصفحات الثلاث في هذه الحالة:
الصفحة [index]

لقد شرحنا هذا النتيجة بالفعل في الفقرة الخاصة بالرابط.
الآن، لنضغط على الرابط [Page 1]:

القيمة المعروضة هي 0. كانت 25 عندما تم طلب الصفحة لأول مرة من الخادم عن طريق كتابة URL يدويًا. التفسير بسيط. الرمز الذي تم تنفيذه هو التالي:
created() {
// العميل والخادم
console.log('[page1 created]')
// الخادم فقط
if (process.server) {
this.$store.commit('increment', 25)
}
// العميل والخادم
this.value = this.$store.state.counter
console.log('value=', this.value)
},
السطر 6 هو الذي كان يضع العداد على 25. وبما أن الصفحة لم تُطلب من الخادم، لم يتم تنفيذ الأسطر 5-7 وبقي عداد مخزن [Vuex] عند 0.
الآن، لنضغط على الرابط [Page 2]:

هذه المرة، لم يتم عرض أي قيمة، كما ظهرت تحذير في سجلات وحدة التحكم:

- في [1]، نكتشف أن الدالة [asyncData] قد تم تنفيذها من قبل العميل. الأمر دائمًا ما يكون على هذا النحو:
- يتم تنفيذها بواسطة الخادم إذا تم طلب الصفحة من الخادم. في هذه الحالة، لا يتم تنفيذها بواسطة العميل؛
- ثم في كل مرة تكون فيها الصفحة هي هدف المسار الحالي للعميل؛
- في [2]: تصدر [nuxt] تحذيرًا لأن قالب الصفحة يحتوي على تعبير تفاعلي {{ value }} في حين أن الصفحة لا تحتوي على خاصية [value]؛
لنتذكر الكود الذي ينفذه العميل:
asyncData() {
// من الذي ينفذ هذا الرمز؟
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// للخادم فقط
if (process.server) {
// يتم إرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما تكون لدينا هنا وظيفة غير متزامنة - ولكن ليس هنا
// سيتم تضمين هذه النتيجة في خصائص [data]
resolve({ value: 87 })
})
}
},
- أظهر السطر 3 أن الدالة [asyncData] تم تنفيذها من قبل العميل حتى قبل دورات حياة الدالة؛
- السطر 5 منع تنفيذ بقية الكود الذي أنشأ الخاصية [value] لأن هذا الكود يتم تنفيذه من قبل العميل؛
الآن ننتقل إلى الصفحة [page3]:

- في [2]، كان لدينا 28 عندما تم توفير الصفحة من قبل الخادم. هنا ليس هذا هو الحال؛
- في [4]، نرى أن الدالة [fetch] قد تم تنفيذها من جانب العميل؛
دعونا نفحص الكود الذي نفذه العميل:
...
fetch(context) {
// من الذي ينفذ هذا الكود؟
console.log('fetch, client=', process.client, 'serveur=', process.server)
// فقط للخادم
if (process.server) {
// يتم إرجاع وعد
return new Promise(function(resolve, reject) {
// عادة ما يكون لدينا هنا دالة غير متزامنة
// نحاكيها بانتظار لمدة ثانية
setTimeout(() => {
// نجاح
resolve()
}, 1000)
}).then(() => {
// يتم تعديل المخزن
context.store.commit('increment', 28)
// سجل
console.log('fetch commit terminé')
})
}
},
...
- يقوم العميل بتنفيذ الطريقة [fetch]، السطر 2؛
- لا يتم تنفيذ الأسطر 6-21 لأن الشرط [process.server] غير صحيح. وبالتالي، لا يتم تنفيذ السطر 17 الذي يضع العداد على 28. ويبقى عند الصفر. ولهذا السبب يعرض العميل 0 بدلاً من 28؛
لننتقل الآن إلى الصفحة [page4]. نحصل على النتيجة التالية:

- في [2]، قيمة العداد؛
- في [3]، سجلات العميل؛
الرمز الذي ينفذه العميل هو التالي:
...
data() {
return {
value: 0
}
},
// دورة الحياة
async beforeCreate() {
// العميل والخادم
console.log('[page4 beforeCreate]')
// للمخادم فقط
if (process.server) {
// يتم تنفيذ الدالة غير المتزامنة
const valeur = await new Promise(function(resolve, reject) {
// عادةً ما تكون لدينا هنا دالة غير متزامنة
// يتم محاكاتها بفترة انتظار مدتها 10 ثوانٍ
setTimeout(() => {
// نجاح - يتم إرجاع قيمة العداد
resolve(52)
}, 10000)
})
// يتم تعديل المخزن
this.$store.commit('increment', valeur)
// سجل
console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
}
},
created() {
// العميل والخادم
this.value = this.$store.state.counter
console.log('[page4 created], value=', this.value)
},
...
- لا يتم تنفيذ الأسطر 12-26 من قبل العميل، لأن الشرط [process.server] في السطر 12 غير صحيح. وبالتالي فإن عداد المخزن [Vuex] يساوي 0 (قيمته الأولية في المخزن) وهذه هي القيمة التي تعرضها الصفحة؛
قد يتساءل المرء عما يحدث عند تعليق [if] في السطرين 12 و 26. وإليك الإجابة:
- يقوم العميل هذه المرة بتنفيذ الأسطر 14-25، لكن [nuxt] لا ينتظر انتهاء الدالة غير المتزامنة (كما هو الحال بالنسبة للخادم)، وبالتالي يترك العداد عند 0؛
- بعد 10 ثوانٍ، تنتهي الوظيفة غير المتزامنة ويتم تعيين العداد على 52 في السطر 23؛
- عند الانتقال مرة أخرى إلى الصفحة [page4]، يتم عرض القيمة 52؛
5.7. Résumé
من تجاربنا المختلفة، نستخلص النقاط التالية:
- إذا كانت الصفحة [index] تحتوي على بيانات خارجية، فيمكن للخادم البحث عنها باستخدام الدالة [asyncData]؛
- إذا كان على الخادم تهيئة مخزن [Vuex] ببيانات خارجية عند تحميل الصفحة [index]، فسيفعل ذلك في الوظيفة [fetch]؛
- يجب أن تكون الصفحة التي تم إنشاؤها بواسطة الخادم والصفحة التي تم إنشاؤها بواسطة العميل متطابقتين إذا أردنا تجنب تأثير "الارتعاش" الناتج عن حقيقة أن صفحة العميل تحل محل الصفحة التي أرسلها الخادم وعُرضت في البداية؛
سنكتشف جوانب أخرى من [nuxt] من خلال مثال جديد.