Skip to content

5. Przykład [nuxt-02]: strony serwera i klienta

W tym projekcie pokazujemy:

  • że strona zbudowana przez klienta może wizualnie różnić się od tej otrzymanej z serwera. Powoduje to szybką zmianę strony, zauważalną dla użytkownika, co negatywnie wpływa na ergonomię aplikacji. Jest to zatem opcja, której należy unikać;
  • rozwiązanie, dzięki któremu strona kliencka odtwarza tę samą stronę, co ta wysłana przez serwer;

Projekt [nuxt-02] powstał początkowo poprzez skopiowanie projektu [nuxt-01].

Image

Do projektu dodano folder [store] oraz dwie nowe strony. Wrócimy do tego później.

5.1. Strona [index]

5.1.1. Kod strony

Kod strony [index] wygląda następująco:


<!-- strona główna -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat-->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[home beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[home created]')
    // tylko serwer
    if (process.server) {
      this.value = 10
    }
    // klient i serwer
    console.log('value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[home beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[home mounted]')
  }
}
</script>

Komentarze

  • wiersz 7: strona [index] wyświetli wartość swojej właściwości [value] (wiersz 28);
  • wiersze 36–45: należy tutaj pamiętać, że funkcja [created] jest wykonywana zarówno po stronie serwera, jak i po stronie klienta. W wierszach 40–42 serwer ustawi wartość właściwości [value] na 10. Klient natomiast nie zmienia tej wartości. Chcemy po prostu sprawdzić, czy wartość ta jest zachowywana przez klienta. Okazuje się, że nie;

5.1.2. Wykonanie

Modyfikujemy plik [/nuxt.config.js], aby uruchomić projekt [nuxt-02]:


...
  // katalog kodu źródłowego
  srcDir: 'nuxt-02',
  // router
  router: {
    // katalog główny aplikacji URL
    base: '/nuxt-02/'
  },
  // serwer
  server: {
    // port usługi, domyślnie 3000
    port: 81,
    // adresy sieciowe, na których nasłuchuje, domyślnie localhost: 127.0.0.1
    // 0.0.0.0 = wszystkie adresy sieciowe komputera
    host: 'localhost'
  }
...

Uruchamiamy projekt [1]:

Image

Wyświetla się wówczas strona [index] [2-3]. Przez chwilę wyświetla ona wartość [10], a następnie wartość [0]. Co się stało?

Krok 1

Najpierw uruchamia się serwer. Wykonuje on kod strony [index]:


export default {
  name: 'Home',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[home beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[home created]')
    // tylko serwer
    if (process.server) {
      this.value = 10
    }
    // klient i serwer
    console.log('value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[home beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[home mounted]')
  }
}
  • z powodu wiersza 23 właściwość [value] z wiersza 10 przyjmuje wartość 10;

Można to sprawdzić, przeglądając kod źródłowy strony otrzymanej przez przeglądarkę (opcja [code source] w przeglądarce):


<!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>
  • wiersz 46: na otrzymanej stronie [value] miało wartość 10;

krok 2

Wiadomo, że po odebraniu strony skrypty z wierszy 57–60 przejmują kontrolę i zmieniają zachowanie otrzymanej strony, a nawet wyświetlane informacje, tak jak w tym przypadku. Skrypty te tworzą klienta, który również wykonuje kod strony [index], ten sam kod co serwer:


export default {
  name: 'Home',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[home beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[home created]')
    // tylko serwer
    if (process.server) {
      this.value = 10
    }
    // klient i serwer
    console.log('value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[home beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[home mounted]')
  }
}
  • aby zrozumieć, co się dzieje, należy zdać sobie sprawę, że klient [nuxt] nie wykona wierszy 22–24 (process.server=false);
  • w klasycznej aplikacji [vue] właściwość [value] w wierszu 10 pozostaje na poziomie 0. Dlatego też, gdy klient przejdzie do otrzymanej strony, wyświetlana wartość zmienia się na [0];

Wartość wygenerowana przez serwer [nuxt] dla właściwości [value] nie miała żadnego znaczenia.

