Skip to content

5. مثال [nuxt-02]: صفحات سرور و کلاینت

در این پروژه، ما نشان می‌دهیم:

  • اینکه صفحه‌ای که توسط کلاینت رندر می‌شود ممکن است با آنچه از سرور دریافت شده متفاوت به نظر برسد. این امر منجر به تغییر سریع صفحه می‌شود که برای کاربر قابل توجه است و در نتیجه به قابلیت استفاده از برنامه آسیب می‌رساند. بنابراین این رویکردی است که باید از آن اجتناب کرد؛
  • یک راه‌حل که در آن صفحه سمت کلاینت، همان صفحه‌ای را که توسط سرور ارسال شده بازسازی می‌کند؛

پروژه [nuxt-02] در ابتدا با کلون کردن پروژه [nuxt-01] ایجاد شد.

Image

یک پوشه با نام [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>

نظرات

  • خط ۷: صفحه [index] مقدار ویژگی [value] (خط ۲۸) را نمایش خواهد داد؛
  • خطوط ۳۶–۴۵: در اینجا مهم است به یاد داشته باشیم که تابع [created] هم در سمت سرور و هم در سمت کلاینت اجرا می‌شود. در خطوط ۴۰–۴۲، سرور مقدار ویژگی [value] را روی ۱۰ تنظیم می‌کند. با این حال، کلاینت این مقدار را تغییر نمی‌دهد. ما صرفاً می‌خواهیم بدانیم آیا این مقدار توسط کلاینت حفظ می‌شود یا خیر. خواهیم دید که چنین نیست؛

5.1.2. اجرا

ما فایل [/nuxt.config.js] را برای اجرای پروژه [nuxt-02] تغییر می‌دهیم:


...
  // پوشهٔ کد منبع
  srcDir: 'nuxt-02',
  //روتر
  router: {
    // دایرکتوری ریشهٔ برنامه
    base: '/nuxt-02/'
  },
  // سرور
  server: {
    // پورت سرویس، به‌طور پیش‌فرض ۳۰۰۰
    port: 81,
    // آدرس‌های شبکه‌ای که گوش داده می‌شوند، پیش‌فرض localhost: 127.0.0.1
    // 0.0.0.0 = تمام آدرس‌های شبکه روی دستگاه
    host: 'localhost'
  }
...

ما پروژه [1] را اجرا می‌کنیم:

Image

صفحه [index] سپس به صورت [2-3] نمایش داده می‌شود. این صفحه برای چند لحظه مقدار [10] را نمایش می‌دهد، سپس مقدار [0] را نمایش می‌دهد. چه اتفاقی افتاده است؟

مرحله ۱

سرور ابتدا اجرا می‌شود. آن کد صفحه [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]')
  }
}
  • به دلیل خط ۲۳، خاصیت [value] در خط ۱۰ مقدار ۱۰ را می‌گیرد؛

این موضوع را می‌توان با بررسی کد منبع صفحه‌ای که توسط مرورگر دریافت شده است تأیید کرد (گزینه [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>
  • خط ۴۶: در صفحه دریافت‌شده، [value] مقدار ۱۰ را داشت؛

مرحله ۲

ما می‌دانیم که پس از دریافت صفحه، اسکریپت‌های خطوط ۵۷–۶۰ کنترل را به دست گرفته و رفتار صفحه دریافتی یا حتی اطلاعات نمایش‌داده‌شده را تغییر می‌دهند، همان‌طور که در اینجا نشان داده شده است. این اسکریپت‌ها کلاینت را تشکیل می‌دهند که همچنین کد صفحه [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] خطوط ۲۲–۲۴ را اجرا نخواهد کرد (process.server=false);
  • در یک برنامه استاندارد [vue]، مقدار [value] در خط ۱۰ همچنان روی 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] پیاده‌سازی می‌کند:

  • خطوط ۱–۳: تعریف وضعیت store با نام [state]. این وضعیت توسط یک تابع بازگردانده می‌شود. در اینجا، این وضعیت تنها یک ویژگی دارد: شمارنده در خط ۲. تابع صادرشده باید [state] نامیده شود؛
  • خطوط ۵–۹: عملیات‌هایی که می‌توان روی وضعیت فروشگاه انجام داد. این‌ها به عنوان [mutations] شناخته می‌شوند. در اینجا، متیشن [increment] مقدار ویژگی [counter] را به اندازه مقدار [inc] افزایش می‌دهد. شیء صادر شده باید با نام [mutations] نامگذاری شود؛

نما [store] که توسط [nuxt] پیاده‌سازی شده است، در مکان‌های مختلف در دسترس است. در نماها، این نما در خصوصیت [this.$store] در دسترس است.

5.2.2. کد صفحه

مانند صفحه [index]، صفحه [page1] مقداری را نمایش خواهد داد، یعنی مقدار شمارنده استور Vuex:


<!-- صفحهٔ ۱ -->
<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>

نظرات

  • خطوط ۳۸–۴۰: سرور شمارنده را ۲۵ واحد افزایش می‌دهد؛
  • خط ۴۲: هم سرور و هم کلاینت مقدار شمارنده را نمایش می‌دهند؛
  • خط ۷: مقدار شمارنده نمایش داده می‌شود؛

هنگام خواندن این کد، دو نکته را باید در نظر داشت:

  • کد اجرا شده برای سرور و کلاینت یکسان است؛
  • شیء [this] یکسان نیست: یک نسخه [this] در سمت سرور و نسخه دیگری، [client]، در سمت کلاینت وجود دارد؛

ما در تلاشیم تا مشخص کنیم آیا [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: {
    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>
  • خطوط ۳۰ تا ۳۲ بدون خطا اجرا شدند. این بدان معناست که در سمت سرور نیز، [this.$store] به فروشگاه [Vuex] اشاره دارد. خط ۳۱ شمارنده فروشگاه را روی ۲۵ تنظیم کرد؛
  • پس از انجام این کار، صفحه برای کلاینت ارسال شد؛

اگر به صفحه‌ای که توسط کلاینت دریافت شده نگاه کنیم، عناصر زیر را می‌یابیم:


<!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>
  • خط ۴۷: مقداری که توسط سرور ارسال شده است؛
  • خط ۶۰: می‌توانیم ببینیم که وضعیت فروشگاه [Vuex] در صفحه جاسازی شده است. این امر به کلاینت، که پس از دریافت صفحه اجرا می‌شود، اجازه می‌دهد تا یک فروشگاه جدید [Vuex] را با مقدار ۲۵ به عنوان مقدار اولیه شمارنده بازسازی کند؛

پس از دریافت و نمایش صفحه از سرور، کلاینت کنترل را به دست می‌گیرد و به نوبه خود صفحه [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>
  • خط ۳۴: مقدار [value] در خط ۱۸ به نوبه خود با مقدار ۲۵ از شمارنده تخصیص داده می‌شود؛

بنابراین، مخزن [nuxt] به سرور این امکان را می‌دهد که در حین بارگذاری اولیه صفحه، زمانی که از سرور فراخوانی می‌شود، اطلاعات را به کلاینت منتقل کند. شایان ذکر است که پس از بارگیری این صفحه، دیگر به سرور دسترسی صورت نمی‌گیرد و برنامه به عنوان یک برنامه استاندارد [vue]، در حالت SAP عمل می‌کند.

5.3. صفحه [page2]

در صفحه [page2]، ما روش دیگری را برای:

  • سرور را برای درج اطلاعات محاسبه شده در صفحه؛
  • کلاینت این اطلاعات را تغییر نمی‌دهد؛

5.3.1. کد صفحه

کد صفحه [page2] به شرح زیر تغییر می‌کند:


<!-- صفحه ۲ -->
<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) {
      //یک promise بازگردانده می‌شود
      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>
  • خط ۷: صفحه مقدار یک ویژگی به نام [value] را نمایش می‌دهد؛
  • ویژگی [value] به عنوان عنصری از ابجکتی که توسط تابع [data] بازگردانده می‌شود، وجود ندارد. در این مورد، این تابع وجود ندارد. ویژگی [value] به صورت پویا توسط خط ۳۶ ایجاد می‌شود؛
  • خط ۲۵: تابع [asyncData] یک تابع [nuxt] است. همانطور که از نامش پیداست، این معمولاً یک تابع غیرهمزمان است. نقش معمول آن فراخوانی داده‌های خارجی است. [nuxt] تضمین می‌کند که صفحه تا زمانی که تابع [asyncData] داده‌های غیرهمزمان خود را بازنگرداند، به مرورگر مشتری ارسال نشود؛
  • تابع [asyncData]، زمینه (context) [nuxt] را به‌عنوان پارامتر دریافت می‌کند. این شیء بسیار جامع است و دسترسی به اطلاعات زیادی دربارهٔ برنامهٔ Nuxt را فراهم می‌کند. ما در بخش‌های بعدی به بررسی این موضوع خواهیم پرداخت؛
  • خط ۳۱: ما تابع [asyncData] را با یک [Promise] پیاده‌سازی می‌کنیم (به سند «مقدمه زبان ECMASCRIPT 6 از طریق مثال‌ها» مراجعه کنید). سازنده این کلاس به عنوان پارامتر تابعی ناهمزمان را می‌پذیرد که:
    • موفقیت را با بازگرداندن داده‌ها از طریق تابع [resolve] نشان می‌دهد. ابجئتی که توسط این تابع بازگردانده می‌شود، به‌طور خودکار در ویژگی‌های [data] صفحه گنجانده می‌شود؛
    • با بازگشت یک خطا از طریق تابع [reject]، یک خطا را سیگنال می‌دهد؛
  • خط ۳۴: یک تابع غیرهمزمان با استفاده از تابع [setTimeout] شبیه‌سازی می‌شود. این تابع پس از یک ثانیه (خط ۳۱) از طریق تابع [resolve]، شیء [{ value: 87 }] (خط ۳۶) را بازمی‌گرداند، که نشان‌دهنده موفقیت [Promise] است. شیء بازگشتی تابع غیرهمزمان به‌طور خودکار در ویژگی‌های [data] صفحه گنجانده می‌شود. بنابراین این ویژگی است که در خط ۷ نمایش داده می‌شود؛
  • خط ۲۷: می‌بینیم که تابع [asyncData] توسط سرور اجرا می‌شود اما توسط کلاینت اجرا نمی‌شود؛
  • خط ۲۹: ویژگی [value] توسط سرور مقداردهی اولیه می‌شود؛

توجه: شیء [this] در تابع [asyncData] شناسایی نمی‌شود زیرا شیئی که کامپوننت [vue] را در بر می‌گیرد هنوز ایجاد نشده است؛

5.3.2. اجرا

ما پروژه [nuxt-02] را اجرا می‌کنیم و به‌صورت دستی [localhost:81/nuxt-02/page2] را وارد می‌کنیم تا سرور را فعال کنیم. همانند اجرای اولیه برای صفحه [index]:

  • سرور صفحه [page2.vue] را اجرا می‌کند؛
  • صفحهٔ تولیدشده را برای مرورگر ارسال می‌کند. این صفحه نمایش داده می‌شود؛
  • اسکریپت‌های سمت کلاینت که در صفحه ارسال‌شده جاسازی شده‌اند، کنترل را به دست گرفته و صفحه [page2.vue] را مجدداً اجرا می‌کنند؛
  • سپس صفحه نمایش‌داده‌شده اصلاح می‌شود؛

نتیجه نهایی به شرح زیر است:

Image

این بار، مقدار نمایش‌داده‌شده واقعاً همان مقداری است که توسط سرور تنظیم شده و از نظر بصری صفحه به دلیل تغییر سمت کلاینت در مقداری که سرور نمایش می‌دهد، «تردید» نمی‌کند. پس این بار چه اتفاقی افتاد؟

سرور صفحه زیر را اجرا کرد: [page2]:


<!-- صفحه ۲ -->
<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) {
      //یک promise بازگردانده می‌شود
      return new Promise(function(resolve, reject) {
        // به طور معمول، در اینجا یک تابع ناهمزمان وجود داشت
        // ما آن را با یک وقفه یک ثانیه‌ای شبیه‌سازی می‌کنیم
        setTimeout(() => {
          //این نتیجه در ویژگی‌های [data] گنجانده خواهد شد
          resolve({ value: 87 })
          // log
          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>

خط ۳۶ مقدار نمایش داده شده توسط خط ۷ را تعیین می‌کرد. بنابراین این چیزی است که مرورگر کلاینت دریافت کرد. برای دقت بیشتر، صفحه زیر را دریافت کرد:


<!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>
  • خط ۴۸: می‌بینیم که مقدار در صفحه دریافت‌شده ۸۷ است؛
  • خط ۶۱: در پاسخ سرور، دو شیء را می‌بینیم: [data] و [state]:
    • [state] وضعیت فروشگاه [Vuex] است. این از محتویات پوشه [store] در داخل برنامه [nuxt-02] نمونه سازی شده است؛
    • [data] شامل ویژگی‌هایی است که توسط سرور با استفاده از تابع [asyncData] ایجاد شده‌اند. می‌توانیم ویژگی [value : 87] را که توسط سرور ایجاد شده است، مشاهده کنیم. اسکریپت‌های سمت کلاینت این ویژگی را در ویژگی‌های صفحه [page2] گنجانده خواهند کرد؛

بیایید به کد صفحه [page2] بازگردیم:


<!-- صفحه ۲ -->
<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>
  • خط ۷ از ویژگی [value] استفاده می‌کند. با این حال، صفحه هیچ ویژگی‌ای با نام [value] تعریف نکرده است. با این وجود، اسکریپت‌های سمت کلاینت این ویژگی را به طور خودکار با استفاده از شیء [data: [{ value: 87 }]] که از سرور دریافت شده، ایجاد کرده‌اند؛

لاگ‌ها همچنین نشان می‌دهند که تابع [asyncData] توسط کلاینت اجرا نشده است:

Image

تابع [asyncData] توسط سرور [1] اجرا شد اما توسط کلاینت [2] اجرا نشد. علاوه بر این، باید توجه داشت که توابع چرخه عمر تا زمانی که تابع [asyncData] تکمیل نشده باشد، توسط سرور اجرا نمی‌شوند. زمان انتظار در داخل تابع [asyncData] را می‌توان برای تأیید این موضوع افزایش داد.

5.4. صفحه [page3]

ما یک صفحه جدید، [page3]، به برنامه خود اضافه می‌کنیم:

Image

5.4.1. کامپوننت [navigation]

کامپوننت [vavigation] برای امکان ناوبری به صفحه جدید اصلاح شده است:


<template>
  <!--منوی بوت‌استرپ با سه گزینه -->
  <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] به شرح زیر است:


<!--صفحه ۳ -->
<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>
  • خط ۳۰: تابع [fetch] به شکلی مشابه تابع [asyncData] رفتار می‌کند:
    • این تابع قبل از توابع چرخهٔ عمر اجرا می‌شود؛
    • شیء [this] در این تابع شناسایی نمی‌شود؛
    • این تابع به‌صورت ناهمزمان عمل می‌کند؛
    • چرخهٔ عمر تا زمانی که تابع غیرهمزمان نتیجهٔ خود را بازنگردانده است، آغاز نمی‌شود؛
    • نتیجه در اینجا توسط متد [then] از [Promise]، خط ۴۳؛ بازگردانده می‌شود؛
    • تابع [fetch] پارامتر [context] را دریافت می‌کند. این نشان‌دهنده زمینه فعلی [nuxt] است؛
  • خط ۳۰: این شیء [context] در میان ویژگی‌های متعدد خود، دارای ویژگی [store] است که نمایانگر مخزن برنامه [Vuex] می‌باشد؛
  • خط ۴۱: برای اهداف نمایشی، [Promise] پس از یک ثانیه با موفقیت علامت‌گذاری می‌شود (به سند «مقدمه‌ای بر زبان ECMASCRIPT 6 از طریق مثال‌ها» مراجعه کنید)؛
  • خط ۴۵: متد [then] سپس اجرا می‌شود. در اینجا شمارنده برای [store] افزایش می‌یابد؛

5.4.3. اجرا

پروژه [nuxt-02] اجرا می‌شود و [localhost:81/nuxt-02/page3] به‌صورت دستی وارد می‌شود تا سرور را فعال کند. همانند راه‌اندازی اولیه صفحه [index]:

  • سرور صفحه [page3.vue] را اجرا می‌کند؛
  • صفحهٔ تولیدشده را به مرورگر ارسال می‌کند. این صفحه نمایش داده می‌شود؛
  • اسکریپت‌های سمت کلاینت که در صفحه ارسال‌شده جاسازی شده‌اند، کنترل را به دست گرفته و صفحه [page3.vue] را دوباره اجرا می‌کنند؛
  • سپس صفحه نمایش‌داده‌شده اصلاح می‌شود؛

نتیجه نهایی به شرح زیر است:

Image

مقدار نمایش‌داده‌شده دقیقاً همان مقداری است که توسط سرور تعیین شده و از نظر بصری صفحه به دلیل تغییر سمت کلاینت در مقداری که سرور نمایش می‌دهد، چشمک نمی‌زند. این بار چه اتفاقی افتاد؟

سرور صفحه زیر را اجرا کرد: [page3]:


<!-- صفحه ۳ -->
<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>
  • خط ۴۵: تابع غیرهمزمان [fetch] اولین تابع از توابع فوق است که اجرا می‌شود. این تابع به عنوان پارامتر، ابجکتی به نام [context] را دریافت می‌کند که زمینه فعلی [nuxt] است. در میان ویژگی‌های متعدد این شیء، ویژگی [context.store] نمایانگر فروشگاه [Vuex] است؛
  • خط ۴۵: در تابع غیرهمزمان [fetch]، سرور شمارنده فروشگاه را روی ۲۸ تنظیم می‌کند؛
  • خط ۵۶: هنگامی که تابع [created] اجرا می‌شود، [nuxt] اطمینان حاصل می‌کند که تابع غیرهمزمان [fetch] کار خود را به پایان رسانده است؛
  • خط ۵۸: مقدار شمارنده مخفی به ویژگی [value] در خط ۲۷ اختصاص داده می‌شود؛
  • خط ۷: مقدار [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>
  • خط ۵۲: می‌بینیم که مقدار در صفحه دریافتی ۲۸ است؛
  • خط ۶۵: در پاسخ سرور، می‌بینیم که سرور وضعیت [state] را برای فروشگاه [Vuex] برای کلاینت ارسال کرده است. با استفاده از این اطلاعات، اسکریپت‌های کلاینت قادر خواهند بود یک فروشگاه [Vuex] را بازسازی کنند؛

اسکریپت‌های کلاینت به نوبه خود کد موجود در صفحه [page3] را اجرا خواهند کرد:


<!-- صفحه ۳ -->
<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) {
      //یک promise بازگردانده می‌شود
      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>
  • خط ۵۸: تابع [created] که توسط کلاینت اجرا می‌شود، مقدار شمارنده را در خط ۲۷ روی ویژگی [value] تنظیم می‌کند؛
  • خط ۷ این مقدار را نمایش می‌دهد. از آنجا که این مقدار با مقداری که توسط سرور ارسال شده یکسان است، صفحه در نتیجه تغییر «تردید» نمی‌کند؛

لاگ‌ها همچنین نشان می‌دهند که تابع [fetch] توسط کلاینت اجرا نشده است:

Image

تابع [fetch] توسط سرور ([1]) اجرا شد اما توسط کلاینت ([2]) اجرا نشد. علاوه بر این، باید توجه داشت که توابع چرخه عمر تا زمانی که تابع [fetch] [3] تکمیل نشده باشد، توسط سرور اجرا نمی‌شوند. زمان انتظار در داخل تابع [fetch] را می‌توان برای تأیید این موضوع افزایش داد.

صفحات [page1] و [page3] دو روش را با استفاده از فروشگاه [Vuex] برای انتقال اطلاعات از سرور به کلاینت نشان دادند. ممکن است این سؤال پیش بیاید که آیا آن‌ها معادل هستند یا خیر. ما در حال ایجاد صفحه‌ای به نام [page4] هستیم تا این موضوع را تأیید کنیم.

5.5. صفحه [page4]

ما یک صفحه جدید، [page4]، به برنامه خود اضافه می‌کنیم:

Image

5.5.1. کامپوننت [navigation]

کامپوننت [navigation] برای امکان ناوبری به صفحه جدید اصلاح شده است:


<template>
  <!--منوی بوت‌استرپ با پنج گزینه -->
  <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] به شرح زیر است:


<!--صفحه ۴ -->
<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) {
        // این معمولاً یک تابع غیرهمزمان است
        // با وقفه ۱۰ ثانیه‌ای شبیه‌سازی شده است
        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>
  • خط ۳۰: کاری که قبلاً در تابع [fetch] انجام می‌شد، اکنون در متد [beforeCreate] انجام می‌شود. جفت async (خط ۳۰) / await (خط ۳۶) برای انتظار تا اتمام تابع ناهمزمان استفاده می‌شود؛
  • خط ۳۶: نتیجه تابع غیرهمزمان در خط ۴۱ پس از ۱۰ ثانیه (خط ۴۲) بازیابی می‌شود؛
  • خطوط 50–54: در متد [created] که هم در سمت سرور و هم در سمت کلاینت اجرا می‌شود، شمارنده به خصوصیت [value] صفحه تخصیص داده می‌شود؛

5.5.3. اجرا

ما پروژه [nuxt-02] را اجرا می‌کنیم و به‌صورت دستی [localhost:81/nuxt-02/page4] را وارد می‌کنیم تا سرور را تحریک کنیم. همانند راه‌اندازی اولیه صفحه [index]:

  • سرور صفحه [page4.vue] را اجرا می‌کند؛
  • صفحهٔ تولیدشده را به مرورگر ارسال می‌کند. این صفحه نمایش داده می‌شود؛
  • اسکریپت‌های سمت کلاینت که در صفحه ارسال‌شده جاسازی شده‌اند، کنترل را به دست گرفته و صفحه [page4.vue] را دوباره اجرا می‌کنند؛
  • سپس صفحه نمایش‌داده‌شده اصلاح می‌شود؛

نتیجه نهایی به شرح زیر است:

Image

برخلاف انتظارات، مقداری که در [2] نمایش داده می‌شود ۵۲ نیست. چه اتفاقی افتاده است؟

لاگ‌ها به شرح زیر است:

Image

مشاهده می‌شود که در [1]، لاگی که پایان اقدام غیرهمزمان را نشان می‌دهد، نمایش داده نشده است. تابع [created] که مقدار شمارنده را نمایش می‌دهد، عدد 0 را نشان می‌دهد. همه این‌ها نشان می‌دهد که [nuxt] منتظر تکمیل عملیات ناهمزمان نمانده است.

اگر به ترمینال VSCode که برای راه‌اندازی برنامه استفاده شده بود بازگردیم، لاگ‌های [3-4] را می‌یابیم. می‌توانیم ببینیم که تابع غیرهمزمان در واقع در سمت سرور اجرا شده است.

در نهایت، تابع [beforeCreate] واقعاً به طور کامل در سمت سرور اجرا شد، اما [nuxt] قبل از ارسال صفحه به مرورگر کلاینت، منتظر اتمام اجرای آن نماند، در حالی که برای اتمام تابع [fetch] منتظر می‌ماند. بنابراین این روشی است که باید استفاده کنید اگر می‌خواهید سرور یک فروشگاه [Vuex] را راه‌اندازی کند.

5.6. ناوبری در برنامه [vue]

ما نشان دادیم که وقتی هر یک از صفحات [index, page1, page2, page3, page4] برای اولین بار توسط سرور بارگذاری می‌شوند، چه اتفاقی می‌افتد. در عمل، این اتفاق نمی‌افتد: در حین کار عادی، تنها صفحه [index] از سرور بازیابی می‌شود. بیایید به سه صفحه در این سناریو نگاهی بیندازیم:

صفحه [index]

Image

ما قبلاً این نتیجه را در بخش «لینک» توضیح داده‌ایم.

اکنون روی لینک [Page 1] کلیک کنیم:

Image

مقدار نمایش‌داده‌شده 0 است. وقتی صفحه برای اولین بار با وارد کردن دستی URL از سرور درخواست شد، مقدار آن 25 بود. توضیح ساده است. کد اجراشده به شرح زیر است:


  created() {
    // کلاینت و سرور
    console.log('[page1 created]')
    //فقط سرور
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // کلاینت و سرور
    this.value = this.$store.state.counter
    console.log('value=', this.value)
},

خط ۶ بود که شمارنده را روی ۲۵ تنظیم کرد. از آنجایی که صفحه از سرور درخواست نشد، خطوط ۵ تا ۷ اجرا نشدند و شمارنده برای فروشگاه [Vuex] روی ۰ باقی ماند.

حالا، بیایید روی لینک [Page 2] کلیک کنیم:

Image

این بار، هیچ مقداری نمایش داده نمی‌شود و علاوه بر این، یک هشدار در لاگ‌های کنسول ظاهر می‌شود:

Image

  • در [1] می‌بینیم که تابع [asyncData] توسط کلاینت اجرا شده است. همیشه اینطور است که:
    • اگر صفحه از سرور درخواست شود، توسط سرور اجرا می‌شود. در این مورد، توسط کلاینت اجرا نمی‌شود؛
    • سپس هر زمان که صفحه هدف مسیر فعلی کلاینت باشد؛
  • در [2]: [nuxt] یک هشدار صادر می‌کند زیرا قالب صفحه شامل یک عبارت واکنشی {{ value }} است، در حالی که صفحه خاصیت [value] را ندارد؛

بیایید کد اجرا شده توسط کلاینت را به یاد بیاوریم:


asyncData() {
    // چه کسی این کد را اجرا می‌کند؟
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    //فقط سرور
    if (process.server) {
      // یک promise بازگردانده می‌شود
      return new Promise(function(resolve, reject) {
        // معمولاً در اینجا یک تابع غیرهمزمان وجود داشت – اما در این مورد این‌طور نیست
        //این نتیجه در ویژگی‌های [data] گنجانده خواهد شد
        resolve({ value: 87 })
      })
    }
  },
  • خط ۳ نشان می‌دهد که تابع [asyncData] توسط کلاینت حتی قبل از توابع چرخه عمر نیز اجرا شده است؛
  • خط ۵ مانع اجرای بقیه کد شد، کدی که توسط کلاینت اجرا می‌شود و قرار بود ویژگی [value] را ایجاد کند؛

اکنون به صفحه [page3] می‌رویم:

Image

  • در [2]، زمانی که صفحه توسط سرور ارائه شد، مقدار ۲۸ بود. در اینجا اینطور نیست؛
  • در [4]، می‌بینیم که تابع [fetch] در سمت کلاینت اجرا شده است؛

بیایید کد اجرا شده توسط کلاینت را بررسی کنیم:


...
fetch(context) {
    // چه کسی این کد را اجرا می‌کند؟
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // فقط روی سرور
    if (process.server) {
      //یک promise بازگردانده می‌شود
      return new Promise(function(resolve, reject) {
        // به طور معمول، در اینجا یک تابع ناهمزمان وجود داشت
        // ما آن را با یک وقفه یک ثانیه‌ای شبیه‌سازی می‌کنیم
        setTimeout(() => {
          // موفقیت
          resolve()
        }, 1000)
      }).then(() => {
        // فروشگاه اصلاح می‌شود
        context.store.commit('increment', 28)
        // لاگ
        console.log('fetch commit terminé')
      })
    }
},
...
  • کلاینت متد [fetch] را در خط ۲ اجرا می‌کند؛
  • خطوط ۶ تا ۲۱ اجرا نمی‌شوند زیرا شرط [process.server] نادرست است. بنابراین، خط ۱۷ که شمارنده را روی ۲۸ تنظیم می‌کند، اجرا نمی‌شود. شمارنده روی صفر باقی می‌ماند. به همین دلیل کلاینت به جای ۲۸، ۰ را نمایش می‌دهد؛

حال به صفحه [page4] می‌رویم. نتیجه زیر را به دست می‌آوریم:

Image

  • در [2]، مقدار شمارنده؛
  • در [3]، لاگ‌های کلاینت ثبت می‌شوند؛

کدی که توسط کلاینت اجرا می‌شود به شرح زیر است:


...
data() {
    return {
      value: 0
    }
  },
  //چرخه عمر
  async beforeCreate() {
    // کلاینت و سرور
    console.log('[page4 beforeCreate]')
    // فقط سرور
    if (process.server) {
      //تابع غیرهمزمان اجرا می‌شود
      const valeur = await new Promise(function(resolve, reject) {
        //این معمولاً یک تابع ناهمزمان است
        // این با یک وقفهٔ ۱۰ ثانیه‌ای شبیه‌سازی می‌شود
        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)
  },
...
  • خطوط ۱۲ تا ۲۶ توسط کلاینت اجرا نمی‌شوند، زیرا شرط [process.server] در خط ۱۲ نادرست است. بنابراین، شمارنده در مخزن [Vuex] برابر ۰ است (مقدار اولیه آن در مخزن) و این همان مقداری است که صفحه نمایش می‌دهد؛

ممکن است این سؤال پیش بیاید که وقتی [if] در خطوط ۱۲ و ۲۶ کامنت (توضیح) می‌شود، چه اتفاقی می‌افتد. پاسخ این است:

  • این بار، کلاینت خطوط ۱۴ تا ۲۵ را اجرا می‌کند، اما [nuxt] منتظر اتمام تابع غیرهمزمان نمی‌ماند (مانند کاری که سرور انجام می‌دهد) و در نتیجه شمارنده را روی ۰ باقی می‌گذارد؛
  • پس از ۱۰ ثانیه، تابع غیرهمزمان کامل می‌شود و شمارنده در خط ۲۳ روی ۵۲ تنظیم می‌شود؛
  • هنگام بازگشت به صفحه [page4]، مقدار ۵۲ نمایش داده می‌شود؛

5.7. Résumé

از آزمایش‌های مختلف ما، نکات زیر قابل توجه است:

  • اگر صفحه [index] حاوی داده‌های خارجی باشد، این داده‌ها را می‌توان با استفاده از تابع [asyncData] توسط سرور بازیابی کرد؛
  • اگر سرور نیاز داشته باشد هنگام بارگذاری صفحه [index]، یک مخزن [Vuex] را با داده‌های خارجی اولیه کند، این کار را در تابع [fetch] انجام خواهد داد؛
  • صفحه تولید شده توسط سرور و صفحه تولید شده توسط کلاینت باید یکسان باشند تا از اثر «پرش» جلوگیری شود؛ این اثر زمانی رخ می‌دهد که صفحه کلاینت به صورت بصری جایگزین صفحه‌ای می‌شود که توسط سرور ارسال و در ابتدا نمایش داده شده است؛

ما جنبه‌های دیگر [nuxt] را از طریق یک مثال جدید بررسی خواهیم کرد.