Переход на Universal Router обусловлен рядом архитектурных и практических факторов:
Vue Router, будучи тесно интегрированным с экосистемой Vue, предлагает декларативный подход, но ограничивает возможности расширения вне контекста Vue-приложений.
Vue Router:
<router-view>Universal Router:
Пример:
// Vue Router
const routes = [
{ path: '/users', component: UsersComponent }
]
// Universal Router
const routes = [
{
path: '/users',
action: () => {
return renderUsers()
}
}
]
В Vue Router переходы обрабатываются через встроенные механизмы:
router.push('/users')
В Universal Router используется явный вызов:
router.resolve('/users').then(result => {
// обработка результата
})
Это позволяет:
Перед переносом необходимо:
beforeEach,
beforeEnter):id,
:slug)npm install universal-router
import UniversalRouter from 'universal-router'
const routes = [
{
path: '',
children: [
{
path: '/',
action: () => renderHome()
},
{
path: '/about',
action: () => renderAbout()
}
]
}
]
const router = new UniversalRouter(routes)
Vue Router:
{ path: '/users/:id', component: User }
Universal Router:
{
path: '/users/:id',
action: ({ params }) => {
return renderUser(params.id)
}
}
Vue Router:
{
path: '/dashboard',
component: Dashboard,
children: [
{ path: 'stats', component: Stats }
]
}
Universal Router:
{
path: '/dashboard',
action: () => renderDashboard(),
children: [
{
path: '/stats',
action: () => renderStats()
}
]
}
В Vue Router отображение происходит автоматически через
<router-view>.
В Universal Router требуется ручной рендер:
router.resolve(location.pathname).then(result => {
document.getElementById('app').innerHTML = result
})
Это даёт:
document.addEventListener('click', event => {
const link = event.target.closest('a')
if (link && link.href) {
event.preventDefault()
history.pushState(null, '', link.pathname)
router.resolve(link.pathname).then(render)
}
})
window.addEventListener('popstate', () => {
router.resolve(location.pathname).then(render)
})
router.beforeEach((to, from, next) => {
if (!isAuth()) next('/login')
else next()
})
Guard’ы реализуются вручную:
const routes = [
{
path: '/dashboard',
action: async (context) => {
if (!isAuth()) {
return router.resolve('/login')
}
return renderDashboard()
}
}
]
Universal Router изначально поддерживает async/await:
{
path: '/posts/:id',
action: async ({ params }) => {
const post = await fetchPost(params.id)
return renderPost(post)
}
}
В Vue Router аналогичное поведение требует дополнительных решений.
const router = new UniversalRouter(routes, {
errorHandler: (error, context) => {
if (error.status === 404) {
return renderNotFound()
}
throw error
}
})
Universal Router позволяет передавать контекст:
router.resolve({
pathname: '/profile',
user: currentUser
})
И использовать его:
{
path: '/profile',
action: ({ user }) => renderProfile(user)
}
{
path: '/admin',
action: async () => {
const module = await import('./admin.js')
return module.renderAdmin()
}
}
При частичной миграции возможно сохранить Vue:
{
path: '/users',
action: () => {
new Vue({
render: h => h(UsersComponent)
}).$mount('#app')
}
}
1. Ожидание автоматического рендера
2. Игнорирование history API
3. Неправильная работа с асинхронностью
4. Потеря контекста
| Характеристика | Vue Router | Universal Router |
|---|---|---|
| Привязка к фреймворку | Жёсткая | Отсутствует |
| Рендер | Автоматический | Ручной |
| Guards | Встроенные | Кастомные |
| SSR | Ограниченный | Полноценный |
| Гибкость | Средняя | Высокая |
const routes = [
{
path: '/',
action: () => renderLayout(),
children: [
{
path: '',
action: () => renderHome()
},
{
path: '/users/:id',
action: async ({ params }) => {
const user = await getUser(params.id)
return renderUser(user)
}
}
]
}
]
const router = new UniversalRouter(routes)
Universal Router не предоставляет встроенного state management:
Возможна реализация:
Пример middleware:
function withAuth(action) {
return async (context) => {
if (!isAuth()) {
return router.resolve('/login')
}
return action(context)
}
}
Universal Router:
Vue Router:
test('user route', async () => {
const result = await router.resolve('/users/1')
expect(result).toContain('User 1')
})
Переход требует смены парадигмы:
Это даёт: