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.
Kotwice <router-link>
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
ihttps://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:
- Implementacja Vuex
- Implementacja frameworka UI (Vue + TypeScript)