5.2. Strona [page1]

5.2.1. Sklep [Vuex]

Dodaliśmy folder [store] do projektu [nuxt-02]:

Image

Obecność tego folderu powoduje, że [nuxt] automatycznie zaimplementuje magazyn [Vuex]. Plik [index.js] implementuje ten magazyn. W tym przypadku plik [index.js] ma następującą postać:


export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state, inc) {
    state.counter += inc
  }
}

[nuxt] implementuje magazyn [Vuex] na podstawie zawartości pliku [index.js]:

  • wiersze 1–3: definicja stanu [state] magazynu. Stan ten jest zwracany przez funkcję. W tym przypadku stan ma tylko jedną właściwość – licznik w wierszu 2. Eksportowana funkcja musi nosić nazwę [state];
  • wiersze 5–9: możliwe operacje na stanie sklepu. Nazywamy je [mutations]. W tym przypadku operacja [increment] pozwala zwiększyć wartość właściwości [counter] o wartość [inc]. Eksportowany obiekt musi nosić nazwę [mutations];

Widok [store] zaimplementowany przez [nuxt] jest dostępny w różnych miejscach. W widokach jest on dostępny we właściwości [this.$store].

5.2.2. Kod strony

Podobnie jak strona [index], strona [page1] wyświetli wartość licznika z magazynu Vuex:


<!-- strona 1 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat-->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[home beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[home created]')
    // tylko serwer
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[home beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[home mounted]')
  }
}
</script>

Komentarze

  • wiersze 38–40: serwer zwiększy licznik o 25;
  • wiersz 42: zarówno serwer, jak i klient wyświetlą wartość licznika;
  • wiersz 7: wyświetlana jest wartość licznika;

Czytając ten kod, należy zrozumieć dwie rzeczy:

  • kod wykonywany jest taki sam zarówno na serwerze, jak i na kliencie;
  • Obiekt [this] nie jest ten sam: po stronie serwera występuje wersja [this], a po stronie klienta – wersja [client];

Chcemy ustalić, czy obiekt [this.$store] po stronie serwera jest taki sam jak obiekt [this.$store] po stronie klienta. Ponieważ to serwer uruchamia się jako pierwszy (podczas uruchamiania aplikacji), sprowadza się to do pytania: czy plik [store] zainicjowany przez serwer jest przekazywany do klienta?

5.2.3. Wykonanie

Uruchamiamy projekt [nuxt-02] i ręcznie wpisujemy adres [localhost:81/nuxt-02/page1], aby wywołać serwer. Podobnie jak podczas uruchamiania strony [index]:

  • serwer uruchamia stronę [page1.vue];
  • wysyła wygenerowaną stronę do przeglądarki. Strona ta zostaje wyświetlona;
  • skrypty klienckie osadzone w wysłanej stronie przejmują kontrolę i ponownie uruchamiają stronę [page1.vue];
  • wyświetlana strona zostaje następnie zmodyfikowana;

Ostateczny wynik jest następujący:

Image

Tym razem wyświetlana wartość jest zgodna z wartością ustaloną przez serwer i wizualnie nie widać, aby strona „podskakiwała” z powodu zmiany przez klienta wartości wyświetlanej przez serwer. Co się tym razem stało?

Serwer wykonał następującą stronę: [page1]:


...

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page1 beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[page1 created]')
    // tylko serwer
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page1 mounted]')
  }
}
</script>
  • wiersze 30–32 zostały wykonane bez błędów. Oznacza to, że również po stronie serwera [this.$store] odnosi się do zmiennej [Vuex]. Wiersz 31 ustawił licznik tej zmiennej na 25;
  • po wykonaniu tej operacji strona została wysłana do klienta;

Jeśli przyjrzymy się stronie otrzymanej przez klienta, znajdziemy następujące elementy:


<!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>
  • wiersz 47: wartość wysłana przez serwer;
  • wiersz 60: widać, że stan magazynu [Vuex] został osadzony na stronie. Umożliwi to klientowi, który uruchomi się po odebraniu strony, odtworzenie nowego magazynu [Vuex] z wartością 25 jako początkową wartością licznika;

Po odebraniu i wyświetleniu strony otrzymanej z serwera klient przejmuje kontrolę i z kolei uruchamia stronę [page1]:


...

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page1 beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[page1 created]')
    // tylko serwer
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page1 mounted]')
  }
}
</script>
  • wiersz 34: właściwość [value] z wiersza 18 otrzymuje z kolei wartość 25 z licznika;

Pamięć [nuxt] umożliwia zatem serwerowi przekazywanie informacji do klienta podczas początkowego ładowania strony, gdy jest ona wyszukiwana na serwerze. Należy pamiętać, że po pobraniu tej strony serwer nie jest już angażowany, a aplikacja działa jak klasyczna aplikacja [vue] w trybie SAP.

5.3. Strona [page2]

Na stronie [page2] pokazujemy inny sposób, dzięki któremu:

  • serwer umieszcza na stronie obliczone informacje;
  • klient nie modyfikuje tych informacji;

5.3.1. Kod strony

Kod strony [page2] zmienia się w następujący sposób:


<!-- strona 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // użyte komponenty
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // kto wykonuje ten kod?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // tylko dla serwera
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zazwyczaj mamy tu do czynienia z funkcją asynchroniczną
        // symulujemy ją, wprowadzając sekundę oczekiwania
        setTimeout(() => {
          // ten wynik zostanie uwzględniony we właściwościach [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page2 beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[page2 created]')
  },
  beforeMount() {
    // tylko klient
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page2 mounted]')
  }
}
</script>
  • wiersz 7: strona wyświetla wartość właściwości o nazwie [value];
  • właściwość [value] nie istnieje jako element obiektu zwracanego przez funkcję [data]. W tym przypadku funkcja ta nie istnieje. Właściwość [value] jest tworzona dynamicznie w wierszu 36;
  • wiersz 25: funkcja [asyncData] jest funkcją [nuxt]. Jak wskazuje jej nazwa, jest to zazwyczaj funkcja asynchroniczna. Jej typowym zadaniem jest pobieranie danych zewnętrznych. [nuxt] zapewnia, że strona nie zostanie wysłana do przeglądarki klienta, dopóki funkcja [asyncData] nie zwróci swoich danych asynchronicznych;
  • funkcja [asyncData] otrzymuje jako parametr kontekst [nuxt]. Obiekt ten jest bardzo bogaty i zapewnia dostęp do wielu informacji o aplikacji [Nuxt]. Poznamy go w kolejnych sekcjach;
  • wiersz 31: implementujemy funkcję [asyncData] z wykorzystaniem [Promise] (patrz dokument |Introduction au langage ECMASCRIPT 6 par l’exemple|). Konstruktor tej klasy przyjmuje jako parametr funkcję asynchroniczną, która:
    • sygnalizuje powodzenie, zwracając dane za pomocą funkcji [resolve]. Obiekt zwrócony przez tę funkcję jest automatycznie dołączany do właściwości [data] strony;
    • sygnalizuje niepowodzenie, zwracając błąd za pomocą funkcji [reject];
  • wiersz 34: symulujemy funkcję asynchroniczną za pomocą funkcji [setTimeout]. Funkcja ta zwraca obiekt [{ value: 87 }] (wiersz 36) po upływie jednej sekundy (wiersz 31) dzięki funkcji [resolve], która sygnalizuje pomyślne wykonanie funkcji [Promise]. Obiekt zwrócony przez funkcję asynchroniczną jest automatycznie dołączany do właściwości [data] strony. I właśnie tę właściwość wyświetla wiersz 7;
  • wiersz 27: przekonamy się, że funkcja [asyncData] jest wykonywana przez serwer, ale nie przez klienta;
  • wiersz 29: inicjalizacja właściwości [value] odbywa się po stronie serwera;

Uwaga: obiekt [this] nie jest rozpoznawany w funkcji [asyncData], ponieważ obiekt enkapsulujący komponent [vue] nie został jeszcze utworzony;

5.3.2. Wykonanie

Uruchamiamy projekt [nuxt-02] i ręcznie wpisujemy adres [localhost:81/nuxt-02/page2], aby wywołać serwer. Podobnie jak podczas uruchamiania strony [index]:

  • serwer uruchamia stronę [page2.vue];
  • wysyła wygenerowaną stronę do przeglądarki. Strona ta zostaje wyświetlona;
  • skrypty klienckie osadzone w wysłanej stronie przejmują kontrolę i ponownie uruchamiają stronę [page2.vue];
  • wyświetlana strona zostaje następnie zmodyfikowana;

Ostateczny wynik jest następujący:

Image

Tym razem wyświetlana wartość jest zgodna z wartością ustaloną przez serwer i wizualnie nie widać, aby strona „podskakiwała” z powodu zmiany przez klienta wartości wyświetlanej przez serwer. Co się tym razem stało?

Serwer wykonał następującą stronę [page2]:


<!-- strona 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // kto wykonuje ten kod?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // tylko dla serwera
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zwykle mamy tu do czynienia z funkcją asynchroniczną
        // symulujemy ją, czekając jedną sekundę
        setTimeout(() => {
          // wynik ten zostanie uwzględniony we właściwościach [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page2 beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[page2 created]')
  },
  beforeMount() {
    // tylko klient
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page2 mounted]')
  }
}
</script>

To właśnie wiersz 36 ustalił wartość wyświetlaną przez wiersz 7. Tak więc właśnie to otrzymała przeglądarka klienta. Dokładniej rzecz biorąc, otrzymała następującą stronę:


<!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>
  • wiersz 48: widać, że wartość na otrzymanej stronie wynosi 87;
  • wiersz 61: w odpowiedzi serwera widoczne są dwa obiekty: [data] i [state]:
    • [state] to stan obiektu [Vuex]. Obiekt ten został utworzony na podstawie zawartości folderu [store] aplikacji [nuxt-02];
    • [data] zawiera właściwości utworzone przez serwer za pomocą funkcji [asyncData]. Znajdujemy tu właściwość [value : 87] utworzoną przez serwer. Skrypty klienta włączą tę właściwość do właściwości strony [page2];

Wróćmy do kodu strony [page2]:


<!-- strona 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // kto wykonuje ten kod?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // tylko dla serwera
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zwykle mamy tu do czynienia z funkcją asynchroniczną
        // symulujemy ją, wprowadzając sekundę oczekiwania
        setTimeout(() => {
          // wynik ten zostanie uwzględniony we właściwościach [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page2 beforeCreate]')
  },
  created() {
    // klient i serwer
    console.log('[page2 created]')
  },
  beforeMount() {
    // tylko klient
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page2 mounted]')
  }
}
</script>
  • W wierszu 7 wykorzystano właściwość [value]. Strona nie definiuje jednak żadnej właściwości o nazwie [value]. Skrypty po stronie klienta automatycznie utworzyły tę właściwość na podstawie obiektu [data: [{ value: 87 }]] otrzymanego z serwera;

Z logów wynika ponadto, że funkcja [asyncData] nie została wykonana przez klienta:

Image

Funkcja [asyncData] została wykonana przez serwer [1], ale nie przez klienta [2]. Ponadto należy zauważyć, że funkcje cyklu życia nie są wykonywane przez serwer przed zakończeniem funkcji [asyncData]. Aby to sprawdzić, można wydłużyć czas oczekiwania w ramach funkcji [asyncData].

5.4. Strona [page3]

Dodajemy do naszej aplikacji nową stronę [page3]:

Image

5.4.1. Komponent [navigation]

Komponent [vavigation] został zmodyfikowany, aby umożliwić nawigację do nowej strony:


<template>
  <!-- menu Bootstrap z trzema opcjami -->
  <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. Kod komponentu [page3]

Kod strony [page3] jest następujący:


<!-- strona 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // kto wykonuje ten kod?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // tylko dla serwera
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zwykle mamy tu do czynienia z funkcją asynchroniczną
        // symulujemy ją, czekając jedną sekundę
        setTimeout(() => {
          // powodzenie
          resolve()
        }, 1000)
      }).then(() => {
        // modyfikuje się zmienną
        context.store.commit('increment', 28)
      })
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page3 beforeCreate]')
  },
  created() {
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page3 mounted]')
  }
}
</script>
  • wiersz 30: funkcja [fetch] zachowuje się analogicznie do funkcji [asyncData]:
    • jest wykonywana przed funkcjami cyklu życia;
    • obiekt [this] nie jest rozpoznawany w tej funkcji;
    • działa asynchronicznie;
    • cykl życia nie rozpoczyna się, dopóki funkcja asynchroniczna nie zwróci wyniku;
    • wynik jest tutaj zwracany przez metodę [then] funkcji [Promise], wiersz 43;
    • funkcja [fetch] otrzymuje parametr [context]. Parametr ten reprezentuje aktualny kontekst [nuxt];
  • wiersz 30: wśród swoich licznych właściwości obiekt [context] posiada właściwość [store], która reprezentuje pasek narzędzi [Vuex] aplikacji;
  • wiersz 41: sztucznie sygnalizuje się powodzenie operacji [Promise] po upływie jednej sekundy (por. dokument |Introduction au langage ECMASCRIPT 6 par l’exemple|);
  • wiersz 45: następnie wykonywana jest metoda [then]. W jej ramach zwiększa się licznik [store];

5.4.3. Wykonanie

Uruchamiamy projekt [nuxt-02] i ręcznie wpisujemy adres [localhost:81/nuxt-02/page3], aby wysłać żądanie do serwera. Podobnie jak przy uruchomieniu strony [index]:

  • serwer wygeneruje stronę [page3.vue];
  • wysyła wygenerowaną stronę do przeglądarki. Strona ta zostaje wyświetlona;
  • skrypty klienckie osadzone w wysłanej stronie przejmują kontrolę i ponownie uruchamiają stronę [page3.vue];
  • wyświetlana strona zostaje następnie zmodyfikowana;

Ostateczny wynik jest następujący:

Image

Wyświetlana wartość jest zgodna z wartością ustaloną przez serwer, a wizualnie nie widać, aby strona „podskakiwała” z powodu zmiany przez klienta wartości wyświetlanej przez serwer. Co się tym razem stało?

Serwer wykonał następującą stronę [page3]:


<!-- strona 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // kto wykonuje ten kod?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // tylko dla serwera
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zwykle mamy tu do czynienia z funkcją asynchroniczną
        // symulujemy ją, czekając jedną sekundę
        setTimeout(() => {
          // powodzenie
          resolve()
        }, 1000)
      }).then(() => {
        // modyfikuje się zmienną store
        context.store.commit('increment', 28)
        // log
        console.log('fetch commit terminé')
      })
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page3 beforeCreate]')
  },
  created() {
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // tylko dla klientów
    console.log('[page3 mounted]')
  }
}
</script>
  • wiersz 45: funkcja asynchroniczna [fetch] jest pierwszą z powyższych funkcji, która zostanie wykonana. Otrzymuje ona jako parametr obiekt o nazwie [context], który jest aktualnym kontekstem [nuxt]. Spośród bardzo wielu właściwości tego obiektu właściwość [context.store] reprezentuje pamięć tymczasową [Vuex];
  • wiersz 45: w funkcji asynchronicznej [fetch] serwer ustawia licznik magazynu na 28;
  • wiersz 56: gdy uruchamia się funkcja [created], [nuxt] gwarantuje, że funkcja asynchroniczna [fetch] zakończyła swoją pracę;
  • wiersz 58: wartość licznika rolety jest przypisywana do właściwości [value] z wiersza 27;
  • wiersz 7: wyświetlenie wartości [value], a więc licznika zmiennej;

Przeglądarka klienta otrzymuje następującą stronę:


<!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>
  • wiersz 52: widać, że wartość na otrzymanej stronie wynosi 28;
  • wiersz 65: w odpowiedzi serwera widać, że serwer wysłał do klienta stan [state] magazynu [Vuex]. Dzięki tej informacji skrypty klienckie będą mogły odtworzyć magazyn [Vuex];

Skrypty klienckie z kolei wykonają kod strony [page3]:


<!-- strona 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // używane komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // kto wykonuje ten kod?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // tylko dla serwera
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zwykle mamy tu do czynienia z funkcją asynchroniczną
        // symulujemy ją, czekając jedną sekundę
        setTimeout(() => {
          // powodzenie
          resolve()
        }, 1000)
      }).then(() => {
        // modyfikuje się zmienną store
        context.store.commit('increment', 28)
        // log
        console.log('fetch commit terminé')
      })
    }
  },
  // cykl życia
  beforeCreate() {
    // klient i serwer
    console.log('[page3 beforeCreate]')
  },
  created() {
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page3 mounted]')
  }
}
</script>
  • wiersz 58: funkcja [created] wykonywana przez klienta przypisuje wartość licznika do właściwości [value] w wierszu 27;
  • w wierszu 7 wyświetlana jest ta wartość. Ponieważ jest to ta sama wartość, co ta wysłana przez serwer, nie widać, aby strona „podskakiwała” w wyniku zmiany;

Z logów wynika ponadto, że funkcja [fetch] nie została wykonana przez klienta:

Image

Funkcja [fetch] została wykonana przez serwer [1], ale nie przez klienta [2]. Ponadto można zauważyć, że funkcje cyklu życia nie są wykonywane przez serwer przed zakończeniem funkcji [fetch] i [3]. Aby to sprawdzić, można wydłużyć czas oczekiwania w ramach funkcji [fetch].

Strony [page1] i [page3] przedstawiły dwie metody wykorzystujące pamięć tymczasową [Vuex] do przekazywania informacji z serwera do klienta. Można zadać sobie pytanie, czy są one równoważne. Aby to sprawdzić, tworzymy stronę [page4].

5.5. Strona [page4]

Dodajemy nową stronę [page4] do naszej aplikacji:

Image

5.5.1. Komponent [navigation]

Komponent [navigation] został zmodyfikowany, aby umożliwić nawigację do nowej strony:


<template>
  <!-- menu Bootstrap z pięcioma opcjami -->
  <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. Kod komponentu [page4]

Kod strony [page4] jest następujący:


