Skip to content

4. Приклад [nuxt-01]: маршрутизація та навігація

Ми створимо серію простих прикладів, щоб поступово розібратися в роботі додатка [nuxt]. Почнемо з перенесення додатка [vuejs-11] з документа |Вступ до фреймворку VUE.JS на прикладі|, щоб спочатку з’ясувати, чим відрізняється організація коду додатка [nuxt] від організації коду додатка [vue].

4.1. Структура проекту

Проєкт [vuejs-11] був проєктом навігації між поданнями:

Image

Структура вихідного коду проекту [vuejs-11] була такою:

Image

  • [main.js] — це скрипт, що виконувався під час запуску додатка [vue];
  • [router.js] визначав правила маршрутизації;
  • [App.vue] — це структурний вигляд додатка. Він організовував макет різних виглядів;
  • [Component1, Component2, Component3, Layout, Navigation] — це компоненти, що використовувалися в різних поданнях додатка;

Під час перенесення додатка [vue] [1] у додаток [nuxt] [2]:

  • скрипти, що виконуються під час запуску додатка, повинні бути вказані у ключі [plugins] файлу [nuxt.config.js]. Крім того, можна відокремити скрипти, призначені для сервера [nuxt], від тих, що призначені для клієнта [nuxt];
  • погляд [App.vue] слід встановити в папку [layouts] і перейменувати на [default.vue];
  • компоненти [Component1, Component2, Component3], які є цілями маршрутизації, мають бути переміщені до папки [pages]. Один із них, який слугує стартовою сторінкою, має бути перейменований на [index.vue]. Тут ми перейменували такі файли:
    • [Component1] --> [index]: відображає текст [Home];
    • [Component2] --> [page1]: відображає текст [Page 1];
    • [Component3] --> [page2]: відображає текст [Page 2];

[nuxt] використовує вміст папки [pages] для динамічного формування таких маршрутів:

1
2
3
{ name :’index’, ‘path’ :’/’}
{ name :’page1’, ‘path’ :’/page1’}
{ name :’page2’, ‘path’ :’/page2’}

Отже, файл [router.js], який використовується в проєкті [vue], стає непотрібним у проєкті [nuxt].

Файл конфігурації [nuxt.config.js] матиме такий вигляд:


export default {
  mode: 'universal',
  /*
   ** Headers of the page
   */
  head: {
    title: 'Introduction à [nuxt.js]',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'ssr routing loading asyncdata middleware plugins store'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: { color: '#fff' },
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Документ: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Документація: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Документація: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios'
  ],
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {},
  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {}
  },
  // каталог з вихідним кодом
  srcDir: 'nuxt-01',
  // маршрутизатор
  router: {
    // кореневий каталог додатка URL
    base: '/nuxt-01/'
  },
  // сервер
  server: {
    // порт служби, за замовчуванням 3000
    port: 81,
    // мережеві адреси, на яких відбувається прослуховування, за замовчуванням localhost: 127.0.0.1
    // 0.0.0.0 = усі мережеві адреси комп’ютера
    host: '0.0.0.0'
  }
}
  • рядок 62: вказується папка, що містить вихідний код проєкту [dvp];
  • рядок 66: вказується коренева папка додатка [dvp] (можна вказати будь-яку);
  • рядок 43: зверніть увагу, що бібліотека [bootstrap-vue] вказана у конфігурації;

4.2. Портування файлу [main.js]

Файл [main.js] проекту [vuejs-11] мав такий вигляд:


// імпорт
import Vue from 'vue'
import App from './App.vue'

// плагіни
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);

// завантажувач
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

// маршрутизатор
import monRouteur from './router'

// конфігурація
Vue.config.productionTip = false

// інстанціювання проєкту [App]
new Vue({
  name: "app",
  // головний вигляд
  render: h => h(App),
  // маршрутизатор
  router: monRouteur,
}).$mount('#додаток')

Окрім [imports], код виконує такі дії:

  • рядки 5–11: використання бібліотеки [bootstrap-vue]. Цю роботу тепер виконує модуль [bootstrap-vue/nuxt] із рядка 43 конфігураційного файлу [nuxt.config.js];
  • рядки 14 та 25: використання файлу маршрутизації [router.js]. Цю роботу тепер автоматично виконує додаток [nuxt] на основі дерева папок [pages];
  • рядки 20–26: створення екземпляра головного виду програми. У програмі [nuxt] головним видом є [layouts/default.vue];

Файл [main.js] відтепер втратив своє призначення. Якби він мав якесь призначення, його б зазначили у ключі [plugins] у рядку 30 конфігураційного файлу [nuxt.config.js];

4.3. Головний вигляд [default.vue]

Image

Головний вигляд [layouts / default.vue] має такий вигляд:


<template>
  <div class="container">
    <b-card>
      <!-- повідомлення -->
      <b-alert show variant="success" align="center">
        <h4>[nuxt-01] : routage et navigation</h4>
      </b-alert>
      <!-- поточний вигляд маршрутизації -->
      <nuxt />
    </b-card>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
  • У рядку 9, у проєкті [vuejs-11], замість тегу <nuxt />, який використовується тут, був тег <router-view />. Обидва, здається, можна використовувати. Я спробував обидва, але змін не помітив. Я залишив тег <nuxt />, який рекомендується. Він відображає поточний вигляд, тобто цільову сторінку поточного маршруту;

4.4. Компоненти

Image

Порівняно з проєктом [vuejs-11], компоненти [layout, navigation] не змінюються:

[components / layout.vue]


<!-- розташування переглядів -->
<template>
  <!-- рядок -->
  <div>
    <b-row>
      <!-- двоколонкова область -->
      <b-col v-if="left" cols="2">
        <slot name="left" />
      </b-col>
      <!-- зона з десятьма стовпцями -->
      <b-col v-if="right" cols="10">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

<script>
export default {
  // параметри
  props: {
    left: {
      type: Boolean
    },
    right: {
      type: Boolean
    }
  }
}
</script>

Цей компонент слугує для структурування сторінок додатка у дві колонки:

  • рядки 7–9: ліва колонка, що займає 2 колонки Bootstrap;
  • рядки 11–13: права колонка, що займає 10 колонок Bootstrap;

[navigation.vue]


<template>
  <!-- меню Bootstrap із трьома пунктами -->
  <b-nav vertical>
    <b-nav-item to="/" exact exact-active-class="active">
      Home
    </b-nav-item>
    <b-nav-item to="/page1" exact exact-active-class="active">
      Page 1
    </b-nav-item>
    <b-nav-item to="/page2" exact exact-active-class="active">
      Page 2
    </b-nav-item>
  </b-nav>
</template>

Цей компонент відображає три навігаційні посилання:

Image

Щоб дізнатися, які значення слід вказати для атрибутів [to] у рядках 4, 7 та 10, потрібно переглянути папку [pages] [2]:

  • сторінка [index] матиме URL [/];
  • сторінка [page1] матиме сторінки URL та [/page1];
  • сторінка [page2] матиме URL та [/page2];

Компонент [navigation] також можна записати таким чином:


<template>
  <!-- меню Bootstrap із трьома пунктами -->
  <b-nav vertical>
    <nuxt-link to="/" exact exact-active-class="active">
      Home
    </nuxt-link>
    <nuxt-link to="/page1" exact exact-active-class="active">
      Page 1
    </nuxt-link>
    <nuxt-link to="/page2" exact exact-active-class="active">
      Page 2
    </nuxt-link>
  </b-nav>
</template>

Тег <b-nav-item> замінено на тег <nuxt-link>, який позначає маршрутизаційне посилання. Під час виконання я не помітив суттєвої різниці, нічого такого, що могло б схилити шальки терезів на користь одного тегу замість іншого.

4.5. Сторінки

Image

На сторінці [index.vue] відображається такий вигляд:

Image

Код сторінки такий:


<!-- головна сторінка -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення-->
    <b-alert slot="right" show variant="warning">
      Home
    </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
  },
  // життєвий цикл
  beforeCreate(...args) {
    console.log('[home beforeCreate]', 'process.server=', process.server,
'process.client=', process.client, "кількість аргументів=", args.length)
  },
  created(...args) {
    console.log('[home created]', 'process.server=', process.server,
'process.client=', process.client, "кількість аргументів=", args.length)
  },
  beforeMount(...args) {
    console.log('[home beforeMount]', 'process.server=', process.server,
'process.client=', process.client, "кількість аргументів=", args.length)
  },
  mounted(...args) {
    console.log('[home mounted]', 'process.server=', process.server,
'process.client=', process.client, "кількість аргументів=", args.length)
  }
}
</script>
  • рядок 5: компонент навігації розміщений у лівій колонці;
  • рядки 7–9: у правій колонці розміщено сповіщення;

У розділі <script> ми розміщуємо код у функціях життєвого циклу сторінки [beforeCreate, created, beforeMount, beforeMounted]. Ми хочемо з’ясувати, які з них виконуються сервером []nuxt, а які — клієнтом [nuxt]. Нагадаємо дві речі:

  • коли сторінка запитується або під час запуску додатка, як у випадку зі сторінкою [index], або вручну користувачем, який оновлює сторінку в браузері чи вводить URL вручну, вона спочатку видається сервером [nuxt]. Сервер інтерпретує наведений вище код і виконує JavaScript, що міститься в ньому;
  • коли сторінка, надіслана сервером [nuxt], надходить до браузера, вона надходить із кодом клієнта [nuxt]. Останній знову інтерпретує зазначену вище сторінку;
  • за допомогою логів ми хочемо з’ясувати, хто що робить, щоб краще зрозуміти цей процес;
  • рядки 30–31: у функції використовується глобальний об’єкт [process], який існує як на сервері, так і на клієнті:
    • [process.server] має значення «true», якщо код виконується сервером, і «false» в іншому випадку;
    • [process.client] має значення «true», якщо код виконується на клієнті, і «false» в іншому випадку;
    • оскільки змінна [process] не оголошена в коді, доводиться додати рядок 14 для [eslint]. Рядок [16] необхідний, оскільки інакше [eslint] повідомляє про інший тип помилки через змінну [process]. Чи необхідний рядок 15 для того, щоб можна було використовувати [console] у функціях життєвого циклу?
  • рядок 29: ми також хочемо дізнатися, чи отримують функції життєвого циклу аргументи. Ми дійсно виявимо, що [nuxt] передає інформацію до певних функцій. Ми хочемо дізнатися, чи входять до них функції життєвого циклу;
  • повторюємо той самий код для всіх чотирьох функцій;

4.6. Файл [nuxt.config.js]

Саме він керує виконанням проєкту [dvp]. Його описано на сторінці 33.

4.7. Виконання проєкту

Ми виконуємо проект:

Image

Відображається така сторінка:

Image

Після встановлення в браузері додаток [nuxt] стає звичайним додатком [vue]. Тому ми не будемо коментувати роботу клієнтської частини додатка [nuxt-01]. Це вже було зроблено в проєкті [vuejs-11] у документі |Вступ до фреймворку VUE.JS на прикладі|.

Додаток [nuxt] відрізняється від додатка [vue] лише у двох моментах:

  • початковий запуск додатка, який відображає стартову сторінку;
  • кожного разу, коли користувач тим чи іншим чином ініціює оновлення браузера;

У цих двох випадках:

  • запитана сторінка надається сервером;
  • отримана сторінка обробляється клієнтом;

Давайте розглянемо журнали запуску додатка (F12 у браузері):

Image

  • у [1] — журнали сервера (process.server=true). Вони з’являються з попередньою позначкою [Nuxt SSR] (SSR= Server Side Rendered);
  • у [2] — журнали клієнта в браузері (process.client=true);

З цих журналів можна зробити висновок, що:

  • сервер виконує функції [beforeCreate, created] життєвого циклу;
  • клієнт виконує функції життєвого циклу [beforeCreate, created, beforeMount, mounted];
  • сервер обробив сторінку раніше, ніж клієнт;
  • в обох випадках жодна з виконуваних функцій не отримує аргументів;

Тепер розглянемо вихідний код отриманої сторінки (опція [Code source de la page] у браузері):


<!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-01/">
  ....
  <link rel="preload" href="/nuxt-01/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_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-01] : routage et navigation</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-01/" target="_self" class="nav-link active nuxt-link-active">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/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
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
    window.__NUXT__ = (function (a, b, c, d, e, f, g, h, i, j) {
      return {
        layout: "default", data: [{}], error: null, serverRendered: true,
        logs: [
          { date: new Date(1574069600078), args: [a, b, c, d, e, f, g, "(repeated 1 times)"], type: h, level: i, tag: j },
          { date: new Date(1574070938091), args: [a, b, c, d, e, f, g], type: h, level: i, tag: j }
        ]
      }
    }("[home beforeCreate]", "process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0", "log", 2, ""));
  </script>
  <script src="/nuxt-01/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-01/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/app.js" defer></script>
</body>
</html>

Коментарі

  • Перше, що можна помітити, — це те, що отриманий код HTML правильно відображає те, що бачить користувач. Це не було так у випадку з додатками [vue], для яких відображуваним вихідним кодом був вихідний код майже порожнього файлу HTML. Саме це отримував браузер. Потім клієнт [vue] брав на себе управління та формував сторінку, яку очікував користувач. Тоді потрібно було перейти на вкладку [inspecteur] у інструментах розробки браузера (F12), щоб дізнатися код HTML відображеної сторінки;
  • рядки 57–67: це скрипт, який відобразив логи з тегом [Nuxt SSR]. Ці логи були згенеровані на стороні сервера, а результати були вбудовані у скрипт, включений у відправлену сторінку;
  • рядки 68–71: скрипти, що утворюють клієнтську частину, яка виконується у браузері;

