Skip to content

5. مثال [nuxt-02]: صفحات الخادم والعميل

في هذا المشروع، نوضح ما يلي:

  • أن الصفحة التي أنشأها العميل قد تبدو مختلفة عن تلك التي تم استلامها من الخادم. ينتج عن ذلك تغيير سريع في الصفحة يلاحظه المستخدم، مما يضر بقابلية استخدام التطبيق. لذلك، يُفضل تجنب هذا الخيار؛
  • حل لصفحة جانب العميل لإعادة إنشاء نفس الصفحة التي أرسلها الخادم؛

يتم إنشاء مشروع [nuxt-02] في البداية عن طريق استنساخ مشروع [nuxt-01].

Image

يتم إضافة مجلد [store] إلى المشروع، إلى جانب صفحتين جديدتين. سنعود إلى هذا لاحقًا.

5.1. صفحة [index]

5.1.1. كود الصفحة

يصبح كود صفحة [index] كما يلي:


<!-- page principale -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[home beforeCreate]')
  },
  created() {
    // client and server
    console.log('[home created]')
    // server only
    if (process.server) {
      this.value = 10
    }
    // client and server
    console.log('value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[home beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[home mounted]')
  }
}
</script>

تعليقات

  • السطر 7: ستعرض صفحة [index] قيمة خاصية [value] الخاصة بها (السطر 28)؛
  • الأسطر 36–45: من المهم أن نتذكر هنا أن الدالة [created] يتم تنفيذها على كل من الخادم والعميل. الأسطر 40–42: سيقوم الخادم بتعيين قيمة الخاصية [value] إلى 10. لكن العميل لا يقوم بتعديل هذه القيمة. نحن نريد ببساطة معرفة ما إذا كان العميل يحتفظ بهذه القيمة. وسنجد أنه لا يحتفظ بها؛

5.1.2. التنفيذ

نقوم بتعديل ملف [/nuxt.config.js] لتشغيل مشروع [nuxt-02]:


...
  // source code directory
  srcDir: 'nuxt-02',
  // router
  router: {
    // application URL root
    base: '/nuxt-02/'
  },
  // server
  server: {
    // service port, default 3000
    port: 81,
    // network addresses listened to, default localhost: 127.0.0.1
    // 0.0.0.0 = all the machine's network addresses
    host: 'localhost'
  }
...

نقوم بتشغيل المشروع [1]:

Image

ثم يتم عرض صفحة [index] [2-3]. تعرض الصفحة القيمة [10] لبضع لحظات ثم تعرض القيمة [0]. ماذا حدث؟

الخطوة 1

يتم تشغيل الخادم أولاً. ويقوم بتنفيذ الكود الموجود في صفحة [index]:


export default {
  name: 'Home',
  // components used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[home beforeCreate]')
  },
  created() {
    // client and server
    console.log('[home created]')
    // server only
    if (process.server) {
      this.value = 10
    }
    // client and server
    console.log('value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[home beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[home mounted]')
  }
}
  • بسبب السطر 23، تأخذ الخاصية [value] في السطر 10 القيمة 10؛

يمكنك التحقق من ذلك من خلال الاطلاع على الكود المصدري للصفحة التي استقبلها المتصفح (خيار [عرض المصدر] في المتصفح):


<!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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[home beforeCreate]')
  },
  created() {
    // client and server
    console.log('[home created]')
    // server only
    if (process.server) {
      this.value = 10
    }
    // client and server
    console.log('value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[home beforeMount]')
  },
  mounted() {
    // customer only
    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]:

Image

يؤدي وجود هذا المجلد إلى قيام [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

يتوفر [المخزن] Vuex الذي تم تنفيذه بواسطة [nuxt] في أماكن مختلفة. في العروض، يتوفر في الخاصية [this.$store].

5.2.2. كود الصفحة

مثل صفحة [index]، ستعرض صفحة [page1] قيمة: العداد من مخزن Vuex:


<!-- page 1 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[home beforeCreate]')
  },
  created() {
    // client and server
    console.log('[home created]')
    // server only
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // client and server
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[home beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[home mounted]')
  }
}
</script>

تعليقات

  • الأسطر 38–40: سيقوم الخادم بزيادة العداد بمقدار 25؛
  • السطر 42: سيعرض كل من الخادم والعميل قيمة العداد؛
  • السطر 7: يتم عرض قيمة العداد؛

