A new Router to guide you
Toronto 11th Nov 2019
Eduardo
San Martin Morote
🌍 Vue core team
👨‍💻 Freelance
đź“ŤParis
Routing in you App
the 1st week
Routing in your App
after a year
SPA
Routers
Opinionated
Flexibility
Three kind of routers
- Imperative
- Declarative
- Configuration-based
Imperative
Page.js
âś… Programmatic navigation
❌ Declarative navigation
âś… Navigation Guards
❌ Dynamic routing (add/remove routes)
❌ Declarative routing
Dynamic routing
router.addRoute('/some-route', options)
router.removeRoute('/some-route')
⚠️ not actual API
Reach Router
<Router>
<Home path="/" />
<UserProfileEdit path="/users/:id/edit" />
</Router>
<div>
<Link to="/">Home</Link>
<Link to={`/users/${this.user.id}/edit`} />
</div>
Reach router / React Router
Declarative
âś… Programmatic navigation (w/ hooks)
âś…Â Declarative navigation
❌ Navigation Guards
âś… Dynamic routing
âś…Â Declarative routing
Â
Vue Router
const router = new Router({
mode: 'history',
routes: [
{ path: '/', component: Home },
{ path: '/users/:id', component: UserProfile },
]
})
<div>
<router-link to="/">Home</router-link>
<router-link :to="`/users/${this.user.id}`">
My Profile
</router-link>
</div>
Vue router
Configuration based
âś…Â Programmatic navigation
âś…Â Declarative navigation
âś… Navigation Guards
⚠️ Dynamic routing (add/remove routes)
❌ Declarative routing
đź—‚ HISTORY
🚦 ROUTER
📦 COMPONENTS
📦 COMPONENTES
<router-view/>
<router-link to="/">Home</router-link>
🚦 ROUTER
-
Route matching​
-
​match()
-
​resolve()
-
-
Navigation
-
​​currentRoute
-
push() replace(), ...
-
​beforeEach(), ...
-
-
Creating routes
-
new Router({ routes })
-
addRoutes()
-
đź—‚ HISTORy
-
JS ↔ URL
-
push() replace(), ...
-
listen()
-
The
Good PARTS
📦 router-view
<router-view/>
- Dynamically render current view
- Pass params as props
$route
📦 router-link
- Resolve target location
- Render an anchor tag with link
- Handles click event
- Applies active classes
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'User', params: {id: '2'}}">...</router-link>
$route
$router
Normalized Route
{
path: '/users/2',
fullPath: '/users/2?q=foo'
name: 'UserProfile',
query: { q: foo },
hash: '',
params: { id: '2' },
meta: {}
}
Navigation
Guards
Global Guards
router.beforeEach((to, from, next) => {
// verify roles based on `to`
// ...
// call `next` *once*
if (!isLoggedIn) next('/login') // redirect to login
else if (!isAuthorized) next(false) // abort
else next() // allow navigation
})
Per-route Guards
{
path: '/admin',
component: AdminPanel,
beforeEnter (to, from, next) {
if (isAdmin) next()
else next(false)
},
}
in-component guards
export default {
name: 'AdminPanel',
data: () => ({ adminInfo: null }),
async beforeRouteEnter (to, from, next) {
const adminInfo = await getAdminInfo()
next(vm => {
vm.adminInfo = adminInfo
})
},
}
beforeEach
beforeEnter
beforeRouteEnter
next()
next(false)
/posts ➡️ /admin
/posts
Admin.vue
beforeEnter
beforeRouteEnter
next()
/posts ➡️ /admin
Fetch Admin Component
The
Sad PARTS
No clear Division of responsibilities
History
Router
router.beforeEach
function beforeEach(guard: NavigationGuard): ListenerRemover {
this.beforeGuards.push(guard)
return () => {
const i = this.beforeGuards.indexOf(guard)
if (i > -1) this.beforeGuards.splice(i, 1)
}
}
router.push
function push (location, onComplete, onAbort) {
this.history.push(location, onComplete, onAbort)
}
No clear Division of responsibilities
- Harder to contribute
- Harder to extend
beforeRouteEnter
<template>
<p>Hello {{ user.name }}!</p>
</template>
<script>
export default {
data: () => ({ user: null }),
async beforeRouteEnter(to, from, next) {
const user = await fetchCurrentUser()
next(vm => {
vm.user = user
})
}
}
</script>
Oops!
After mounted
Differences between guards
beforeRouteEnter
beforeRouteUpdate beforeRouteLeave
❌ No access to the component instance (this)
âś… Can pass a callback
âś… Access to the component instance (this)
❌ Callback is ignored
Save
Vue
Router
đź—‚ HISTORy
-
Store visited URLs
-
JS ↔ URL
-
push() replace(), ...
-
listen()
-
🚦 ROUTER
-
Route matching
-
​match()
-
​resolve()
-
-
Navigation
-
​​currentRoute
-
push() replace(), ...
-
​beforeEach(), ...
-
-
Creating routes
-
new Router({ routes })
-
addRoutes()
-
đź—‚ HISTORy
-
push() replace()
-
listen()
- URL parsing
API
- Modify the Location
- Parses URL
- path
- query
- hash
- Notifies when Location changes
- Handles Encoding problems
- Can be overloaded
Responsibilities / Expectations
🚦 ROUTER
-
Route matching
-
​resolve()
-
- Adding Route Records
-
addRouteRecord
-
removeRouteRecord
-
-
Navigation
-
​​currentRoute
-
push() replace(), ...
-
-
Navigation Guards
-
​beforeEach(), ...
-
- Dynamic Routing
-
​addRoute / removeRoute
-
🚦 ROUTER
🛣 Matcher
🛣 Matcher
-
Route matching
-
​resolve()
-
- Adding Route Records
-
addRouteRecord
-
removeRouteRecord
-
API
- Resolving a Router Location to a Route Record
- Only handles the path of URL
- Handles priority of Route Records
- Parses/handle params
Responsibilities / Expectations
-
Navigation
-
​​currentRoute
-
push(), replace()
-
-
Navigation Guards
-
​beforeEach(), ...
-
- Dynamic Routing
- Lazy loading Pages
- Error handlers
🚦 ROUTER
API
- Async navigation
- Trigger Navigation guards
- Expose current Route Location
- Handle redirections
- Dynamic Routing
Responsibilities / Expectations
Cool
new
Vue
Router
The Future is in Typescript
export type RouteRecord =
| RouteRecordSingleView
| RouteRecordMultipleViews
| RouteRecordRedirect
- Easier to contribute
- Less regressions
- Improved discoverability
<router-link
to="/about"
v-slot="{ href, route, navigate, isActive }"
>
<NavLink
:active="isActive"
:href="href"
@click="navigate"
>{{ route.fullPath }}</NavLink>
</router-link>
router-link scoped slot
Navigation Failures
- Navigating to the current location
- Cancelling navigation in a guard
- Redirecting
- Aborting
- Unhandled Errors
try {
await router.push('/somewhere')
} catch (err) {
console.log(
'Did not make it to ' + err.to.fullPath
)
}
Navigation Failures
Complete Stacktraces
Complete Stacktraces
function createHistory(): RouterHistory {
const base = createAbstractHistory()
const navigationStack = []
// overload push/replace methods to write the stack
function push() {}
function replace() {}
return {
...base,
push,
replace,
navigationStack,
}
}
Custom History
new Router({
mode: createHistory(),
})
Custom History
new Router({
routes: [
{ path: '/(.*)', name: 'NotFound' },
{ path: '/home', name: 'Home' }
]
})
Path Ranking
paths.esm.dev
router.addRoute({ name: 'User', path: '/users/:id' })
router.addRoute({ name: 'UserById', path: '/users/:id(\d+)' })
router.addRoute({ name: 'Myself', path: '/users/me' })
router.removeRoute({ name: 'TemporaryRoute' })
router.resolve('/users/me') // -> { name: 'Myself' }
router.resolve('/users/230') // -> { name: 'UserById' }
router.resolve('/users/posva') // -> { name: 'User' }
Dynamic Routing
secret
routes
if (loggedIn) router.addRoutes()
import { useLink } from 'vue-router'
function useConfirmLink(to, message) {
const link = useLink(to)
function navigate() {
if (window.confirm(message)) return link.navigate()
}
return { ...link, navigate }
}
Composition API
Composition API
​useLink
useLocation
onBeforeRouteLeave
onBeforeRouteUpdate
More Tests
- Full unit test coverage
- Automated Cross Browser E2E tests
RFCs
- Scoped Slot API for router-link
- Better active matching
- a11y improvements
- More navigation information
Vue 2 & 3
- Support both versions
Vue Router 4
Vue Router 4
Vue 2
Vue 3
Focus on supporting Vue 3
Smaller improvements for Vue 2
Sponsors 🙌
esm.dev/sponsor
Thanks! đź––
A new Router to guide you
By Eduardo San Martin Morote
A new Router to guide you
Vue official router has been a pleasure to build apps with but as the community grows, so do the router needs. However, it hasn't kept up as good as I wish it had with all the good proposals out there. As a result, we have also taken more time to work on the new version of Vue router and shaping an API that will serve Vue 2 and Vue 3. A more extensible router, easier to contribute and new architecture from scratch. Let's talk about what has changed in the new Router and all the improvements we are bringing.
- 3,269