Миграция с Vue 2
Большая часть Vue Router API осталась неизменной при переписывании с версии 3 (для Vue 2) на версию 4 (для Vue 3). Однако всё равно существуют несколько изменений без обратной совместимости, с которыми вы можете столкнуться при миграции вашего приложения. Это руководство предназначено, чтобы помочь вам понять, почему произошли эти изменения, и как адаптировать ваше приложение, чтобы оно работало с Vue Router 4.
Критические изменения
Изменения расположены в порядке их использования. Поэтому рекомендуется следовать этому списку по порядку.
createRouter заместо new Router
Vue Router больше не является классом, а представляет собой набор функций. Вместо того чтобы писать new Router()
, теперь нужно вызывать createRouter
:
// раньше было
// import Router from 'vue-router'
import { createRouter } from 'vue-router'
const router = createRouter({
// ...
})
Новая опция history
для замены mode
Опция mode: 'history'
была заменена на более гибкую с названием history
. В зависимости от того, какой режим вы использовали, вам придется заменить его на соответствующую функцию:
"history"
:createWebHistory()
"hash"
:createWebHashHistory()
"abstract"
:createMemoryHistory()
Полный пример:
import { createRouter, createWebHistory } from 'vue-router'
// существуют также createWebHashHistory и createMemoryHistory
createRouter({
history: createWebHistory(),
routes: [],
})
При рендеринге на стороне сервера (SSR) необходимо вручную передать соответствующий history:
// router.js
let history = isServer ? createMemoryHistory() : createWebHistory()
let router = createRouter({ routes, history })
// где-то в файле server-entry.js
router.push(req.url) // url запроса
router.isReady().then(() => {
// разрешенный запрос
})
Причина: включение оптимизации встряхивания дерева (tree shaking) неиспользуемых history режимов, а также реализация пользовательских историй для сложных сценариев, таких как нативные решения.
Опция base
была перемещена
Опция base
теперь передается в качестве первого аргумента функции createWebHistory
(и других history):
import { createRouter, createWebHistory } from 'vue-router'
createRouter({
history: createWebHistory('/base-directory/'),
routes: [],
})
Удаление опции fallback
Опция fallback
больше не поддерживается при создании маршрутизатора:
-new VueRouter({
+createRouter({
- fallback: false,
// другие опции...
})
Причина: Все браузеры, поддерживаемые Vue, поддерживают HTML5 History API, что позволяет нам избежать хаков с модификацией location.hash
и напрямую использовать history.pushState()
.
Удалены маршруты *
(звездочка или все)
Теперь все маршруты (*
, /*
) должны определяться с помощью параметра с регулярным выражением:
const routes = [
// pathMatch - это имя параметра, например, переход на /not/found даст
// { params: { pathMatch: ['not', 'found'] }}
// это происходит благодаря последнему *, означающему повторение params, и необходимо, если вы
// планируете напрямую перейти к маршруту not-found, используя его имя
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
// если опустить последний `*`, то символ `/` в параметрах будет
// закодирован при resolve или push
{ path: '/:pathMatch(.*)', name: 'bad-not-found', component: NotFound },
]
// плохой пример при использовании именованных маршрутов:
router.resolve({
name: 'bad-not-found',
params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
// хороший пример:
router.resolve({
name: 'not-found',
params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'
Совет
Вам не нужно добавлять *
для повторяющихся параметров, если вы не планируете напрямую переходить на несуществующий маршрут, используя его имя. Если вы вызываете router.push('/not/found/url')
, то он предоставит правильный параметр pathMatch
.
Причина: Vue Router больше не использует path-to-regexp
, вместо этого он реализует собственную систему парсинга, которая позволяет ранжировать маршруты и обеспечивает динамическую маршрутизацию. Поскольку мы обычно добавляем всего один универсальный маршрут на проект, нет большой выгоды в поддержке специального синтаксиса для *
. Кодирование параметров применяется ко всем маршрутам без исключений, чтобы упростить прогнозируемость поведения.
Свойство currentRoute
теперь является ref()
Раньше доступ к свойствам объекта currentRoute
на экземпляре маршрутизатора можно было получить напрямую.
С появлением vue-router v4 базовый тип объекта currentRoute
у экземпляра маршрутизатора изменился на Ref<RouteLocationNormalizedLoaded>
, что следует из более новых основ реактивности, представленных во Vue 3.
Хотя это ничего не меняет, если вы читаете маршрут с помощью useRoute()
или this.$route
, если вы обращаетесь к нему напрямую через экземпляр маршрутизатора, то вам нужно будет обращаться к фактическому объекту маршрута через currentRoute.value
:
const { page } = router.currentRoute.query
const { page } = router.currentRoute.value.query
onReady
заменена на isReady
Существующая функция router.onReady()
была заменена на router.isReady()
, которая не принимает никаких аргументов и возвращает Promise:
// была заменена
router.onReady(onSuccess, onError)
// на
router.isReady().then(onSuccess).catch(onError)
// или с использованием await:
try {
await router.isReady()
// onSuccess
} catch (err) {
// onError
}
Изменения scrollBehavior
Объект, возвращаемый в scrollBehavior
, теперь аналогичен ScrollToOptions
: x
переименован в left
, а y
- в top
. См. RFC.
Причина: сделать объект похожим на ScrollToOptions
, чтобы он был более привычен к нативному JS API и потенциально позволял использовать новые опции в будущем.
<router-view>
, <keep-alive>
и <transition>
Теперь transition
и keep-alive
должны использоваться внутри RouterView
через v-slot
API:
<router-view v-slot="{ Component }">
<transition>
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
Причина: Это было необходимое изменение. См. связанный RFC.
Удаление свойства append
в <router-link>
Свойство append
было удалено из <router-link>
. Вместо этого можно вручную конкатенировать значение c существующим path
:
замените
<router-link to="child-route" append>to relative child</router-link>
на это
<router-link :to="append($route.path, 'child-route')">
to relative child
</router-link>
Вы должны определить глобальную функцию append
для своего экземпляра App:
app.config.globalProperties.append = (path, pathToAppend) =>
path + (path.endsWith('/') ? '' : '/') + pathToAppend
Причина: append
использовался нечасто, его легко воспроизвести в пользовательской среде.
Удаление входных параметорв event
и tag
из <router-link>
Входные параметры event
и tag
были удалены из <router-link>
. Для полной настройки <router-link>
можно использовать v-slot
API:
замените
<router-link to="/about" tag="span" event="dblclick">About Us</router-link>
на это
<router-link to="/about" custom v-slot="{ navigate }">
<span @click="navigate" @keypress.enter="navigate" role="link">About Us</span>
</router-link>
Причина: Эти свойства часто использовались вместе для создания чего-то, отличного от тега <a>
, но они были введены до появления v-slot
API и используются недостаточно часто, чтобы оправдать добавление их в размер бандла для всех.
Удаление входного параметра exact
из <router-link>
.
Входной параметр exact
был удален, потому что проблема, которую он решал, больше не существует, поэтому его можно безопасно убрать. Однако есть две вещи, о которых стоит знать:
- Теперь маршруты активны на основе записей о маршрутах, которые они представляют, а не на основе сгенерированных объектов местоположения маршрута и их свойств
path
,query
иhash
- Сопоставляется только секция
path
,query
иhash
больше не учитываются
Если вы хотите настроить это поведение, например, учесть секцию hash
, то для расширения <router-link>
следует воспользоваться v-slot
API.
Причина: Подробнее см. изменения в RFC о сопоставлении активных маршрутов.
Навигационные хуки в миксинах игнорируются
На данный момент навигационные хуки в миксинах не поддерживаются. Отслеживать поддержку можно тут: vue-router#454.
Удаление router.match
и изменение router.resolve
И router.match
, и router.resolve
были объединены в router.resolve
с несколько иной сигнатурой. [Более подробная информация приведена в API] (/api/interfaces/Router.md#resolve).
Причина: Объединение нескольких методов, которые использовались для одной и той же цели.
Функция router.getMatchedComponents()
была удалена
Метод router.getMatchedComponents
теперь удален, поскольку совпадающие компоненты могут быть получены из router.currentRoute.value.matched
:
router.currentRoute.value.matched.flatMap(record =>
Object.values(record.components)
)
Причина: Данный метод использовался только в SSR и является одноразовым и может быть написан пользователем.
Записи перенаправления не могут использовать специальные пути
Ранее не документированная возможность позволяла установить запись перенаправления на специальный путь типа /events/:id
и при этом повторно использовать существующий параметр id
. Теперь это невозможно, и есть два варианта:
- Использование имени маршрута без параметра:
redirect: { name: 'events' }
. Обратите внимание, что это не будет работать, если параметр:id
является необязательным - Использование функции для воссоздания нового местоположения на основе цели:
redirect: to => ({ name: 'events', params: to.params })
.
Причина: Этот синтаксис использовался редко и был другим способом выполнения задач, который не был достаточно коротким по сравнению с версиями выше, при этом вносил некоторую сложность и делал маршрутизатор более тяжелым.
Все переходы навигации теперь всегда асинхронные
Все переходы навигации, включая первую, теперь асинхронные, то есть при использовании transition
может потребоваться дождаться, пока маршрутизатор будет готов перед монтированием приложения:
app.use(router)
// Примечание: на стороне сервера необходимо вручную указать начальный маршрут
router.isReady().then(() => app.mount('#app'))
В противном случае анимация перехода будет выполнена, как если бы вы предоставили свойство appear
для transition
, потому что маршрутизатор отображает своё начальное положение (ничего) и затем отображает первое положение.
Обратите внимание, что если при начальной навигации у вас есть навигационные хуки, то блокировать рендеринг приложения до их разрешения, возможно, не стоит, если только вы не используете рендеринг на стороне сервера. В этом случае, монтируйте приложение, не дожидаясь готовности маршрутизатора , чтобы получить тот же результат, что и в Vue 2.
router.app
был удален
router.app
используется для обозначения последнего корневого компонента (экземпляра Vue), который внедрил маршрутизатор. Теперь Vue Router может безопасно использоваться несколькими приложениями Vue одновременно. При использовании маршрутизатора его по-прежнему можно добавлять:
app.use(router)
router.app = app
Вы также можете расширить TypeScript определение интерфейса Router
для добавления свойства app
.
Причина: Приложения Vue 3 не существуют во Vue 2, и теперь мы должным образом поддерживаем несколько приложений, использующих один экземпляр Router, поэтому наличие свойства app
вводило бы в заблуждение, поскольку вместо корневого экземпляра использовалось бы приложение.
Передача контента в <slot>
компонентов маршрута
Раньше можно было напрямую передать шаблон для отображения через <slot>
компонентов маршрута , вложив его в компонент <router-view>
:
<router-view>
<p>In Vue Router 3, I render inside the route component</p>
</router-view>
В связи с введением v-slot
api для <router-view>
, необходимо передавать его в <component>
, используя v-slot
API:
<router-view v-slot="{ Component }">
<component :is="Component">
<p>In Vue Router 3, I render inside the route component</p>
</component>
</router-view>
Удаление parent
из местоположений маршрутов
Свойство parent
было удалено из нормализованных местоположений маршрутов (this.$route
и объекта, возвращаемого функцией router.resolve
). Доступ к нему по-прежнему можно получить через массив matched
:
const parent = this.$route.matched[this.$route.matched.length - 2]
Причина: Наличие parent
и children
создает ненужные круговые ссылки, в то время как эти свойства могут быть уже получены через matched
.
Свойство pathToRegexpOptions
было удалено
Свойства pathToRegexpOptions
и caseSensitive
записей маршрутов были заменены на опции sensitive
и strict
для createRouter()
. Теперь они также могут передаваться напрямую при создании маршрутизатора с помощью createRouter()
. Любые другие опции, специфичные для path-to-regexp
, были удалены, поскольку path-to-regexp
больше не используется для парсинга путей.
Удаление неименованных параметров
В связи с удалением path-to-regexp
, неименованные параметры больше не поддерживаются:
/foo(/foo)?/suffix
становится/foo/:_(foo)?/suffix
/foo(foo)?
становится/foo:_(foo)?
/foo/(.*)
становится/foo/:_(.*)
Совет
Обратите внимание, что вместо _
для параметра можно использовать любое имя. Главное, чтобы оно было одно.
Использование history.state
Vue Router сохраняет информацию в history.state
. Если у вас есть код, вручную вызывающий history.pushState()
, то, скорее всего, вам следует отказаться от него или отрефакторить его с помощью обычного router.push()
и history.replaceState()
:
// замените
history.pushState(myState, '', url)
// на это
await router.push(url)
history.replaceState({ ...history.state, ...myState }, '')
Аналогично, если вы вызывали history.replaceState()
без сохранения текущего состояния, то вам нужно будет передать текущее history.state
:
// замените
history.replaceState({}, '', url)
// на это
history.replaceState(history.state, '', url)
Причина: Мы используем состояние history для сохранения информации о навигации, такой как позиция прокрутки, предыдущее местоположение и т.д.
Опция routes
обязательна в options
Свойство routes
теперь обязательно в options
.
createRouter({ routes: [] })
Причина: Маршрутизатор предназначен для создания маршрутов, хотя вы можете добавить их позже. В большинстве сценариев требуется хотя бы один маршрут, и в общем случае он пишется один раз для каждого приложения.
Несуществующие именованные маршруты
При push или resolve несуществующего именованного маршрута возникает ошибка:
// Упс, мы допустили опечатку в имени
router.push({ name: 'homee' }) // выбросит ошибку
router.resolve({ name: 'homee' }) // выбросит ошибку
Причина: Ранее маршрутизатор переходил по адресу /
, но ничего не отображал (вместо главной страницы). Выброс ошибки имеет смысл, поскольку мы не можем создать корректный URL для перехода.
Отсутствие обязательных params
в именованных маршрутах
При push или resolve именованного маршрута без необходимых параметров будет выдана ошибка:
// задан следующий маршрут:
const routes = [{ path: '/users/:id', name: 'user', component: UserDetails }]
// Отсутствие параметра `id` приведет к ошибке
router.push({ name: 'user' })
router.resolve({ name: 'user' })
Причина: Та же, что и выше.
Именованные дочерние маршруты с пустым path
больше не добавляют слэш
Предположим, у нас есть вложенный именованный маршрут с пустым path
:
const routes = [
{
path: '/dashboard',
name: 'dashboard-parent',
component: DashboardParent,
children: [
{ path: '', name: 'dashboard', component: DashboardDefault },
{
path: 'settings',
name: 'dashboard-settings',
component: DashboardSettings,
},
],
},
]
При переходе или разрешении на именованный маршрут dashboard
теперь будет выдаваться URL без завершающего слэша:
router.resolve({ name: 'dashboard' }).href // '/dashboard'
Это имеет важный побочный эффект в отношении подобных дочерних redirect
записей:
const routes = [
{
path: '/parent',
component: Parent,
children: [
// теперь это будет перенаправление на `/home`, а не на `/parent/home`.
{ path: '', redirect: 'home' },
{ path: 'home', component: Home },
],
},
]
Обратите внимание, что это будет работать, если path
был /parent/
, так как относительное расположение home
к /parent/
действительно /parent/home
, но относительное расположение home
к /parent
- /home
.
Причина: Это сделано для того, чтобы сделать поведение с слэшем единообразным: по умолчанию все маршруты допускают слэш. Это можно отключить, используя опцию strict
и вручную добавляя (или не добавляя) слэш к маршрутам.
Кодировка параметров $route
Декодированные значения в params
, query
и hash
теперь совпадают независимо от того, где инициирована навигация (в старых браузерах по-прежнему будут выдаваться некодированные path
и fullPath
). Начальная навигация должна давать те же результаты, что и навигация в приложении.
Дано любюе нормализованное расположение маршрута:
- Значения в
path
,fullPath
больше не декодируются. Они будут отображаться в том виде, в каком их предоставляет браузер (большинство браузеров предоставляют их в кодированном виде). Например, при прямой записи в адресной строкеhttps://example.com/hello world
будет получена кодированная версия:https://example.com/hello%20world
, аpath
иfullPath
будут иметь вид/hello%20world
. - Теперь
hash
декодирован, что позволяет скопировать его:router.push({ hash: $route.hash })
и использовать напрямую в опцииel
в scrollBehavior. - При использовании
push
,resolve
иreplace
и указании в объекте местоположенияstring
или свойстваpath
, они должны быть закодированы (как и в предыдущей версии). С другой стороны,params
,query
иhash
должны предоставляться в некодированном виде. - Теперь символ слэша (
/
) корректно декодируется внутриparams
, при этом в URL сохраняется его кодированная версия:%2F
.
Причина: Это позволяет легко копировать существующие свойства местоположения при вызове router.push()
и router.resolve()
, а также сделать результирующее местоположение маршрута согласованным в разных браузерах. router.push()
теперь является идемпотентным, то есть вызов router.push(route.fullPath)
, router.push({ hash: route.hash })
, router.push({ query: route.query })
и router.push({ params: route.params })
не будет создавать лишней кодировки.
$router.push()
и $router.replace()
- коллбеки onComplete
и onAbort
Ранее методы $router.push()
и $router.replace()
принимали два коллбека — onComplete
и onAbort
— в качестве второго и третьего аргументов. Эти функции вызывались после завершения навигации в зависимости от результата. С введением API, основанного на Promise, эти коллбеки стали избыточными и были удалены. Подробнее о том, как отслеживать успешную и неудачную навигацию, см. в разделе Сбои при навигации.
Причина: Сокращение размера библиотеки, адаптировав её к современным стандартам JavaScript (Promises).
Изменения для TypeScript
Чтобы сделать типизацию более последовательной и выразительной, некоторые типы были переименованы:
vue-router@3 | vue-router@4 |
---|---|
RouteConfig | RouteRecordRaw |
Location | RouteLocation |
Route | RouteLocationNormalized |
Новые возможности
Среди новых возможностей Vue Router 4 следует отметить следующие: