client-side routing with history API
in createApp, create a router as root component
router reads current URL and renders the corresponding component
thinking of "mapping a URL to a component view"
Installation
yarn add vue-router@4
https://next.router.vuejs.org/guide/#router-view
define a root component with <router-view>
define and import some route components
define routes:
array of route objects, each is: path map to component
create router instance with:
a history mode (see below)
route definition
create app with root component
app.use(routerInstance); app.mount('#app')
createWebHashHistory()
Use # before actual URL (root / redirects to liks "http://localhost:3000/#/")
because whatever after "#" is never sent to server, it does not require special treatment on server, everything goes /
bad impact in SEO
createWebHistory()
URL looks normal
Server needs to catch-all (whatever url) to give the root page (single page)
no server side 404, use client-side catch-all route option to render a 404 page
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/users/:id', component: User }, // value available under $route.params (e.g. $route.params.id)
{ path: '/:parameterName(.*)*', name: 'NotFound', component: NotFound }, // param regexp (after colon ":"), use regular expression to catch, then continue match the rest
value is put under `$route.params.parameterName` as an array of path segments
{ path: '/user-:anotherName(.*)', component: UserGeneric },
Difference between regex "+","*" - must separate by /, return as array
"+" : 1 or more, "*" 0 or more, "?" 0 or 1
{ path: '/:chapters+' }, // /:chapters -> matches /one, /one/two, /one/two/three, etc
{ path: '/:chapters*' }, // /:chapters -> matches /, /one, /one/two, /one/two/three, etc
{ path: '/:chapters(\\d+)+' }, // only match numbers, /1, /1/2, etc
{ path: '/user/:username', name: 'user', component: User }
To resolve (construct and refer) to a named route, must pass an object
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
params: for repeatable parameters, must pass an array
Boolean mode { path: '/user/:id', component: User, props: true }
route.params will be set as component props
for named view, set for each name: props: { default: true, sidebar: false }
Object mode: props: { newsletterPopup: false }
set the props key/value as is
Function mode: props: route => ({ query: route.query.q })
parameters: the route object
return: the props object (key/value pairs for props)
{
path: 'new',
component: PostsNew,
// only authenticated users can create posts
meta: { requiresAuth: true }
},
Accessible by:
on route location
navigation guards
Meta shallow merging:
because of nesting, URL can match multiple route records (parent -> child)
all can be found in $route.matched, can examine individually
a shallow merged meta(s) is in $route.meta - merge from parent to child, so child over-rides parent. use for e.g. "requireAuth"
Typing meta field: see https://next.router.vuejs.org/guide/advanced/meta.html#typescript
$route:
matched - an array of all matched route records
meta - merge (shallow) of all meta fields, from parent to child (so child over-rides parent)
layout component - components with one or more <route-view> provide layouts
router options - maps URL to a "fill out" of the <route-view>s with components
so think of a URL as a "screen configuration" + parameters (query, path parameter)
Defining route mapping:
a route option include "path" and "children"
"children" is an array of path options (like "routes" in root router)
in children path options, leading "/" is treated as root - just the same
otherwise as relative to already matched path
Rendering
"path" in parent route option map to a component that has <router-view>
the component further matched by a child route opton renders inside the nested <router-view>
Defining :
a route option include "path" and "components"
"components" is an object:
property key is "name" of <router-view>
value is component
Rendering
Include multiple <router-view name="nameOfYourView"> in a (layout) component
"components" will fill multiple router-view with matching names
https://next.router.vuejs.org/guide/essentials/named-views.html#nested-named-views
Same as above, a children[] has an option {path:"somePath", components: {}}
Basics:
redirect: user visits /home, the URL will be replaced by /, and then matched as /
alias: user visits /home, the URL remains /home, but it will be matched as if the user is visiting /
can be a string or an array of strings
Examples
{ path: '/home', redirect: '/' }
{ path: '/home', redirect: { name: 'homepage' } } // target a named route
// a URL mapping function, can use to do relative redirecting
{
path: '/search/:searchText',
redirect: to => {
// the function receives the route object as the argument
// we return a redirect path/location here.
return { path: '/search', query: { q: to.params.searchText } }
},
},
Notes
Navigation Guards are not applied to the route that redirects, only on its target
redirect:
normal route option with redirect does NOT has component - it goes to the target
nested route (has "children") with redirect has component (behaviour? test to see)
When map to same component, it's reused (meaning some lifecycle event not triggered)
to handle change:
watch $route.param change
created() {
this.$watch(
() => this.$route.params,
(toParams, previousParams) => {
// react to route changes...
}
)
},
or use beforeRouteUpdate navigation guard
async beforeRouteUpdate(to, from) {
// react to route changes...
this.userData = await fetchUser(to.params.id)
},
Link
do NOT use <a href="/">
use <router-link to="/">Go to Home</router-link>
To pass location descriptor object for named route <router-link :to="...">
$router.push() - returns a Promise, normal navigation
$router.replace() - navigation without pushing a new history entry
$router.go() - accepts a number, backward / forward number of records, fail silently if there's not enough records
$router.forward() - same as $router.go(1)
$router.back() - same as $router.go(-1)
Navigation is asynchronous. If want to do something AFTER it's done, use await.
If want to learn about the result (which might be cancelled):
const navigationFailure = await router.push('/my-profile')
path - string - path (use either "path" or "name"+"params")
name - string - name (for named route)
query - object - ?key=value
hash - string - '#something'
replace - boolean - act as replace()
params - object - is IGNORED if "path" is provided
parameterName
parameterValue:
single string
array of strings (for repeatable parameters)
"" empty string for optional parameter
// literal string path
router.push('/users/eduardo')
// object with path
router.push({ path: '/users/eduardo' })
// named route with params to let the router build the url
router.push({ name: 'user', params: { username: 'eduardo' } })
// with query, resulting in /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// with hash, resulting in /about#team
router.push({ path: '/about', hash: '#team' })
Navigation triggered.
Call beforeRouteLeave guards in deactivated components.
Call global beforeEach guards.
Call beforeRouteUpdate guards in reused components.
Call beforeEnter in route configs.
Resolve async route components.
Call beforeRouteEnter in activated components.
Call global beforeResolve guards.
Navigation is confirmed.
Call global afterEach hooks.
DOM updates triggered.
Call callbacks passed to next in beforeRouteEnter guards with instantiated instances.
guard navigation by redirecting or cancelling it
ways to hook:
globally (register with router instance)
router.beforeEach((to, from, next)=>{}) - triggered on every navigation
router.beforeResolve((to)=>{}) - after all in-component guards and async route components resolved, before navigation is confirmed - do something right before user enters a page
router.afterEach((to, from, failure)=>{}) - after navigation
pre-route (register in route option)
in-component (register with component options)
beforeRouteEnter(to, from) {} // before route (that renders this component) is confirmed, no access to "this"
beforeRouteUpdate(to, from){} // when route has changed but component is reused in new route (i.e. params change), has access to "this"
beforeRouteLeave(to, from){} // when route is about to be navigated away, has access to "this"
router.beforeEach((to, from) => {
// explicitly return false to cancel the navigation
return false
})
action:
may return false to cancel navigation
may return RouteLocationRaw https://next.router.vuejs.org/api/#routelocationnormalized to redirect
throw Error - cancel navigation and call router.onError() callback
parameters: RouteLocationNormalized https://next.router.vuejs.org/api/#routelocationnormalized
may resolve asynchonously - navigation will be pending before resolve
router.beforeResolve(async to => {
action: the same
usage: do something right before user enters a page - however if user cannot enter the page for some other reason, not to
router.afterEach((to, from, failure) => { // do something })
Cannot affect nevigation
Useful for:
analytics
changing title
check navigation failure
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
Only trigger when entering the route (from a DIFFERENT route) - no triggering when params / query / hash changes
can pass an array of functions - will be called in turn
no access to "this"
can pass a callback to "next", the callback receive the component as first parameter next(vm => {})
can access "this"
to prevent user from accidentally leaving a route with unsaved data
ask user, then return false to cancel
fetch data in component's "created" hook
display a loading state
fetch in "beforeRouteEnter" guard, only call "next" to set data when fetch is complete
user stay in previous view - display a progress bar
import { useRouter, useRoute } from 'vue-router'
// inside "setup"
const router = useRouter()
const route = useRoute()
The route object is reactive. Avoid watching the whole object, just the param expected to change.
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// inside "setup
// same as beforeRouteLeave option with no access to `this`
onBeforeRouteLeave((to, from) => {})
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate(async (to, from) => {})
export declare function useLink(props: RouterLinkOptions): {
route: ComputedRef<RouteLocationNormalized & { href: string }>,
href: ComputedRef<string>,
isActive: ComputedRef<boolean>,
isExactActive: ComputedRef<boolean>,
navigate: (event?: MouseEvent) => Promise(NavigationFailure | void),
}
Accepts: prop object that can be passed to <router-link>
returns:
route: RouteLocationNormalized + href:string
href: string
isActive
isExactActive
navigate: (event?)=>Promise()
ignored
Controll scrolling behaviour, needs browser support.
How:
provide router object (when creating) with function scrollBehavior (to, from, savedPosition) { // return position }
savedPosition: available if this is a popstate nevigation (browser back/forward)
return a ScrollToOptions object, scroll to
el: "#elementId" - or a CSS selector or DOM element
top: 0 - a number
left: 0 - a number
behavior: 'smooth' - if browser supports "scroll behavior"
return false - no scrolling
return the savedPosition: result in a native-like behaviour with back/forward buttons
return a Promise - can be used to delay scrolling
// instead of static import, use dynamic import
// and do not use "defineAsyncComponent" (async component), that's for use inside component, not inside router
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
instead of a component constructor, give a function that returns a Promise that resolves to the constructor
inside the function, use dynamic import
will only fetch it when entering the page for the first time
"In general, it's a good idea to always use dynamic imports for all your routes."
Webpack Grouping Components in the Same Chunk, see document
See document for creating a component that extends <router-link>
already on the page
a guard cancelled the navigation
a new guard takes place while old one not finished
a guard redirects
a guard throws
const navigationFailure = await router.push('/my-profile')
false value (undefined) - success
Error instance
import { NavigationFailureType, isNavigationFailure } from 'vue-router'
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
// show a small notification to the user
showToast('You have unsaved changes, discard and leave anyway?')
}
They can be differentiated using the isNavigationFailure and NavigationFailureType. There are three different types:
aborted: false was returned inside of a navigation guard to the navigation.
cancelled: A new navigation took place before the current navigation could finish. e.g. router.push was called while waiting inside of a navigation guard.
duplicated: The navigation was prevented because we are already at the target location.
API: https://next.router.vuejs.org/api/#navigationfailure
if (router.currentRoute.value.redirectedFrom) {
const removeRoute = router.addRoute(routeRecord) // return callback used to remove
only register, does not cause navigation (if the current location matches)
inside navigation guard, if want to trigger redirection, do NOT call replace(), return new location to redirect
add a new route with same name - removes the old route first
call the callback
route.removeRoute() - remove by name
When route is removed, all aliases and children are removed
router.addRoute('admin', { path: 'settings', component: AdminSettings }) // pass name as first parameter, adds to the children
router.hasRoute(): check if a route exists
router.getRoutes(): get an array with all the route records.
The "$route" object:
is reactive
avoid watching the whole object, just the param expected to change