Komplexe Webanwendungen mit Vue.js, Teil 2

Routing

Ähnlich wie Vuex ist der Vue-Router eine separat zu installierende Bibliothek. Das Vue-Core-Team übernimmt auch bei ihr Entwicklung und Wartung. Da Routing aber nicht so tief in die interne Architektur eines Frameworks integriert ist, lassen sich problemlos auch andere Routing-Libraries einsetzen. Es gibt vielfältige Community-Alternativen.

Der Vue-Router unterstützt alles, was man von einer modernen Routing-Library erwartet: dynamische Parameter, verschachtelte Routen, eine vollständige API sowie Ereignisse und Berechtigungsprüfungen in Form von Navigation Guards. Auch asynchrones Laden von Routen (Lazy Loading) und die Zwischenspeicherung von Scroll-Positionen sind damit umsetzbar.

Der Router ist als Vue-Plug-in realisiert. Vue konfiguriert ihn wie üblich über ein Objekt. Der wichtigste Parameter des Konfigurationsobjekts ist das Route-Array. Es bildet Routen auf Komponenten ab und konfiguriert alle weiteren Details jeder Route.

Ein ganz einfaches Beispiel sieht so aus:

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import HelloWorld from './views/HelloWorld.vue'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/hello/:id',
name: 'hello',
component: HelloWorld
}
]
})

Es sind zwei Routen definiert. Die zweite Route enthält den dynamischen Parameter id. Mittels $route.params.id kann auf dessen Wert in der Komponente zugegriffen werden. Alternativ setzt man das Attribut props innerhalb der Routen-Konfiguration auf true. Dadurch übergibt Vue die Routen-Parameter als normale Properties an die Komponente, sodass die Komponente nicht mehr an den Router gekoppelt ist.

Das HTML-Template verwendet einen Platzhalter, an dessen Stelle die Komponente der aktuellen Route gerendert wird: <router-view/>. Weiterhin kann man Links auf andere Seiten über <router-link to="/home">Home</router-link> definieren oder über die API in eigenen Funktionen navigieren. Eine Seite kann mehrere Platzhalter enthalten, die unterschiedliche Namen benötigen. In der Route sind dann ebenfalls mehrere Komponenten definiert.

Möchte man vor der Navigation Aktionen ausführen, zum Beispiel Berechtigungen überprüfen, Daten laden oder prüfen, ob die Session noch gültig ist, kann man das im beforeEnter-Hook ausführen. Als Parameter erhält man Informationen zur aktuellen Route, zur Zielroute und einen Callback, um die Navigation auszuführen. Dadurch ist es möglich, asynchrone Aktionen auszuführen. Weiterhin können Entwickler die Zielroute verändern, beispielsweise wenn ein Redirect auf eine Login-Seite gewünscht ist. Es ist auch ein geeigneter Ort, um Daten zu laden und anschließend als Properties an die Zielkomponente zu übergeben:

{
path: '/book/:id',
name: 'book',
component: BookComponent,
beforeEnter(routeTo, routeFrom, next) {
Promise.resolve('ISBN' + routeTo.params.id)
.then(isbn => {
routeTo.params.isbn = isbn
next()
}).catch(() => {
next({ name: '404' })
})
},
props: true
}

Die Route erhält eine ID und lädt weitere Daten. Im Beispiel ist eine asynchrone Aktion über Promise.resolve angedeutet. Das Ergebnis wird in den Routen-Parameter isbn geschrieben. Anschließend ruft die Anwendung next auf, um die Navigation auszuführen. Tritt ein Fehler auf, erfolgt die Weiterleitung auf eine Fehlerseite.

Die Zielkomponente BookComponent erhält nun die ISBN als Property und ist vollständig von der Herkunft der Information entkoppelt. Die Logik im beforeEnter-Callback könnte die Information auch aus einem Vuex-Store laden.

Möchte man die Logik zum Laden der Daten nicht von der Komponente trennen, aber sie trotzdem vor der eigentlichen Navigation ausführen, ist ein Nutzen der Navigation-Hooks innerhalb der Zielkomponente denkbar:

export default {
name: 'BookComponent',
data() {
return {
isbn: null
}
},
beforeRouteEnter(routeTo, routeFrom, next) {
Promise.resolve("ISBN" + routeTo.params.id)
.then(isbn => {
next(viewModell => {
viewModell.isbn = isbn
})
})
.catch(() => {
next({ name: "404" })
});
}
}

Bevor man zur Komponente navigiert, startet die Callback-Methode beforeRouteEnter. Deshalb kann man noch nicht auf deren Kontext zugegreifen. Um die geladenen Daten an die Komponente zu übergeben, erhält die next-Funktion eine Callback-Methode. Ihr übergibt der Vue-Router den Kontext, sobald er erzeugt ist (im Beispiel mit viewModell bezeichnet).

Folgendes ist dabei allerdings zu beachten: der Lifecycle-Hook beforeRouteEnter wird nur ausgeführt, wenn die Komponente noch nicht geladen war. Für Fälle, bei denen man von einer Instanz der Komponente zur nächsten navigiert, ist die Verwendung von beforeRouteUpdate alternativ oder zusätzlich möglich.

Lazy Loading mit dem Vue-Router

Eine große Anwendung besteht aus vielen Routen und Komponenten. Müsste man sie alle auf einmal laden, wäre der erste Aufbau der Anwendung sehr langsam. Um das zu verhindern, lädt die Anwendung Inhalte erst, wenn ein Nutzer dorthin navigiert – auch bekannt als Lazy Loading. Der Vue-Router unterstützt das ebenfalls. Die eigentliche Arbeit überlässt man der Code-Splitting-Funktion von webpack. Damit das funktioniert, müssen lediglich die Routen asynchron geladen werden.

Anstatt beim Aufbau des Routen-Arrays die Komponenten direkt als JavaScript-Module zu laden, definiert man eine Funktion, die ein Promise-Objekt zurückliefert.

Der Import ohne Code-Splitting funktioniert so

import Home from './views/Home.vue'
import HelloWorld from './views/HelloWorld.vue'

während der Import mit Code-Splitting wie folgt aussieht:

const Home = () => import('./views/Home.vue')
const HelloWorld = () => import('./views/HelloWorld.vue')

Der Code ruft das import-Statement als Funktion auf und gibt ein Promise-Objekt zurück. Die weitere Definition des Routen-Arrays kann unverändert bleiben.