<!-- strona 4 -->
<template>
  <Layout :left="true" :right="true">
    <!-- nawigacja -->
    <Navigation slot="left" />
    <!-- komunikat -->
    <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',
  // użyte komponenty
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // cykl życia
  async beforeCreate() {
    // klient i serwer
    console.log('[page4 beforeCreate]')
    // tylko dla serwera
    if (process.server) {
      // wykonuje się funkcję asynchroniczną
      const valeur = await new Promise(function(resolve, reject) {
        // zwykle mamy tu do czynienia z funkcją asynchroniczną
        // symuluje się ją z 10-sekundowym oczekiwaniem
        setTimeout(() => {
          // powodzenie – zwracamy wartość licznika
          resolve(52)
        }, 10000)
      })
      // modyfikuje się zmienną przechowującą
      this.$store.commit('increment', valeur)
      // log
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
  beforeMount() {
    // tylko klient
    console.log('[page4 beforeMount]')
  },
  mounted() {
    // tylko klient
    console.log('[page4 mounted]')
  }
}
</script>
  • wiersz 30: to, co wcześniej było wykonywane w funkcji [fetch], jest teraz wykonywane w metodzie [beforeCreate]. Używa się pary async (wiersz 30) / await (wiersz 36), aby poczekać na zakończenie funkcji asynchronicznej;
  • wiersz 36: wynik funkcji asynchronicznej, zwrócony w wierszu 41, jest pobierany po upływie 10 sekund (wiersz 42);
  • wiersze 50–54: w metodzie [created], wykonywanej zarówno po stronie serwera, jak i po stronie klienta, licznik jest przypisywany do właściwości [value] strony;

5.5.3. Wykonanie

Uruchamiamy projekt [nuxt-02] i ręcznie wpisujemy adres [localhost:81/nuxt-02/page4], aby wysłać żądanie do serwera. Podobnie jak przy uruchomieniu strony [index]:

  • serwer uruchamia stronę [page4.vue];
  • wysyła wygenerowaną stronę do przeglądarki. Strona ta zostaje wyświetlona;
  • skrypty klienckie osadzone w wysłanej stronie przejmują kontrolę i ponownie uruchamiają stronę [page4.vue];
  • wyświetlana strona zostaje następnie zmodyfikowana;

Ostateczny wynik jest następujący:

Image

Wbrew oczekiwaniom wartość wyświetlana w [2] nie wynosi 52. Co się stało?

Oto zapisy w logach:

Image

Można zauważyć, że w przypadku [1] nie wyświetlono logu zakończenia operacji asynchronicznej. Funkcja [created], która wyświetla wartość licznika, pokazuje 0. Wszystko to sugeruje, że [nuxt] nie doczekało się zakończenia operacji asynchronicznej.

Jeśli wrócimy do terminala funkcji VSCode, która służyła do uruchomienia aplikacji, znajdziemy tam logi funkcji [3-4]. Widać, że funkcja asynchroniczna została rzeczywiście wykonana po stronie serwera.

Ostatecznie funkcja [beforeCreate] została w całości wykonana po stronie serwera, ale [nuxt] nie poczekała na zakończenie jej wykonywania, aby wysłać stronę do przeglądarki klienta, podczas gdy czeka na zakończenie funkcji [fetch]. Należy zatem zastosować tę metodę, jeśli chcemy, aby serwer zainicjował magazyn [Vuex].

5.6. Nawigacja w aplikacji [vue]

Pokazaliśmy, co się dzieje, gdy każda ze stron [index, page1, page2, page3, page4] jest początkowo ładowana przez serwer. W praktyce tak się nie dzieje: podczas normalnego działania na serwerze wyszukiwana jest tylko strona [index]. Przyjrzyjmy się w tym przypadku trzem stronom:

strona [index]

Image

Wyjaśniliśmy już ten wynik w akapicie dotyczącym linków.

Teraz kliknijmy na link [Page 1]:

Image

Wyświetlana wartość to 0. Wynosiła ona 25, gdy strona została po raz pierwszy zażądana od serwera poprzez ręczne wpisanie jej adresu URL. Wyjaśnienie jest proste. Wykonany kod wygląda następująco:


  created() {
    // klient i serwer
    console.log('[page1 created]')
    // tylko serwer
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('value=', this.value)
},

To właśnie wiersz 6 ustawiał licznik na 25. Ponieważ strona nie została wywołana z serwera, wiersze 5–7 nie zostały wykonane, a licznik w zmiennej [Vuex] pozostał na poziomie 0.

Teraz kliknijmy link [Page 2]:

Image

Tym razem nie wyświetla się żadna wartość, a ponadto w logach konsoli pojawia się ostrzeżenie:

Image

  • W przypadku [1] okazuje się, że funkcja [asyncData] została wykonana przez klienta. Zawsze jest tak:
    • jest ona wykonywana przez serwer, jeśli strona jest żądana od serwera. W takim przypadku nie jest ona wykonywana przez klienta;
    • a następnie za każdym razem, gdy strona jest celem bieżącej trasy klienta;
  • w przypadku [2]: funkcja [nuxt] generuje ostrzeżenie, ponieważ w szablonie strony znajduje się wyrażenie reaktywne {{ value }}, podczas gdy strona nie posiada właściwości [value];

Przypomnijmy kod wykonywany przez klienta:


asyncData() {
    // kto wykonuje ten kod?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // tylko dla serwera
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zwykle mamy tu funkcję asynchroniczną – ale nie w tym przypadku
        // ten wynik zostanie uwzględniony we właściwościach [data]
        resolve({ value: 87 })
      })
    }
  },
  • w wierszu 3 widać, że funkcja [asyncData] została wykonana przez klienta jeszcze przed funkcjami cyklu życia;
  • wiersz 5 uniemożliwił wykonanie pozostałej części kodu, który tworzył właściwość [value], ponieważ kod ten jest wykonywany przez klienta;

Przejdźmy teraz do strony [page3]:

Image

  • w [2] mieliśmy wartość 28, gdy strona została dostarczona przez serwer. Tutaj tak nie jest;
  • w [4] widać, że funkcja [fetch] została wykonana po stronie klienta;

Przyjrzyjmy się kodowi wykonanemu przez klienta:


...
fetch(context) {
    // kto wykonuje ten kod?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // tylko na serwerze
    if (process.server) {
      // zwracamy obietnicę
      return new Promise(function(resolve, reject) {
        // zazwyczaj mamy tu do czynienia z funkcją asynchroniczną
        // symulujemy ją, czekając jedną sekundę
        setTimeout(() => {
          // powodzenie
          resolve()
        }, 1000)
      }).then(() => {
        // modyfikuje się zmienną store
        context.store.commit('increment', 28)
        // log
        console.log('fetch commit terminé')
      })
    }
},
...
  • klient wykonuje metodę [fetch], wiersz 2;
  • wiersze 6–21 nie są wykonywane, ponieważ warunek [process.server] jest fałszywy. W związku z tym wiersz 17, który ustawia licznik na 28, nie jest wykonywany. Licznik pozostaje na zero. Dlatego klient wyświetla 0 zamiast 28;

Przejdźmy teraz do strony [page4]. Otrzymujemy następujący wynik:

Image

  • w [2] – wartość licznika;
  • w [3] – logi klienta;

Kod wykonywany przez klienta jest następujący:


...
data() {
    return {
      value: 0
    }
  },
  // cykl życia
  async beforeCreate() {
    // klient i serwer
    console.log('[page4 beforeCreate]')
    // tylko dla serwera
    if (process.server) {
      // wykonuje się funkcję asynchroniczną
      const valeur = await new Promise(function(resolve, reject) {
        // zazwyczaj mamy tu do czynienia z funkcją asynchroniczną
        // symuluje się ją z 10-sekundowym oczekiwaniem
        setTimeout(() => {
          // powodzenie – zwracamy wartość licznika
          resolve(52)
        }, 10000)
      })
      // modyfikuje się zmienną
      this.$store.commit('increment', valeur)
      // log
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // klient i serwer
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
...
  • wiersze 12–26 nie są wykonywane przez klienta, ponieważ warunek [process.server] w wierszu 12 jest fałszywy. Zatem licznik w zmiennej [Vuex] ma wartość 0 (jego wartość początkowa w zmiennej) i to właśnie ta wartość jest wyświetlana na stronie;

Można się zastanawiać, co się stanie, gdy skomentujemy warunek [if] w wierszach 12 i 26. Oto odpowiedź:

  • klient wykonuje tym razem linie 14–25, ale [nuxt] nie czeka na zakończenie funkcji asynchronicznej (tak jak w przypadku serwera) i pozostawia licznik na 0;
  • po 10 sekundach funkcja asynchroniczna kończy działanie, a licznik w wierszu 23 zostaje ustawiony na 52;
  • gdy ponownie przejdzie się do strony [page4], wyświetlana jest wówczas wartość 52;

5.7. Résumé

Z naszych różnych testów można wyciągnąć następujące wnioski:

  • jeśli strona [index] ma zawierać dane zewnętrzne, serwer może je pobrać za pomocą funkcji [asyncData];
  • jeśli serwer ma zainicjować pamięć [Vuex] danymi zewnętrznymi podczas ładowania strony [index], zrobi to w funkcji [fetch];
  • strona wygenerowana przez serwer i strona wygenerowana przez klienta muszą być identyczne, jeśli chcemy uniknąć efektu „podskakiwania” spowodowanego tym, że strona klienta wizualnie zastępuje stronę wysłaną przez serwer i pierwotnie wyświetloną;

Poznamy inne aspekty funkcji [nuxt] na podstawie nowego przykładu.