Скрипти з рядків 68–71 виконуються та перетворюють отриману сторінку. Щоб дізнатися, яка сторінка зрештою відображається для користувача, потрібно перейти на вкладку [inspecteur] у інструментах розробки браузера (F12):

Image

При розгортанні тегу <html> [3] ми бачимо такий вміст:


<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-01/">
  ...
  <link rel="preload" href="/nuxt-01/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/app.js" as="script">

  <script charset="utf-8" src="/nuxt-01/_nuxt/pages_index.js"></script>
  <script charset="utf-8" src="/nuxt-01/_nuxt/pages_page1.js"></script>
  <script charset="utf-8" src="/nuxt-01/_nuxt/pages_page2.js"></script>
</head>
<body>
  <div id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-success" align="center">
              <h4>[nuxt-01] : routage et navigation</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-01/" target="_self" class="nav-link active nuxt-link-active">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/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
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
    window.__NUXT__ = (function (a, b, c, d, e, f, g, h, i) {
      return {
        layout: "default", data: [{}], error: null, serverRendered: true,
        logs: [
          { date: new Date(1574068674481), args: ["[home beforeCreate]", a, b, c, d, e, f], type: g, level: h, tag: i },
          { date: new Date(1574068674482), args: ["[home created]", a, b, c, d, e, f], type: g, level: h, tag: i }
        ]
      }
    }("process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0", "log", 2, ""));
  </script>
  <script src="/nuxt-01/_nuxt/runtime.js" defer=""></script>
  <script src="/nuxt-01/_nuxt/commons.app.js" defer=""></script>
  <script src="/nuxt-01/_nuxt/vendors.app.js" defer=""></script>
  <script src="/nuxt-01/_nuxt/app.js" defer=""></script>

  <iframe id="mc-sidebar-container" ...></iframe>
  <iframe id="mc-topbar-container"...>  </iframe>
  <iframe id="mc-toast-container" ...></iframe>
  <iframe id="mc-download-overlay-container"...></iframe>
</body>

Коментарі

  • на перший погляд, сторінка, що відображається у рядках 19–59, здається такою самою, як і отримана сторінка;
  • рядки 14–16: з’являються три нові скрипти, по одному для кожної сторінки додатка;
  • рядки 76–79: з’являються чотири [iframe];

У рядках 33, 37 та 42 виникають проблеми з посиланнями. Вони здаються звичайними посиланнями, які при натисканні мають надсилати запит на сервер. Однак під час виконання виявляється, що це не так: запит на сервер не надсилається. Щоб зрозуміти, чому так відбувається, потрібно повернутися на вкладку [inspecteur] у браузері:

Image

Бачимо, що у [1, 2] до посилань було прив’язано події. Саме скрипти у рядках 71–74 прив’язали обробники подій до посилань. Отже:

  • сторінка, що відображається клієнтом, візуально ідентична тій, що надіслана сервером;
  • клієнт додав до сторінки динамічну поведінку;

Тепер завантажимо сторінку [page1], вручну ввівши URL замість [http://192.168.1.128:81/nuxt-01/page1]. Журнали виглядатимуть так:

Image

Ми отримуємо ті самі результати, що й для сторінки [index], але для [page1]. Вихідний код отриманої сторінки має такий вигляд:


<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-01] : routage et navigation</h4></div> <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-01/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page1" target="_self" class="nav-link active nuxt-link-active">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/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
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>window.__NUXT__ = { layout: "default", data: [{}], error: null, serverRendered: true, logs: [{ date: new Date(1573917721122), args: ["[page1 beforeCreate]", "process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0"], type: "log", level: 2, tag: "" }] };</script>
  <script src="/nuxt-01/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-01/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/app.js" defer></script>
</body>

Ми отримуємо сторінку того ж типу, що й [index], але з попередженням про перегляд [Page 1] (рядок 30). У рядках 41–44 код клієнта було повернуто разом зі сторінкою. У підсумку, ручне запитування сторінки URL еквівалентно перезапуску додатка. Просто відображена сторінка не обов’язково є головною сторінкою — це та, яку було запрошено. Після отримання сторінки управління переходить до клієнта. Сервер більше не буде задіяний, якщо тільки користувач не вирішить інакше.