Vue2 搭配的是 vue-router@3.x 和 vuex@3.x,Vue3 则换成 vue-router@4.x 和 vuex@4.x。版本号都是 4.x,不要和 Vue 3.x 混为一谈。本文是全家桶系列第 3 篇,对照写法看迁移点。
为方便叙述,文中用 vue2.x router、vue3.x router 代替 vue-router@3.x 和 vue-router@4.x,用 vue2.x vuex、vue3.x vuex 代替 vuex@3.x 和 vuex@4.x。
系列目录:
| 点 |
vue2.x |
vue3.x |
| 创建路由 |
Vue.use(Router); new Router |
createRouter + createWebHistory |
| 滚动 |
{ x, y, selector, offset } |
{ el, left, top, behavior } |
| 父级 redirect |
路由选项 redirect |
子路由 path: ” + redirect |
| 通配 |
path: ‘/user/:a*’ |
/:catchAll(.*) |
| 当前匹配组件 |
router.getMatchedComponents |
currentRoute.value.matched |
| 拿 store/router |
this.$store / this.$router |
useStore / useRouter,不要用 ctx |
创建实例
// vue2.x router
import Vue from 'vue'
import Router from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
Vue.use(Router)
const router = new Router({
base: process.env.BASE_URL,
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes
})
export default router
// vue3.x router
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
scrollBehavior 滚动行为
vue3 弃用 vue2 的 { selector, x, y, offset },改用更接近原生 DOM 的 { el, left, top, behavior }:
// vue2.x router
const router = new Router({
base: process.env.BASE_URL,
mode: 'history',
scrollBehavior: () => ({ x: 0, y: 0 }),
routes
})
// vue3.x router
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
// scroll to id `#app` + 200px, and scroll smoothly (when supported by the browser)
return {
el: '#app',
top: 0,
left: 0,
behavior: 'smooth'
}
}
})
路由组件跳转
vue2 用路由选项 redirect 自动跳;vue3 去掉该选项,改为在子路由里加一个空路径来匹配跳转。
// vue2.x router
[
{
path: '/',
component: Layout,
name: 'WebHome',
meta: { title: '平台首页' },
redirect: '/dashboard', // 这里写跳转
children: [
{
path: 'dashboard',
name: 'Dashboard',
meta: { title: '工作台' },
component: () => import('../views/dashboard/index.vue')
}
]
}
]
// vue3.x router
[
{
path: '/',
component: Layout,
name: 'WebHome',
meta: { title: '平台首页' },
children: [
{ path: '', redirect: 'dashboard' }, // 这里写跳转
{
path: 'dashboard',
name: 'Dashboard',
meta: { title: '工作台' },
component: () => import('../views/dashboard/index.vue')
}
]
}
]
捕获所有路由:/:catchAll(.*)
捕获 (/*) 时必须用自定义正则参数 /:catchAll(.*):
// vue2.x router
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/user/:a*' },
]
})
// vue3.x router
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/user/:a:catchAll(.*)', component: component },
]
})
当路由为 /user/a/b 时,捕获到的 params 为 {{"a": "a", "catchAll": "/b"}}。
router.resolve
router.match 与 router.resolve 合并为 router.resolve,签名略有不同:
// vue2.x router
...
resolve ( to: RawLocation, current?: Route, append?: boolean) {
...
return {
location,
route,
href,
normalizedTo: location,
resolved: route
}
}
// vue3.x router
function resolve(
rawLocation: Readonly<RouteLocationRaw>,
currentLocation?: Readonly<RouteLocationNormalizedLoaded>
): RouteLocation & { href: string } {
...
let matchedRoute = matcher.resolve(matcherLocation, currentLocation)
...
return {
fullPath,
hash,
query: normalizeQuery(rawLocation.query),
...matchedRoute,
redirectedFrom: undefined,
href: routerHistory.base + fullPath,
}
}
获取当前路由
删除 router.getMatchedComponents,改从 router.currentRoute.value.matched 取。原文说明:该方法返回目标位置或当前路由匹配的组件数组(是定义/构造类,不是实例),常用于 SSR 数据预加载。
[{
aliasOf: undefined
beforeEnter: undefined
children: []
components: {default: {…}, other: {…}}
instances: {default: null, other: Proxy}
leaveGuards: []
meta: {}
name: undefined
path: "/"
props: ƒ (to)
updateGuards: []
}]
使用与就绪
如果使用过渡,可能需要等 router 就绪再挂载:
app.use(router)
// Note: on Server Side, you need to manually push the initial location
router.isReady().then(() => app.mount('#app'))
一般直接挂载也能用。但导航已是异步的,初始化时若有守卫,在 resolve 之前会出现一次初始渲染过渡,类似给过渡提供 appear。
SSR 要传合适的 history mode
const history = isServer ? createMemoryHistory() : createWebHistory()
const router = createRouter({ routes, history })
// on server only
router.push(req.url) // request url
router.isReady().then(() => {
// resolve the request
})
push / resolve 不存在的命名路由会报错
vue2 里 push 一个不存在的 name,会导航到根路由 / 且不渲染内容,控制台警告,URL 跳到 /。
const router = new VueRouter({
mode: 'history',
routes: [{ path: '/', name: 'foo', component: Foo }]
}
this.$router.push({ name: 'baz' })
vue3 同样写法会抛错:
const router = createRouter({
history: routerHistory(),
routes: [{ path: '/', name: 'foo', component: Foo }]
})
...
import { useRouter } from 'vue-next-router'
...
const router = userRouter()
router.push({ name: 'baz' })) // 这行代码会报错
获取路由:不要用 ctx
网上有教程通过 ctx 访问 router 和 store。这只在 development 有效,production 编译后那些属性访问不到,容易误导。
错误示例:
import { getCurrentInstance } from 'vue'
export default {
setup () {
const { ctx } = getCurrentInstance()
console.log(ctx)
console.log(ctx.$router.currentRoute.value)
const userId = computed(() => ctx.$store.state.app.userId)
return {
userId
}
}
}
正确使用:用 useRouter、useRoute、useStore。
import { getCurrentInstance } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
export default {
setup () {
const { ctx } = getCurrentInstance()
console.log(ctx)
const router = useRouter()
const route = useRoute()
const store = userStore()
console.log(router, route, store)
console.log(router.currentRoute.value)
const userId = computed(() => store.state.app.userId)
return {
userId
}
}
}
vuex 4.x breaking change 很少,整体改动不大。
创建实例
// vue2.x vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
getters: {},
modules: {}
}
// vue3.x vuex
import Vuex from 'vuex'
export default Vuex.createStore({
state: {},
mutations: {},
actions: {},
getters: {},
modules: {}
})
获取 store
// vue3.x vuex
import { getCurrentInstance } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
export default {
setup () {
const { ctx } = getCurrentInstance()
console.log(ctx)
const router = useRouter()
const route = useRoute()
const store = userStore()
console.log(router, route, store)
console.log(router.currentRoute.value)
const userId = computed(() => store.state.app.userId)
return {
userId
}
}
}
一句话总结:Vue3 路由用 createRouter / createWebHistory,通配改 catchAll,守卫异步要等 isReady;取实例用 useRouter/useStore,不要依赖 setup 里的 ctx。