Миграция с Vue 2
Большая часть API Vue Router осталась неизменной во время переписывания с версии v3 (для Vue 2) на v4 (для Vue 3), но всё же есть несколько критических изменений, с которыми вы можете столкнуться при миграции приложения. Это руководство поможет вам понять, почему произошли эти изменения и как адаптировать ваше приложение для работы с Vue Router 4.
Критические изменения
Изменения упорядочены по частоте использования. Поэтому рекомендуется следовать этому списку в порядке.
new Router стало createRouter
Vue Router больше не является классом, а представляет собой набор функций. Вместо того чтобы писать new Router(), теперь вы должны вызвать createRouter:
// previously was
// 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'
// there is also createWebHashHistory and createMemoryHistory
createRouter({
history: createWebHistory(),
routes: [],
})
При SSR необходимо вручную передать соответствующую историю:
// router.js
let history = isServer ? createMemoryHistory() : createWebHistory()
let router = createRouter({ routes, history })
// somewhere in your server-entry.js
router.push(req.url) // request url
router.isReady().then(() => {
// resolve the request
})
Причина: возможность исключения неиспользуемых историй с помощью tree shaking, а также реализация пользовательских историй для расширенных случаев использования, таких как нативные решения.
Перемещение параметра base
Параметр base теперь передаётся в качестве первого аргумента в createWebHistory (и другие истории):
import { createRouter, createWebHistory } from 'vue-router'
createRouter({
history: createWebHistory('/base-directory/'),
routes: [],
})
Удаление параметра fallback
Параметр fallback больше не поддерживается при создании маршрутизатора:
-new VueRouter({
+createRouter({
- fallback: false,
// other options...
})
Причина: все браузеры, поддерживаемые Vue, поддерживают API истории HTML5, что позволяет избежать обходных путей с модификацией location.hash и напрямую использовать history.pushState().
Удаление маршрутов «все» (звёздочка или catch all)
Маршруты «все» (*, /* необходимо теперь определять с помощью параметра со специальным регулярным выражением:
const routes = [
// pathMatch is the name of the param, e.g., going to /not/found yields
// { params: { pathMatch: ['not', 'found'] }}
// this is thanks to the last *, meaning repeated params and it is necessary if you
// plan on directly navigating to the not-found route using its name
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
// if you omit the last `*`, the `/` character in params will be encoded when resolving or pushing
{ path: '/:pathMatch(.*)', name: 'bad-not-found', component: NotFound },
]
// bad example if using named routes:
router.resolve({
name: 'bad-not-found',
params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
// good example:
router.resolve({
name: 'not-found',
params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'
СОВЕТВам не нужно добавлять
*для повторяющихся параметров, если вы не планируете напрямую переходить на маршрут не найден используя его имя. Если вы вызываетеrouter.push('/not/found/url'), он предоставит правильный параметрpathMatch.
Причина: Vue Router больше не использует path-to-regexp; вместо этого он реализует собственную систему разбора, позволяющую сортировать маршруты и использовать динамическое маршрутизирование. Поскольку мы обычно добавляем один единственный маршрут «все» в проект, нет большой выгоды в поддержке специального синтаксиса для *.
Замена onReady на isReady
Существующая функция router.onReady() была заменена на router.isReady(), которая не принимает аргументов и возвращает промис:
// replace
router.onReady(onSuccess, onError)
// with
router.isReady().then(onSuccess).catch(onError)
// or use 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 через API v-slot:
<router-view v-slot="{ Component }">
<transition>
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
Причина: Это необходимое изменение. Смотрите соответствующий RFC.
Удаление параметра append в <router-link>
Параметр append был удалён из <router-link>. Вместо этого вы можете вручную конкатенировать значение с существующим path:
replace
<router-link to="child-route" append>to relative child</router-link>
with
<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>. Вы можете использовать v-slot API для полной настройки <router-link>:
replace
<router-link to="/about" tag="span" event="dblclick">About Us</router-link>
with
<router-link to="/about" custom v-slot="{ navigate }">
<span @click="navigate" @keypress.enter="navigate" role="link">About Us</span>
</router-link>
Причина: Эти параметры часто использовались вместе для использования чего-то отличного от тега <a>, но были введены до API v-slot и недостаточно используются, чтобы оправдать увеличение размера сборки для всех.
Удаление параметра exact в <router-link>
Параметр exact был удалён, так как проблема, которую он решал, больше не актуальна. Однако необходимо учитывать два момента:
- Маршруты теперь активны на основе записей маршрутов, а не сгенерированных объектов расположения маршрутов и их свойств
path,query, иhash - Сопоставляется только раздел
path,query, иhashбольше не учитываются.
Если вам необходимо настроить это поведение, например, учитывать раздел hash, используйте v-slot API для расширения <router-link>.
Причина: см. RFC о изменениях в сопоставлении активных ссылок для получения дополнительных сведений.
Навигационные гварды в миксинах игнорируются
В настоящее время навигационные гварды в миксинах не поддерживаются. Следите за поддержкой на vue-router#454.
Удаление router.match и изменения в router.resolve
router.match, и router.resolve объединены в router.resolve с немного изменённой сигнатурой. См. API для получения дополнительных сведений.
Причина: Объединение нескольких методов, которые использовались для одной цели.
Удаление router.getMatchedComponents()
Метод router.getMatchedComponents теперь удалён, так как соответствующие компоненты можно получить из router.currentRoute.value.matched:
router.currentRoute.value.matched.flatMap(record =>
Object.values(record.components)
)
Причина: Этот метод использовался только при SSR и представляет собой одну строку кода, которую пользователь может легко реализовать самостоятельно.
**Все** переходы теперь всегда асинхронны
Все переходы, включая первый, теперь асинхронные. Это означает, что при использовании transition, возможно, потребуется подождать, пока маршрутизатор не станет готовым, прежде чем монтировать приложение:
app.use(router)
// Note: on Server Side, you need to manually push the initial location
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, и теперь мы правильно поддерживаем несколько приложений, использующих один и тот же экземпляр маршрутизатора. Поэтому наличие свойства app было бы вводящим в заблуждение, поскольку оно относилось бы к приложению, а не к корневому экземпляру.
Передача содержимого компонентам маршрута через <slot>
Ранее вы могли напрямую передавать шаблон для рендеринга компонентами маршрута через <slot>, вкладывая его в компонент <router-view>:
<router-view>
<p>In Vue Router 3, I render inside the route component</p>
</router-view>
Из-за введения API v-slot для <router-view>, вы должны передавать его компоненту <component> с использованием API v-slot:
<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():
// replace
history.pushState(myState, '', url)
// with
await router.push(url)
history.replaceState({ ...history.state, ...myState }, '')
Аналогично, если вы вызывали history.replaceState() без сохранения текущего состояния, вам необходимо передать текущее history.state:
// replace
history.replaceState({}, '', url)
// with
history.replaceState(history.state, '', url)
Причина: Мы используем состояние истории для сохранения информации о навигации, такой как позиция прокрутки, предыдущее местоположение и т.д.
Опция routes обязательна в options
Свойство routes теперь является обязательным в options.
createRouter({ routes: [] })
Причина: Роутер разработан для создания с маршрутами, хотя вы можете добавить их позже. Вам в большинстве сценариев нужен хотя бы один маршрут, и это записывается один раз на приложение в целом.
Отсутствующие именованные маршруты
Попытка перехода или разрешения несуществующего именованного маршрута вызывает ошибку:
// Oops, we made a typo in name
router.push({ name: 'homee' }) // throws
router.resolve({ name: 'homee' }) // throws
Причина: Ранее роутер переходил на /, но ничего не отображал (вместо домашней страницы). Вызов ошибки более целесообразен, так как мы не можем создать допустимый URL для перехода.
Отсутствующие обязательные params в именованных маршрутах
Попытка перехода или разрешения именованного маршрута без его обязательных параметров вызовет ошибку:
// given the following route:
const routes = [{ path: '/users/:id', name: 'user', component: UserDetails }]
// Missing the `id` param will fail
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: [
// this would now redirect to `/home` instead of `/parent/home`
{ path: '', redirect: 'home' },
{ path: 'home', component: Home },
],
},
]
Обратите внимание, что это будет работать, если path был /parent/, так как относительное расположение home к /parent/ действительно /parent/home, но относительное расположение home к /parent равно /home.
Причина: Это сделано для согласованности поведения с конечным слешем: по умолчанию все маршруты допускают конечный слеш. Его можно отключить с помощью опции strict и вручную добавлять (или не добавлять) слеш к маршрутам.
Кодировка свойств маршрута
Декодированные значения в 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 })и использовать непосредственно в опцииelscrollBehavior. - При использовании
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 }) не создаст дополнительной кодировки.
Изменения в TypeScript
Для повышения согласованности и выразительности типов некоторые типы были переименованы:
vue-router@3 |
vue-router@4 |
|---|---|
| RouteConfig | RouteRecordRaw |
| Location | RouteLocation |
| Route | RouteLocationNormalized |
Новые возможности
Некоторые новые возможности Vue Router 4, которые стоит изучить:
© 2013–present Evan You
Licensed under the MIT License.
https://next.router.vuejs.org/guide/migration/