عند قراءة هذا الكود، يجب فهم أمرين:

  • الرمز الذي يتم تنفيذه هو نفسه لكل من الخادم والعميل؛
  • كائن [this] ليس هو نفسه: هناك [this] من جانب الخادم و[this] من جانب العميل؛

نريد معرفة ما إذا كان [this.$store] الخاص بالخادم هو نفسه [this.$store] الخاص بالعميل. نظرًا لأن الخادم يعمل أولاً (عند بدء تشغيل التطبيق)، فإن هذا يتلخص في السؤال التالي: هل يتم تمرير [store] الذي تم تهيئته بواسطة الخادم إلى العميل؟

5.2.3. التنفيذ

نقوم بتشغيل مشروع [nuxt-02] ونكتب يدويًا [localhost:81/nuxt-02/page1] لتشغيل الخادم. كما هو الحال مع صفحة [index] عند بدء التشغيل:

  • يقوم الخادم بتنفيذ الصفحة [page1.vue
  • يرسل الصفحة التي تم إنشاؤها إلى المتصفح. يتم عرض الصفحة؛
  • تتولى البرامج النصية المضمنة في الصفحة المرسلة من جانب العميل زمام الأمور وتنفذ صفحة [page1.vue] مرة أخرى؛
  • ثم يتم تعديل الصفحة المعروضة؛

والنتيجة النهائية هي كما يلي:

Image

هذه المرة، القيمة المعروضة هي بالفعل تلك التي حددها الخادم، وبصريًا، لا "تقفز" الصفحة بسبب تغيير من جانب العميل للقيمة التي يعرضها الخادم. ماذا حدث هذه المرة؟

نفذ الخادم الصفحة التالية [page1]:


...
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'Page1',
  // components used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page1 beforeCreate]')
  },
  created() {
    // client and server
    console.log('[page1 created]')
    // server only
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // client and server
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // customer only
    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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page1 beforeCreate]')
  },
  created() {
    // client and server
    console.log('[page1 created]')
    // server only
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // client and server
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[page1 mounted]')
  }
}
</script>
  • السطر 34: يتم تعيين القيمة 25 من العداد لخاصية [value] الموجودة في السطر 18؛

وبالتالي، يسمح مخزن [nuxt] للخادم بنقل المعلومات إلى العميل أثناء التحميل الأولي للصفحة، عندما يتم طلب الصفحة من الخادم. لاحظ أنه بمجرد الحصول على هذه الصفحة، لا يعود الخادم متورطًا، وتعمل التطبيق كأحد تطبيقات [vue] الكلاسيكية، في وضع الصفحة الواحدة.

5.3. الصفحة [page2]

في صفحة [page2]، نعرض طريقة أخرى لـ:

  • يضمّن الخادم المعلومات المحسوبة في الصفحة؛
  • لا يقوم العميل بتعديل هذه المعلومات؛

5.3.1. كود الصفحة

يتغير كود الصفحة [page2] على النحو التالي:


<!-- page2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <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 used
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // who executes this code?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a one-second wait
        setTimeout(() => {
          // this result will be included in the properties of [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page2 beforeCreate]')
  },
  created() {
    // client and server
    console.log('[page2 created]')
  },
  beforeMount() {
    // customer only
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // customer only
    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] مرة أخرى؛
  • ثم يتم تعديل الصفحة المعروضة؛

والنتيجة النهائية هي كما يلي:

Image

هذه المرة، القيمة المعروضة هي بالفعل تلك التي حددها الخادم، وبصريًا، لا "تقفز" الصفحة بسبب تغيير من جانب العميل للقيمة التي يعرضها الخادم. ماذا حدث هذه المرة؟

نفذ الخادم الصفحة التالية [page2]:


<!-- page2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <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 used
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // who executes this code?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a one-second wait
        setTimeout(() => {
          // this result will be included in the properties of [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page2 beforeCreate]')
  },
  created() {
    // client and server
    console.log('[page2 created]')
  },
  beforeMount() {
    // customer only
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // customer only
    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]:


<!-- page2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <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 used
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // who executes this code?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a one-second wait
        setTimeout(() => {
          // this result will be included in the properties of [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page2 beforeCreate]')
  },
  created() {
    // client and server
    console.log('[page2 created]')
  },
  beforeMount() {
    // customer only
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[page2 mounted]')
  }
}
</script>
  • يستخدم السطر 7 الخاصية [value]. ومع ذلك، لا تحدد الصفحة أي خاصية باسم [value]. قامت البرامج النصية من جانب العميل بإنشاء هذه الخاصية تلقائيًا باستخدام الكائن [data: [{ value: 87 }]] الذي تم استلامه من الخادم؛

تُظهر السجلات أيضًا أن الدالة [asyncData] لم يتم تنفيذها من قِبل العميل:

Image

تم تنفيذ الدالة [asyncData] من قبل الخادم [1] ولكن لم يتم تنفيذها من قبل العميل [2]. علاوة على ذلك، تجدر الإشارة إلى أن دوال دورة الحياة لا يتم تنفيذها من قبل الخادم حتى تنتهي الدالة [asyncData]. يمكننا زيادة وقت الانتظار داخل الدالة [asyncData] للتحقق من ذلك.

5.4. الصفحة [page3]

نقوم بإضافة صفحة جديدة [page3] إلى تطبيقنا:

Image

5.4.1. مكون [navigation]

تم تعديل مكون [navigation] للسماح بالانتقال إلى الصفحة الجديدة:


<template>
  <!-- bootstrap menu with three options -->
  <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] هو كما يلي:


<!-- page3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // who executes this code?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a one-second wait
        setTimeout(() => {
          // success
          resolve()
        }, 1000)
      }).then(() => {
        // modify the blind
        context.store.commit('increment', 28)
      })
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page3 beforeCreate]')
  },
  created() {
    // client and server
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // customer only
    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] مرة أخرى؛
  • ثم يتم تعديل الصفحة المعروضة؛

والنتيجة النهائية هي كما يلي:

Image

القيمة المعروضة هي بالفعل تلك التي حددها الخادم، وبصريًا، لا "تقفز" الصفحة بسبب تغيير من جانب العميل للقيمة التي يعرضها الخادم. ماذا حدث هذه المرة؟

نفذ الخادم الصفحة التالية [page3]:


<!-- page3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // who executes this code?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a one-second wait
        setTimeout(() => {
          // success
          resolve()
        }, 1000)
      }).then(() => {
        // modify the blind
        context.store.commit('increment', 28)
        // log
        console.log('fetch commit terminé')
      })
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page3 beforeCreate]')
  },
  created() {
    // client and server
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // customer only
    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: في استجابة الخادم، نرى أن الخادم أرسل حالة مخزن [Vuex] إلى العميل. باستخدام هذه المعلومات، ستتمكن نصوص العميل البرمجية من إعادة بناء مخزن [Vuex

وستقوم نصوص العميل بدورها بتنفيذ كود الصفحة [page3]:


<!-- page3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // who executes this code?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a one-second wait
        setTimeout(() => {
          // success
          resolve()
        }, 1000)
      }).then(() => {
        // modify the blind
        context.store.commit('increment', 28)
        // log
        console.log('fetch commit terminé')
      })
    }
  },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[page3 beforeCreate]')
  },
  created() {
    // client and server
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[page3 mounted]')
  }
}
</script>
  • السطر 58: تقوم الدالة [created] التي ينفذها العميل بتعيين قيمة العداد إلى الخاصية [value] الموجودة في السطر 27؛
  • السطر 7 يعرض هذه القيمة. وبما أنها مطابقة لتلك التي أرسلها الخادم، فإننا لا نرى "قفزة" في الصفحة بسبب التغيير؛

تُظهر السجلات أيضًا أن الدالة [fetch] لم يتم تنفيذها من قِبل العميل:

Image

تم تنفيذ وظيفة [fetch] بواسطة الخادم [1] ولكن لم يتم تنفيذها بواسطة العميل [2]. علاوة على ذلك، لاحظ أن وظائف دورة الحياة لا يتم تنفيذها بواسطة الخادم حتى تنتهي وظيفة [fetch] [3]. يمكننا زيادة وقت الانتظار داخل وظيفة [fetch] للتحقق من ذلك.

أظهرت الصفحتان [page1] و [page3] طريقتين لاستخدام مخزن [Vuex] لنقل المعلومات من الخادم إلى العميل. قد يتساءل المرء عما إذا كانتا متكافئتين. سننشئ صفحة [page4] للتحقق من ذلك.

5.5. الصفحة [page4]

نقوم بإضافة صفحة جديدة [page4] إلى تطبيقنا:

Image

5.5.1. مكون [navigation]

تم تعديل مكون [navigation] للسماح بالتنقل إلى الصفحة الجديدة:


<template>
  <!-- bootstrap menu with five options -->
  <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] هو كما يلي:


<!-- page4 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <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 used
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // life cycle
  async beforeCreate() {
    // client and server
    console.log('[page4 beforeCreate]')
    // only for the server
    if (process.server) {
      // execute the asynchronous function
      const valeur = await new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a 10-second wait
        setTimeout(() => {
          // success - the counter value is returned
          resolve(52)
        }, 10000)
      })
      // modify the blind
      this.$store.commit('increment', valeur)
      // log
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // client and server
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
  beforeMount() {
    // customer only
    console.log('[page4 beforeMount]')
  },
  mounted() {
    // customer only
    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] مرة أخرى؛
  • ثم يتم تعديل الصفحة المعروضة؛

والنتيجة النهائية هي كما يلي:

Image

خلافًا للتوقعات، القيمة المعروضة في [2] ليست 52. ماذا حدث؟

السجلات هي كما يلي:

Image

يمكننا أن نرى أنه في [1]، لم يتم عرض السجل الذي يشير إلى نهاية الإجراء غير المتزامن. تُظهر الدالة [created]، التي تعرض قيمة العداد، الرقم 0. كل هذا يشير إلى أن [nuxt] لم تنتظر انتهاء الإجراء غير المتزامن.

إذا عدنا إلى محطة VSCode المستخدمة لتشغيل التطبيق، نجد السجلات [3-4]. يمكننا أن نرى أن الدالة غير المتزامنة قد نُفِّذت بالفعل على جانب الخادم.

في النهاية، تم بالفعل تنفيذ الدالة [beforeCreate] بالكامل على جانب الخادم، لكن [Nuxt] لم تنتظر انتهاء تنفيذها قبل إرسال الصفحة إلى متصفح العميل، على الرغم من أنها تنتظر اكتمال الدالة [fetch]. لذلك، هذه هي الطريقة التي يجب أن تستخدمها إذا كنت تريد أن يقوم الخادم بتهيئة مخزن [Vuex].

5.6. التنقل في تطبيق [Vue]

لقد أوضحنا ما يحدث عندما يتم تحميل كل صفحة من الصفحات [index، page1، page2، page3، page4] مبدئيًا بواسطة الخادم. في الواقع، هذا ليس ما يحدث: في ظل التشغيل العادي، يتم جلب صفحة [index] فقط من الخادم. دعونا نلقي نظرة على الصفحات الثلاث في هذه الحالة:

صفحة [index]

Image

لقد أوضحنا هذه النتيجة بالفعل في قسم "الرابط".

الآن، لنضغط على رابط [الصفحة 1]:

Image

القيمة المعروضة هي 0. كانت 25 عندما تم طلب الصفحة لأول مرة من الخادم عن طريق كتابة عنوان URL يدويًا. التفسير بسيط. يتم تنفيذ الكود على النحو التالي:


  created() {
    // client and server
    console.log('[page1 created]')
    // server only
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // client and server
    this.value = this.$store.state.counter
    console.log('value=', this.value)
},

السطر 6 هو الذي قام بتعيين العداد على 25. وبما أن الصفحة لم تُطلب من الخادم، لم يتم تنفيذ الأسطر من 5 إلى 7، وبقي العداد في مخزن [Vuex] عند 0.

الآن، دعونا نضغط على رابط [Page 2]:

Image

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

Image

  • في [1]، نرى أن الدالة [asyncData] تم تنفيذها من قبل العميل. وهذا هو الحال دائمًا:
    • يتم تنفيذها بواسطة الخادم إذا تم طلب الصفحة من الخادم. في هذه الحالة، لا يتم تنفيذها بواسطة العميل؛
    • ثم في كل مرة تكون فيها الصفحة هي هدف المسار الحالي للعميل؛
  • في [2]: يصدر [nuxt] تحذيرًا لأن قالب الصفحة يحتوي على تعبير تفاعلي {{ value }}، على الرغم من أن الصفحة لا تحتوي على خاصية [value

دعونا نراجع الكود الذي نفذه العميل:


asyncData() {
    // who executes this code?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function - so not here
        // this result will be included in the properties of [data]
        resolve({ value: 87 })
      })
    }
  },
  • أظهر السطر 3 أن الدالة [asyncData] تم تنفيذها من قبل العميل حتى قبل دورات حياة الدالة؛
  • السطر 5 منع تنفيذ بقية الكود الذي أنشأ الخاصية [value] لأن هذا الكود يتم تنفيذه من قبل العميل؛

الآن دعونا ننتقل إلى الصفحة [page3]:

Image

  • في [2]، كان لدينا 28 عندما تم تقديم الصفحة من قبل الخادم. هذا ليس هو الحال هنا؛
  • في [4]، نرى أن وظيفة [fetch] تم تنفيذها على جانب العميل؛

دعونا نفحص الكود الذي نفذه العميل:


...
fetch(context) {
    // who executes this code?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // only for the server
    if (process.server) {
      // we return a promise
      return new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a one-second wait
        setTimeout(() => {
          // success
          resolve()
        }, 1000)
      }).then(() => {
        // modify the blind
        context.store.commit('increment', 28)
        // log
        console.log('fetch commit terminé')
      })
    }
},
...
  • يقوم العميل بتنفيذ طريقة [fetch]، السطر 2؛
  • لا يتم تنفيذ الأسطر من 6 إلى 21 لأن شرط [process.server] غير صحيح. لذلك، لا يتم تنفيذ السطر 17، الذي يضبط العداد على 28. ويبقى عند الصفر. وهذا هو السبب في أن العميل يعرض 0 بدلاً من 28؛

الآن دعونا ننتقل إلى الصفحة [page4]. نحصل على النتيجة التالية:

Image

  • في [2]، قيمة العداد؛
  • في [3]، سجلات العميل؛

الرمز الذي ينفذه العميل هو كما يلي:


...
data() {
    return {
      value: 0
    }
  },
  // life cycle
  async beforeCreate() {
    // client and server
    console.log('[page4 beforeCreate]')
    // only for the server
    if (process.server) {
      // execute the asynchronous function
      const valeur = await new Promise(function(resolve, reject) {
        // this is normally an asynchronous function
        // we simulate it with a 10-second wait
        setTimeout(() => {
          // success - the counter value is returned
          resolve(52)
        }, 10000)
      })
      // modify the blind
      this.$store.commit('increment', valeur)
      // log
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // client and server
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
...
  • لا يتم تنفيذ الأسطر 12–26 من قبل العميل لأن شرط [process.server] في السطر 12 هو false. لذلك، فإن العداد في مخزن [Vuex] يساوي 0 (قيمته الأولية في المخزن)، وهذه هي القيمة المعروضة على الصفحة؛

قد تتساءل عما يحدث عند تعليق عبارات [if] في السطرين 12 و26. وإليك الإجابة:

  • هذه المرة، يقوم العميل بتنفيذ الأسطر 14–25، لكن [nuxt] لا ينتظر انتهاء الدالة غير المتزامنة (كما يفعل على الخادم) وبالتالي يترك العداد عند 0؛
  • بعد 10 ثوانٍ، تكتمل الدالة غير المتزامنة ويتم تعيين العداد على 52 في السطر 23؛
  • عند العودة إلى صفحة [page4]، يتم عرض القيمة 52؛

5.7. ملخص

من خلال اختباراتنا المختلفة، يمكننا استنتاج ما يلي:

  • إذا كان يجب أن تحتوي صفحة [index] على بيانات خارجية، فيمكن للخادم جلب هذه البيانات باستخدام الدالة [asyncData
  • إذا احتاج الخادم إلى تهيئة مخزن [Vuex] ببيانات خارجية عند تحميل صفحة [index]، فسيقوم بذلك في دالة [fetch
  • يجب أن تكون الصفحة التي تم إنشاؤها بواسطة الخادم والصفحة التي تم إنشاؤها بواسطة العميل متطابقتين لتجنب تأثير "الارتعاش" الناتج عن استبدال الصفحة المرسلة من الخادم والمعروضة مبدئيًا بصريًا بصفحة من جانب العميل؛

سنستكشف جوانب أخرى من [Nuxt] من خلال مثال جديد.