Vue Router i TypeScript - jak zrobić frontowy routing?

Dzień dobry! Mam nadzieje, że macie się dobrze, wakacje minęły pogodnie i jesteście głodni wiedzy. Dzisiaj powrócimy do Vue zaliczając kolejny, niezbędny element z stacku Vue: Vue Router. Zaczniemy prosto dlatego zapraszam wszystkich początkujących do nauki!

Krótki blok reklamowy: Facebook, Instagram. Implementacja - jak zawsze - w TypeScriptcie.

Konfiguracja Typescripta jest w tym poście.

Instalacja

Instalacja Vue Routera polega na pobraniu paczki vue-router:

npm install vue-router

Możemy zacząć od przekazania Vue Routera w konstruktorze Vue, za chwilę go stworzymy:

// front.ts
import Vue from 'vue';
import App from 'Front/components/app.vue';
import { router } from 'Front/routing/router';

const v: Vue = new Vue({
    router,
    el: '#app',
    template: '<App/>',
    components: {
        App,
    },
});

To czas na konfigurację VueRoutera, która na początku będzie polegała na zmapowaniu komponentów i ścieżek. Musimy wiedzieć pod którym adresem wyświetli się jaki komponent.

// Front/routing/router/index.ts
import VueRouter, { RouteConfig } from "vue-router";
Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
    { path: 'login', component: Login},
    { path: '/', component: StoryList},
]
    
export const router = new VueRouter({
    routes
});

Musimy także znaleźć miejsce w aplikacji w którym wyświetlimy komponent z wskazanej ścieżki. Może być to w głównym komponencie, gdy będziemy mieli każdą podstronę inną albo pomiędzy nagłówkiem a stopką, gdy zdecydujemy się na proste SPA.

W perfekcyjne miejsce wkleimy komponent, który pełni rolę slota:

// part of front/components/app.vue   
<template>
    <div class="layout">
        <div class="layout_content">
            <HeaderNavigation></HeaderNavigation>
            <router-view></router-view>
        </div>

        <Footer></Footer>
    </div>
</template>

Ok, to jak sprawdzić czy działa? Najlepiej stworzyć linka i nacisnąć w niego.

Zapewne za chwilę zaczniecie się zastanawiać dlaczego nie stworzyć zwykłej kotwicy <a>? Po synchronizacji z paskiem adresu wszystko powinno działać.
Tak, będzie działać, lecz stracimy część funkcjonalności.

Skąd to wynika? Z tego, że gdy linki obsługuje Vue Router to stara się maksymalnie zminimalizować przeładowania. Na przykład gdy będąc na podstronie https://twojastrona.com/#/boo klikniesz w link przekierowujący na tę samą podstronę, nie załaduje się żaden nowy komponent.

To jest jeden z wielu powodów dlaczego warto używać komponentu Vue Routera <router-link> do wewnętrznych linków.

<router-link :to="{ name: ROUTINGS.MAIN }">Nazwa linka</router-link>

Jeśli chcemy wykonać przekierowanie w kodzie wystarczy wykonać metodę push z odpowiednio zbudowanym obiektem.

router.push({ path: '/' });

Proste? To dobrze, podstawy mamy już za sobą!

Parametry w URL

Kolejnym częstym przypadkiem jest parametr w URL. Pokażę Wam jak zaimplementować taki URL: https://twojastrona.com/#/story/5 który otwiera opowiadanie o id równym pięć. Będzie on mapowany z stringa https://twojastrona.com/#/story/:id gdzie :id będzie miejscem wstawienia parametru.

Aby to uzyskać najlepiej aby nasze routingi miały zdefiniowaną unikalną nazwę. Osobiście stosuję enumy, chociaż gdy macie potrzebę iterowania po wszystkich routingach - na przykład w celu stworzenia menu - to równie dobre będą obiekty.

// Front/routing/router/routings.ts
export const ROUTING_URLS = {
    MAIN: '/',
    LOGIN: '/login',
    STORIES: '/stories',
    STORY: '/story/:id',
    ERROR404: '/404'
}

Możemy zaimplementować różną logikę:

  • parametr może być wymagany, a jego brak skutkuje przeniesieniem do innej podstrony np.: podstrony z błędem. Przykładem jest na przykład podstrona z opowiadaniem, gdzie gdy nie wiemy jakie opowiadanie mamy wyświetlić, to nie wyświetlamy żadnego.
  • parametr może być opcjonalny i jego brak skutkuje np.: nie pojawieniem się jakiegoś elementu
  • parametr może być opcjonalny i posiadać wartość domyślną np.: jeśli wyświetlając listę opowiadań nie podamy wartości paginacji to zakładamy, że chodzi o pierwszą stronę. Tak więc https://twojastrona.com/#/stories i https://twojastrona.com/#/stories/1/ będą wyświetlały to samo.

Wszystkie obecne parametry są podstępne w globalnym obiekcie this.$route.params, jeśli ustawimy opcję props: true. W innych przypadkach możemy przypisać obiekt, jeśli chcemy podać stałą wartość lub... funkcję, jeśli chcemy stworzyć parametr wyliczanych z innych :)

import Vue from 'vue';
import VueRouter, { RouteConfig } from "vue-router";
import DisplayStory from 'Front/components/story/DisplayStory.vue';
import { ROUTING_URLS } from 'Front/routing/routings';

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
    { path: ROUTING_URLS.STORY, component: DisplayStory, name: ROUTING_URLS.STORY, props: true },
    { path: ROUTING_URLS.STORY2, component: DisplayStory, name: ROUTING_URLS.STORY, props: { id: 1 },
        { path: ROUTING_URLS.STORY3, component: DisplayStory, name: ROUTING_URLS.STORY, props: props: route => ({ query: route.query.id }),
]
    
export const router = new VueRouter({
    routes
});

Więcej dynamicznych slotów

Przyjrzyjmy się temu co tej pory zrobiliśmy: w naszej testowej aplikacji mamy nagłówek, stopkę i treść. Nagłówek i stopka są stałe, a treść jest zależna od routingu i wyświetlana w komponencie <router-view>.

Tylko co z sytuacja kiedy na różnych podstronach chcemy wyświetlać nie tylko inną treść ale także inny nagłówek?

Jak pewnie się domyślacie, Vue oferuje rozwiązanie tego problemu, bowiem slot z którego korzystaliśmy to jedynie slot domyślny i można ich napisać nieskończenie wiele. Także z:

<Header />
<router-view></router-view>
<Footer />

Można zrobić:

<router-view name="myheader"></router-view>
<router-view></router-view>
<Footer />

i uzyskać możliwość zmiany nagłówka w zależności od podstrony. Jedyną różnicą jest rozróżnienie slota atrybutem name. Musimy tylko jeszcze zadeklarować jaki widok ma się wyświetlać po jakim atrybutem:

const router = new VueRouter({
  routes: [{
    path: '/story/',
    components: {
        default: DisplayStory,
        myheader: StoryHeader
    },
  }]
});

Zagnieżdżone widoki

Skoro już wiemy, że da się spokojnie używać więcej niż jednego slotu router-view to pewnie przyszło Wam do głowy, że dobrze byłoby je zagnieżdżać.

Otóż da się i wcale nie jest to takie trudne! Wystarczy - poza dodaniem komponentu <router-view>  w innym komponencie wyświetlanym za pomocą <router-view> - zdefiniować potomków w konfiguracji Vue Routera za pomocą klucza children.

const router = new VueRouter({
  routes: [
    { path: '/author/:id', component: Author,
      children: [
        {
          // /author/:id/profile
          path: 'profile',
          component: AuthorProfile
        },
        {
          // /author/:id/stories
          path: 'stories',
          component: AuthorPosts
        }
      ]
    }
  ]
})

Podsumowanie

Mam nadzieje, że się podobało i początkujący programiści Vue.js już lecą zaimplementować swój pierwszy Vue Router :) Niestety temat nie dość, że nie został wyczerpany to jeszcze ledwo co zaczęty, gdyż Vue Router - i w ogóle frontowy routing - kryje dużo więcej: middleware, lazy-loading, sprawdzanie ścieżek to tylko przykłady. Na razie nie jest to jeszcze wersja, która mogłaby się pojawić na produkcji w standardowej aplikacji. Będę kontynuować temat w następnych wpisach, więc wyczekujcie cierpliwie :)

Zapraszam do zadawania pytań i komentowania, w szczególności jeśli coś jest niejasne :)

Trzymajcie się!

Polecam też inne wpisy o Vue na moim blogu:

Kamila

Dziękuję za poświęcony czas, będzie mi bardzo miło jak zostawisz komentarz :)