Skip to content
Перевод синхронизирован с документацией от , хэш коммита 0d8d997.

Миграция с Vue 2

Большая часть Vue Router API осталась неизменной при переписывании с версии 3 (для Vue 2) на версию 4 (для Vue 3). Однако всё равно существуют несколько изменений без обратной совместимости, с которыми вы можете столкнуться при миграции вашего приложения. Это руководство предназначено, чтобы помочь вам понять, почему произошли эти изменения, и как адаптировать ваше приложение, чтобы оно работало с Vue Router 4.

Критические изменения

Изменения расположены в порядке их использования. Поэтому рекомендуется следовать этому списку по порядку.

createRouter заместо new Router

Vue Router больше не является классом, а представляет собой набор функций. Вместо того чтобы писать new Router(), теперь нужно вызывать createRouter:

js
// раньше было
// import Router from 'vue-router'
import { createRouter } from 'vue-router'

const router = createRouter({
  // ...
})

Новая опция history для замены mode

Опция mode: 'history' была заменена на более гибкую с названием history. В зависимости от того, какой режим вы использовали, вам придется заменить его на соответствующую функцию:

  • "history": createWebHistory()
  • "hash": createWebHashHistory()
  • "abstract": createMemoryHistory()

Полный пример:

js
import { createRouter, createWebHistory } from 'vue-router'
// существуют также createWebHashHistory и createMemoryHistory

createRouter({
  history: createWebHistory(),
  routes: [],
})

При рендеринге на стороне сервера (SSR) необходимо вручную передать соответствующий history:

js
// 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):

js
import { createRouter, createWebHistory } from 'vue-router'
createRouter({
  history: createWebHistory('/base-directory/'),
  routes: [],
})

Удаление опции fallback

Опция fallback больше не поддерживается при создании маршрутизатора:

diff
-new VueRouter({
+createRouter({
-  fallback: false,
// другие опции...
})

Причина: Все браузеры, поддерживаемые Vue, поддерживают HTML5 History API, что позволяет нам избежать хаков с модификацией location.hash и напрямую использовать history.pushState().

Удалены маршруты * (звездочка или все)

Теперь все маршруты (*, /*) должны определяться с помощью параметра с регулярным выражением:

js
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:

ts
const { page } = router.currentRoute.query
const { page } = router.currentRoute.value.query

onReady заменена на isReady

Существующая функция router.onReady() была заменена на router.isReady(), которая не принимает никаких аргументов и возвращает Promise:

js
// была заменена
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:

template
<router-view v-slot="{ Component }">
  <transition>
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </transition>
</router-view>

Причина: Это было необходимое изменение. См. связанный RFC.

Свойство append было удалено из <router-link>. Вместо этого можно вручную конкатенировать значение c существующим path:

template
замените
<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:

js
app.config.globalProperties.append = (path, pathToAppend) =>
  path + (path.endsWith('/') ? '' : '/') + pathToAppend

Причина: append использовался нечасто, его легко воспроизвести в пользовательской среде.

Входные параметры event и tag были удалены из <router-link>. Для полной настройки <router-link> можно использовать v-slot API:

template
замените
<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 был удален, потому что проблема, которую он решал, больше не существует, поэтому его можно безопасно убрать. Однако есть две вещи, о которых стоит знать:

  • Теперь маршруты активны на основе записей о маршрутах, которые они представляют, а не на основе сгенерированных объектов местоположения маршрута и их свойств 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:

js
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 может потребоваться дождаться, пока маршрутизатор будет готов перед монтированием приложения:

js
app.use(router)
// Примечание: на стороне сервера необходимо вручную указать начальный маршрут
router.isReady().then(() => app.mount('#app'))

В противном случае анимация перехода будет выполнена, как если бы вы предоставили свойство appear для transition, потому что маршрутизатор отображает своё начальное положение (ничего) и затем отображает первое положение.

Обратите внимание, что если при начальной навигации у вас есть навигационные хуки, то блокировать рендеринг приложения до их разрешения, возможно, не стоит, если только вы не используете рендеринг на стороне сервера. В этом случае, монтируйте приложение, не дожидаясь готовности маршрутизатора , чтобы получить тот же результат, что и в Vue 2.

router.app был удален

router.app используется для обозначения последнего корневого компонента (экземпляра Vue), который внедрил маршрутизатор. Теперь Vue Router может безопасно использоваться несколькими приложениями Vue одновременно. При использовании маршрутизатора его по-прежнему можно добавлять:

js
app.use(router)
router.app = app

Вы также можете расширить TypeScript определение интерфейса Router для добавления свойства app.

Причина: Приложения Vue 3 не существуют во Vue 2, и теперь мы должным образом поддерживаем несколько приложений, использующих один экземпляр Router, поэтому наличие свойства app вводило бы в заблуждение, поскольку вместо корневого экземпляра использовалось бы приложение.

Передача контента в <slot> компонентов маршрута

Раньше можно было напрямую передать шаблон для отображения через <slot> компонентов маршрута , вложив его в компонент <router-view>:

template
<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:

template
<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:

js
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():

js
// замените
history.pushState(myState, '', url)
// на это
await router.push(url)
history.replaceState({ ...history.state, ...myState }, '')

Аналогично, если вы вызывали history.replaceState() без сохранения текущего состояния, то вам нужно будет передать текущее history.state:

js
// замените
history.replaceState({}, '', url)
// на это
history.replaceState(history.state, '', url)

Причина: Мы используем состояние history для сохранения информации о навигации, такой как позиция прокрутки, предыдущее местоположение и т.д.

Опция routes обязательна в options

Свойство routes теперь обязательно в options.

js
createRouter({ routes: [] })

Причина: Маршрутизатор предназначен для создания маршрутов, хотя вы можете добавить их позже. В большинстве сценариев требуется хотя бы один маршрут, и в общем случае он пишется один раз для каждого приложения.

Несуществующие именованные маршруты

При push или resolve несуществующего именованного маршрута возникает ошибка:

js
// Упс, мы допустили опечатку в имени
router.push({ name: 'homee' }) // выбросит ошибку
router.resolve({ name: 'homee' }) // выбросит ошибку

Причина: Ранее маршрутизатор переходил по адресу /, но ничего не отображал (вместо главной страницы). Выброс ошибки имеет смысл, поскольку мы не можем создать корректный URL для перехода.

Отсутствие обязательных params в именованных маршрутах

При push или resolve именованного маршрута без необходимых параметров будет выдана ошибка:

js
// задан следующий маршрут:
const routes = [{ path: '/users/:id', name: 'user', component: UserDetails }]

// Отсутствие параметра `id` приведет к ошибке
router.push({ name: 'user' })
router.resolve({ name: 'user' })

Причина: Та же, что и выше.

Именованные дочерние маршруты с пустым path больше не добавляют слэш

Предположим, у нас есть вложенный именованный маршрут с пустым path:

js
const routes = [
  {
    path: '/dashboard',
    name: 'dashboard-parent',
    component: DashboardParent,
    children: [
      { path: '', name: 'dashboard', component: DashboardDefault },
      {
        path: 'settings',
        name: 'dashboard-settings',
        component: DashboardSettings,
      },
    ],
  },
]

При переходе или разрешении на именованный маршрут dashboard теперь будет выдаваться URL без завершающего слэша:

js
router.resolve({ name: 'dashboard' }).href // '/dashboard'

Это имеет важный побочный эффект в отношении подобных дочерних redirect записей:

js
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@3vue-router@4
RouteConfigRouteRecordRaw
LocationRouteLocation
RouteRouteLocationNormalized

Новые возможности

Среди новых возможностей Vue Router 4 следует отметить следующие:

Released under the MIT License.